QGIS API Documentation 3.30.0-'s-Hertogenbosch (f186b8efe0)
qgsmapboxglstyleconverter.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsmapboxglstyleconverter.cpp
3 --------------------------------------
4 Date : September 2020
5 Copyright : (C) 2020 by Nyall Dawson
6 Email : nyall dot dawson at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16
17/*
18 * Ported from original work by Martin Dobias, and extended by the MapTiler team!
19 */
20
24#include "qgssymbollayer.h"
25#include "qgssymbollayerutils.h"
26#include "qgslogger.h"
27#include "qgsfillsymbollayer.h"
28#include "qgslinesymbollayer.h"
29#include "qgsfontutils.h"
30#include "qgsjsonutils.h"
31#include "qgspainteffect.h"
32#include "qgseffectstack.h"
33#include "qgsblureffect.h"
36#include "qgsfillsymbol.h"
37#include "qgsmarkersymbol.h"
38#include "qgslinesymbol.h"
39#include "qgsapplication.h"
40#include "qgsfontmanager.h"
41#include "qgis.h"
42#include "qgsrasterlayer.h"
43#include "qgsproviderregistry.h"
44#include "qgsrasterpipe.h"
45
46#include <QBuffer>
47#include <QRegularExpression>
48
50{
51}
52
54{
55 mError.clear();
56 mWarnings.clear();
57
58 if ( style.contains( QStringLiteral( "sources" ) ) )
59 {
60 parseSources( style.value( QStringLiteral( "sources" ) ).toMap(), context );
61 }
62
63 if ( style.contains( QStringLiteral( "layers" ) ) )
64 {
65 parseLayers( style.value( QStringLiteral( "layers" ) ).toList(), context );
66 }
67 else
68 {
69 mError = QObject::tr( "Could not find layers list in JSON" );
70 return NoLayerList;
71 }
72 return Success;
73}
74
76{
77 return convert( QgsJsonUtils::parseJson( style ).toMap(), context );
78}
79
81{
82 qDeleteAll( mSources );
83}
84
86{
87 std::unique_ptr< QgsMapBoxGlStyleConversionContext > tmpContext;
88 if ( !context )
89 {
90 tmpContext = std::make_unique< QgsMapBoxGlStyleConversionContext >();
91 context = tmpContext.get();
92 }
93
94 QList<QgsVectorTileBasicRendererStyle> rendererStyles;
95 QList<QgsVectorTileBasicLabelingStyle> labelingStyles;
96
97 QgsVectorTileBasicRendererStyle rendererBackgroundStyle;
98 bool hasRendererBackgroundStyle = false;
99
100 for ( const QVariant &layer : layers )
101 {
102 const QVariantMap jsonLayer = layer.toMap();
103
104 const QString layerType = jsonLayer.value( QStringLiteral( "type" ) ).toString();
105 if ( layerType == QLatin1String( "background" ) )
106 {
107 hasRendererBackgroundStyle = parseFillLayer( jsonLayer, rendererBackgroundStyle, *context, true );
108 if ( hasRendererBackgroundStyle )
109 {
110 rendererBackgroundStyle.setStyleName( layerType );
111 rendererBackgroundStyle.setLayerName( layerType );
112 rendererBackgroundStyle.setFilterExpression( QString() );
113 rendererBackgroundStyle.setEnabled( true );
114 }
115 continue;
116 }
117
118 const QString styleId = jsonLayer.value( QStringLiteral( "id" ) ).toString();
119 context->setLayerId( styleId );
120
121 if ( layerType.compare( QLatin1String( "raster" ), Qt::CaseInsensitive ) == 0 )
122 {
123 QgsMapBoxGlStyleRasterSubLayer raster( styleId, jsonLayer.value( QStringLiteral( "source" ) ).toString() );
124 const QVariantMap jsonPaint = jsonLayer.value( QStringLiteral( "paint" ) ).toMap();
125 if ( jsonPaint.contains( QStringLiteral( "raster-opacity" ) ) )
126 {
127 const QVariant jsonRasterOpacity = jsonPaint.value( QStringLiteral( "raster-opacity" ) );
128 double defaultOpacity = 1;
129 raster.dataDefinedProperties().setProperty( QgsRasterPipe::Property::RendererOpacity, parseInterpolateByZoom( jsonRasterOpacity.toMap(), *context, 100, &defaultOpacity ) );
130 }
131
132 mRasterSubLayers.append( raster );
133 continue;
134 }
135
136 const QString layerName = jsonLayer.value( QStringLiteral( "source-layer" ) ).toString();
137
138 const int minZoom = jsonLayer.value( QStringLiteral( "minzoom" ), QStringLiteral( "-1" ) ).toInt();
139 const int maxZoom = jsonLayer.value( QStringLiteral( "maxzoom" ), QStringLiteral( "-1" ) ).toInt();
140
141 const bool enabled = jsonLayer.value( QStringLiteral( "visibility" ) ).toString() != QLatin1String( "none" );
142
143 QString filterExpression;
144 if ( jsonLayer.contains( QStringLiteral( "filter" ) ) )
145 {
146 filterExpression = parseExpression( jsonLayer.value( QStringLiteral( "filter" ) ).toList(), *context );
147 }
148
151
152 bool hasRendererStyle = false;
153 bool hasLabelingStyle = false;
154 if ( layerType == QLatin1String( "fill" ) )
155 {
156 hasRendererStyle = parseFillLayer( jsonLayer, rendererStyle, *context );
157 }
158 else if ( layerType == QLatin1String( "line" ) )
159 {
160 hasRendererStyle = parseLineLayer( jsonLayer, rendererStyle, *context );
161 }
162 else if ( layerType == QLatin1String( "circle" ) )
163 {
164 hasRendererStyle = parseCircleLayer( jsonLayer, rendererStyle, *context );
165 }
166 else if ( layerType == QLatin1String( "symbol" ) )
167 {
168 parseSymbolLayer( jsonLayer, rendererStyle, hasRendererStyle, labelingStyle, hasLabelingStyle, *context );
169 }
170 else
171 {
172 mWarnings << QObject::tr( "%1: Skipping unknown layer type %2" ).arg( context->layerId(), layerType );
173 QgsDebugMsg( mWarnings.constLast() );
174 continue;
175 }
176
177 if ( hasRendererStyle )
178 {
179 rendererStyle.setStyleName( styleId );
180 rendererStyle.setLayerName( layerName );
181 rendererStyle.setFilterExpression( filterExpression );
182 rendererStyle.setMinZoomLevel( minZoom );
183 rendererStyle.setMaxZoomLevel( maxZoom );
184 rendererStyle.setEnabled( enabled );
185 rendererStyles.append( rendererStyle );
186 }
187
188 if ( hasLabelingStyle )
189 {
190 labelingStyle.setStyleName( styleId );
191 labelingStyle.setLayerName( layerName );
192 labelingStyle.setFilterExpression( filterExpression );
193 labelingStyle.setMinZoomLevel( minZoom );
194 labelingStyle.setMaxZoomLevel( maxZoom );
195 labelingStyle.setEnabled( enabled );
196 labelingStyles.append( labelingStyle );
197 }
198
199 mWarnings.append( context->warnings() );
200 context->clearWarnings();
201 }
202
203 if ( hasRendererBackgroundStyle )
204 rendererStyles.prepend( rendererBackgroundStyle );
205
206 mRenderer = std::make_unique< QgsVectorTileBasicRenderer >();
207 QgsVectorTileBasicRenderer *renderer = dynamic_cast< QgsVectorTileBasicRenderer *>( mRenderer.get() );
208 renderer->setStyles( rendererStyles );
209
210 mLabeling = std::make_unique< QgsVectorTileBasicLabeling >();
211 QgsVectorTileBasicLabeling *labeling = dynamic_cast< QgsVectorTileBasicLabeling * >( mLabeling.get() );
212 labeling->setStyles( labelingStyles );
213}
214
215bool QgsMapBoxGlStyleConverter::parseFillLayer( const QVariantMap &jsonLayer, QgsVectorTileBasicRendererStyle &style, QgsMapBoxGlStyleConversionContext &context, bool isBackgroundStyle )
216{
217 const QVariantMap jsonPaint = jsonLayer.value( QStringLiteral( "paint" ) ).toMap();
218
219 QgsPropertyCollection ddProperties;
220 QgsPropertyCollection ddRasterProperties;
221
222 std::unique_ptr< QgsSymbol > symbol( std::make_unique< QgsFillSymbol >() );
223
224 // fill color
225 QColor fillColor;
226 if ( jsonPaint.contains( isBackgroundStyle ? QStringLiteral( "background-color" ) : QStringLiteral( "fill-color" ) ) )
227 {
228 const QVariant jsonFillColor = jsonPaint.value( isBackgroundStyle ? QStringLiteral( "background-color" ) : QStringLiteral( "fill-color" ) );
229 switch ( jsonFillColor.type() )
230 {
231 case QVariant::Map:
232 ddProperties.setProperty( QgsSymbolLayer::PropertyFillColor, parseInterpolateColorByZoom( jsonFillColor.toMap(), context, &fillColor ) );
233 break;
234
235 case QVariant::List:
236 case QVariant::StringList:
237 ddProperties.setProperty( QgsSymbolLayer::PropertyFillColor, parseValueList( jsonFillColor.toList(), PropertyType::Color, context, 1, 255, &fillColor ) );
238 break;
239
240 case QVariant::String:
241 fillColor = parseColor( jsonFillColor.toString(), context );
242 break;
243
244 default:
245 {
246 context.pushWarning( QObject::tr( "%1: Skipping unsupported fill-color type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonFillColor.type() ) ) );
247 break;
248 }
249 }
250 }
251 else
252 {
253 // defaults to #000000
254 fillColor = QColor( 0, 0, 0 );
255 }
256
257 QColor fillOutlineColor;
258 if ( !isBackgroundStyle )
259 {
260 if ( !jsonPaint.contains( QStringLiteral( "fill-outline-color" ) ) )
261 {
262 if ( fillColor.isValid() )
263 fillOutlineColor = fillColor;
264
265 // match fill color data defined property when active
266 if ( ddProperties.isActive( QgsSymbolLayer::PropertyFillColor ) )
268 }
269 else
270 {
271 const QVariant jsonFillOutlineColor = jsonPaint.value( QStringLiteral( "fill-outline-color" ) );
272 switch ( jsonFillOutlineColor.type() )
273 {
274 case QVariant::Map:
275 ddProperties.setProperty( QgsSymbolLayer::PropertyStrokeColor, parseInterpolateColorByZoom( jsonFillOutlineColor.toMap(), context, &fillOutlineColor ) );
276 break;
277
278 case QVariant::List:
279 case QVariant::StringList:
280 ddProperties.setProperty( QgsSymbolLayer::PropertyStrokeColor, parseValueList( jsonFillOutlineColor.toList(), PropertyType::Color, context, 1, 255, &fillOutlineColor ) );
281 break;
282
283 case QVariant::String:
284 fillOutlineColor = parseColor( jsonFillOutlineColor.toString(), context );
285 break;
286
287 default:
288 context.pushWarning( QObject::tr( "%1: Skipping unsupported fill-outline-color type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonFillOutlineColor.type() ) ) );
289 break;
290 }
291 }
292 }
293
294 double fillOpacity = -1.0;
295 double rasterOpacity = -1.0;
296 if ( jsonPaint.contains( isBackgroundStyle ? QStringLiteral( "background-opacity" ) : QStringLiteral( "fill-opacity" ) ) )
297 {
298 const QVariant jsonFillOpacity = jsonPaint.value( isBackgroundStyle ? QStringLiteral( "background-opacity" ) : QStringLiteral( "fill-opacity" ) );
299 switch ( jsonFillOpacity.type() )
300 {
301 case QVariant::Int:
302 case QVariant::LongLong:
303 case QVariant::Double:
304 fillOpacity = jsonFillOpacity.toDouble();
305 rasterOpacity = fillOpacity;
306 break;
307
308 case QVariant::Map:
309 if ( ddProperties.isActive( QgsSymbolLayer::PropertyFillColor ) )
310 {
311 symbol->setDataDefinedProperty( QgsSymbol::PropertyOpacity, parseInterpolateByZoom( jsonFillOpacity.toMap(), context, 100 ) );
312 }
313 else
314 {
315 ddProperties.setProperty( QgsSymbolLayer::PropertyFillColor, parseInterpolateOpacityByZoom( jsonFillOpacity.toMap(), fillColor.isValid() ? fillColor.alpha() : 255, &context ) );
316 ddProperties.setProperty( QgsSymbolLayer::PropertyStrokeColor, parseInterpolateOpacityByZoom( jsonFillOpacity.toMap(), fillOutlineColor.isValid() ? fillOutlineColor.alpha() : 255, &context ) );
317 ddRasterProperties.setProperty( QgsSymbolLayer::PropertyOpacity, parseInterpolateByZoom( jsonFillOpacity.toMap(), context, 100, &rasterOpacity ) );
318 }
319 break;
320
321 case QVariant::List:
322 case QVariant::StringList:
323 if ( ddProperties.isActive( QgsSymbolLayer::PropertyFillColor ) )
324 {
325 symbol->setDataDefinedProperty( QgsSymbol::PropertyOpacity, parseValueList( jsonFillOpacity.toList(), PropertyType::Numeric, context, 100, 100 ) );
326 }
327 else
328 {
329 ddProperties.setProperty( QgsSymbolLayer::PropertyFillColor, parseValueList( jsonFillOpacity.toList(), PropertyType::Opacity, context, 1, fillColor.isValid() ? fillColor.alpha() : 255 ) );
330 ddProperties.setProperty( QgsSymbolLayer::PropertyStrokeColor, parseValueList( jsonFillOpacity.toList(), PropertyType::Opacity, context, 1, fillOutlineColor.isValid() ? fillOutlineColor.alpha() : 255 ) );
331 ddRasterProperties.setProperty( QgsSymbolLayer::PropertyOpacity, parseValueList( jsonFillOpacity.toList(), PropertyType::Numeric, context, 100, 255, nullptr, &rasterOpacity ) );
332 }
333 break;
334
335 default:
336 context.pushWarning( QObject::tr( "%1: Skipping unsupported fill-opacity type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonFillOpacity.type() ) ) );
337 break;
338 }
339 }
340
341 // fill-translate
342 QPointF fillTranslate;
343 if ( jsonPaint.contains( QStringLiteral( "fill-translate" ) ) )
344 {
345 const QVariant jsonFillTranslate = jsonPaint.value( QStringLiteral( "fill-translate" ) );
346 switch ( jsonFillTranslate.type() )
347 {
348
349 case QVariant::Map:
350 ddProperties.setProperty( QgsSymbolLayer::PropertyOffset, parseInterpolatePointByZoom( jsonFillTranslate.toMap(), context, context.pixelSizeConversionFactor(), &fillTranslate ) );
351 break;
352
353 case QVariant::List:
354 case QVariant::StringList:
355 fillTranslate = QPointF( jsonFillTranslate.toList().value( 0 ).toDouble() * context.pixelSizeConversionFactor(),
356 jsonFillTranslate.toList().value( 1 ).toDouble() * context.pixelSizeConversionFactor() );
357 break;
358
359 default:
360 context.pushWarning( QObject::tr( "%1: Skipping unsupported fill-translate type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonFillTranslate.type() ) ) );
361 break;
362 }
363 }
364
365 QgsSimpleFillSymbolLayer *fillSymbol = dynamic_cast< QgsSimpleFillSymbolLayer * >( symbol->symbolLayer( 0 ) );
366 Q_ASSERT( fillSymbol ); // should not fail since QgsFillSymbol() constructor instantiates a QgsSimpleFillSymbolLayer
367
368 // set render units
369 symbol->setOutputUnit( context.targetUnit() );
370 fillSymbol->setOutputUnit( context.targetUnit() );
371
372 if ( !fillTranslate.isNull() )
373 {
374 fillSymbol->setOffset( fillTranslate );
375 }
376 fillSymbol->setOffsetUnit( context.targetUnit() );
377
378 if ( jsonPaint.contains( isBackgroundStyle ? QStringLiteral( "background-pattern" ) : QStringLiteral( "fill-pattern" ) ) )
379 {
380 // get fill-pattern to set sprite
381
382 const QVariant fillPatternJson = jsonPaint.value( isBackgroundStyle ? QStringLiteral( "background-pattern" ) : QStringLiteral( "fill-pattern" ) );
383
384 // fill-pattern disabled dillcolor
385 fillColor = QColor();
386 fillOutlineColor = QColor();
387
388 // fill-pattern can be String or Object
389 // String: {"fill-pattern": "dash-t"}
390 // Object: {"fill-pattern":{"stops":[[11,"wetland8"],[12,"wetland16"]]}}
391
392 QSize spriteSize;
393 QString spriteProperty, spriteSizeProperty;
394 const QString sprite = retrieveSpriteAsBase64( fillPatternJson, context, spriteSize, spriteProperty, spriteSizeProperty );
395 if ( !sprite.isEmpty() )
396 {
397 // when fill-pattern exists, set and insert QgsRasterFillSymbolLayer
399 rasterFill->setImageFilePath( sprite );
400 rasterFill->setWidth( spriteSize.width() );
401 rasterFill->setWidthUnit( context.targetUnit() );
403
404 if ( rasterOpacity >= 0 )
405 {
406 rasterFill->setOpacity( rasterOpacity );
407 }
408
409 if ( !spriteProperty.isEmpty() )
410 {
411 ddRasterProperties.setProperty( QgsSymbolLayer::PropertyFile, QgsProperty::fromExpression( spriteProperty ) );
412 ddRasterProperties.setProperty( QgsSymbolLayer::PropertyWidth, QgsProperty::fromExpression( spriteSizeProperty ) );
413 }
414
415 rasterFill->setDataDefinedProperties( ddRasterProperties );
416 symbol->appendSymbolLayer( rasterFill );
417 }
418 }
419
420 fillSymbol->setDataDefinedProperties( ddProperties );
421
422 if ( fillOpacity != -1 )
423 {
424 symbol->setOpacity( fillOpacity );
425 }
426
427 if ( fillOutlineColor.isValid() )
428 {
429 fillSymbol->setStrokeColor( fillOutlineColor );
430 }
431 else
432 {
433 fillSymbol->setStrokeStyle( Qt::NoPen );
434 }
435
436 if ( fillColor.isValid() )
437 {
438 fillSymbol->setFillColor( fillColor );
439 }
440 else
441 {
442 fillSymbol->setBrushStyle( Qt::NoBrush );
443 }
444
445 style.setGeometryType( Qgis::GeometryType::Polygon );
446 style.setSymbol( symbol.release() );
447 return true;
448}
449
451{
452 if ( !jsonLayer.contains( QStringLiteral( "paint" ) ) )
453 {
454 context.pushWarning( QObject::tr( "%1: Style has no paint property, skipping" ).arg( context.layerId() ) );
455 return false;
456 }
457
458 QgsPropertyCollection ddProperties;
459 QString rasterLineSprite;
460
461 const QVariantMap jsonPaint = jsonLayer.value( QStringLiteral( "paint" ) ).toMap();
462 if ( jsonPaint.contains( QStringLiteral( "line-pattern" ) ) )
463 {
464 const QVariant jsonLinePattern = jsonPaint.value( QStringLiteral( "line-pattern" ) );
465 switch ( jsonLinePattern.type() )
466 {
467 case QVariant::Map:
468 case QVariant::String:
469 {
470 QSize spriteSize;
471 QString spriteProperty, spriteSizeProperty;
472 rasterLineSprite = retrieveSpriteAsBase64( jsonLinePattern, context, spriteSize, spriteProperty, spriteSizeProperty );
474 break;
475 }
476
477 case QVariant::List:
478 case QVariant::StringList:
479 default:
480 break;
481 }
482
483 if ( rasterLineSprite.isEmpty() )
484 {
485 // unsupported line-pattern definition, moving on
486 context.pushWarning( QObject::tr( "%1: Skipping unsupported line-pattern property" ).arg( context.layerId() ) );
487 return false;
488 }
489 }
490
491 // line color
492 QColor lineColor;
493 if ( jsonPaint.contains( QStringLiteral( "line-color" ) ) )
494 {
495 const QVariant jsonLineColor = jsonPaint.value( QStringLiteral( "line-color" ) );
496 switch ( jsonLineColor.type() )
497 {
498 case QVariant::Map:
499 ddProperties.setProperty( QgsSymbolLayer::PropertyFillColor, parseInterpolateColorByZoom( jsonLineColor.toMap(), context, &lineColor ) );
501 break;
502
503 case QVariant::List:
504 case QVariant::StringList:
505 ddProperties.setProperty( QgsSymbolLayer::PropertyFillColor, parseValueList( jsonLineColor.toList(), PropertyType::Color, context, 1, 255, &lineColor ) );
507 break;
508
509 case QVariant::String:
510 lineColor = parseColor( jsonLineColor.toString(), context );
511 break;
512
513 default:
514 context.pushWarning( QObject::tr( "%1: Skipping unsupported line-color type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonLineColor.type() ) ) );
515 break;
516 }
517 }
518 else
519 {
520 // defaults to #000000
521 lineColor = QColor( 0, 0, 0 );
522 }
523
524
525 double lineWidth = 1.0;
526 QgsProperty lineWidthProperty;
527 if ( jsonPaint.contains( QStringLiteral( "line-width" ) ) )
528 {
529 const QVariant jsonLineWidth = jsonPaint.value( QStringLiteral( "line-width" ) );
530 switch ( jsonLineWidth.type() )
531 {
532 case QVariant::Int:
533 case QVariant::LongLong:
534 case QVariant::Double:
535 lineWidth = jsonLineWidth.toDouble() * context.pixelSizeConversionFactor();
536 break;
537
538 case QVariant::Map:
539 lineWidth = -1;
540 lineWidthProperty = parseInterpolateByZoom( jsonLineWidth.toMap(), context, context.pixelSizeConversionFactor(), &lineWidth );
541 ddProperties.setProperty( QgsSymbolLayer::PropertyStrokeWidth, lineWidthProperty );
542 break;
543
544 case QVariant::List:
545 case QVariant::StringList:
546 lineWidthProperty = parseValueList( jsonLineWidth.toList(), PropertyType::Numeric, context, context.pixelSizeConversionFactor(), 255, nullptr, &lineWidth );
547 ddProperties.setProperty( QgsSymbolLayer::PropertyStrokeWidth, lineWidthProperty );
548 break;
549
550 default:
551 context.pushWarning( QObject::tr( "%1: Skipping unsupported fill-width type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonLineWidth.type() ) ) );
552 break;
553 }
554 }
555
556 double lineOffset = 0.0;
557 if ( jsonPaint.contains( QStringLiteral( "line-offset" ) ) )
558 {
559 const QVariant jsonLineOffset = jsonPaint.value( QStringLiteral( "line-offset" ) );
560 switch ( jsonLineOffset.type() )
561 {
562 case QVariant::Int:
563 case QVariant::LongLong:
564 case QVariant::Double:
565 lineOffset = -jsonLineOffset.toDouble() * context.pixelSizeConversionFactor();
566 break;
567
568 case QVariant::Map:
569 lineWidth = -1;
570 ddProperties.setProperty( QgsSymbolLayer::PropertyOffset, parseInterpolateByZoom( jsonLineOffset.toMap(), context, context.pixelSizeConversionFactor() * -1, &lineOffset ) );
571 break;
572
573 case QVariant::List:
574 case QVariant::StringList:
575 ddProperties.setProperty( QgsSymbolLayer::PropertyOffset, parseValueList( jsonLineOffset.toList(), PropertyType::Numeric, context, context.pixelSizeConversionFactor() * -1, 255, nullptr, &lineOffset ) );
576 break;
577
578 default:
579 context.pushWarning( QObject::tr( "%1: Skipping unsupported line-offset type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonLineOffset.type() ) ) );
580 break;
581 }
582 }
583
584 double lineOpacity = -1.0;
585 if ( jsonPaint.contains( QStringLiteral( "line-opacity" ) ) )
586 {
587 const QVariant jsonLineOpacity = jsonPaint.value( QStringLiteral( "line-opacity" ) );
588 switch ( jsonLineOpacity.type() )
589 {
590 case QVariant::Int:
591 case QVariant::LongLong:
592 case QVariant::Double:
593 lineOpacity = jsonLineOpacity.toDouble();
594 break;
595
596 case QVariant::Map:
597 if ( ddProperties.isActive( QgsSymbolLayer::PropertyStrokeColor ) )
598 {
599 context.pushWarning( QObject::tr( "%1: Could not set opacity of layer, opacity already defined in stroke color" ).arg( context.layerId() ) );
600 }
601 else
602 {
603 ddProperties.setProperty( QgsSymbolLayer::PropertyStrokeColor, parseInterpolateOpacityByZoom( jsonLineOpacity.toMap(), lineColor.isValid() ? lineColor.alpha() : 255, &context ) );
604 }
605 break;
606
607 case QVariant::List:
608 case QVariant::StringList:
609 if ( ddProperties.isActive( QgsSymbolLayer::PropertyStrokeColor ) )
610 {
611 context.pushWarning( QObject::tr( "%1: Could not set opacity of layer, opacity already defined in stroke color" ).arg( context.layerId() ) );
612 }
613 else
614 {
615 ddProperties.setProperty( QgsSymbolLayer::PropertyStrokeColor, parseValueList( jsonLineOpacity.toList(), PropertyType::Opacity, context, 1, lineColor.isValid() ? lineColor.alpha() : 255 ) );
616 }
617 break;
618
619 default:
620 context.pushWarning( QObject::tr( "%1: Skipping unsupported line-opacity type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonLineOpacity.type() ) ) );
621 break;
622 }
623 }
624
625 QVector< double > dashVector;
626 if ( jsonPaint.contains( QStringLiteral( "line-dasharray" ) ) )
627 {
628 const QVariant jsonLineDashArray = jsonPaint.value( QStringLiteral( "line-dasharray" ) );
629 switch ( jsonLineDashArray.type() )
630 {
631 case QVariant::Map:
632 {
633 QString arrayExpression;
634 if ( !lineWidthProperty.asExpression().isEmpty() )
635 {
636 arrayExpression = QStringLiteral( "array_to_string(array_foreach(%1,@element * (%2)), ';')" ) // skip-keyword-check
637 .arg( parseArrayStops( jsonLineDashArray.toMap().value( QStringLiteral( "stops" ) ).toList(), context, 1 ),
638 lineWidthProperty.asExpression() );
639 }
640 else
641 {
642 arrayExpression = QStringLiteral( "array_to_string(%1, ';')" ).arg( parseArrayStops( jsonLineDashArray.toMap().value( QStringLiteral( "stops" ) ).toList(), context, lineWidth ) );
643 }
645
646 const QVariantList dashSource = jsonLineDashArray.toMap().value( QStringLiteral( "stops" ) ).toList().first().toList().value( 1 ).toList();
647 for ( const QVariant &v : dashSource )
648 {
649 dashVector << v.toDouble() * lineWidth;
650 }
651 break;
652 }
653
654 case QVariant::List:
655 case QVariant::StringList:
656 {
657 const QVariantList dashSource = jsonLineDashArray.toList();
658
659 QVector< double > rawDashVectorSizes;
660 rawDashVectorSizes.reserve( dashSource.size() );
661 for ( const QVariant &v : dashSource )
662 {
663 rawDashVectorSizes << v.toDouble();
664 }
665
666 // handle non-compliant dash vector patterns
667 if ( rawDashVectorSizes.size() == 1 )
668 {
669 // match behavior of MapBox style rendering -- if a user makes a line dash array with one element, it's ignored
670 rawDashVectorSizes.clear();
671 }
672 else if ( rawDashVectorSizes.size() % 2 == 1 )
673 {
674 // odd number of dash pattern sizes -- this isn't permitted by Qt/QGIS, but isn't explicitly blocked by the MapBox specs
675 // MapBox seems to add the extra dash element to the first dash size
676 rawDashVectorSizes[0] = rawDashVectorSizes[0] + rawDashVectorSizes[rawDashVectorSizes.size() - 1];
677 rawDashVectorSizes.resize( rawDashVectorSizes.size() - 1 );
678 }
679
680 if ( !rawDashVectorSizes.isEmpty() && ( !lineWidthProperty.asExpression().isEmpty() ) )
681 {
682 QStringList dashArrayStringParts;
683 dashArrayStringParts.reserve( rawDashVectorSizes.size() );
684 for ( double v : std::as_const( rawDashVectorSizes ) )
685 {
686 dashArrayStringParts << qgsDoubleToString( v );
687 }
688
689 QString arrayExpression = QStringLiteral( "array_to_string(array_foreach(array(%1),@element * (%2)), ';')" ) // skip-keyword-check
690 .arg( dashArrayStringParts.join( ',' ),
691 lineWidthProperty.asExpression() );
693 }
694
695 // dash vector sizes for QGIS symbols must be multiplied by the target line width
696 for ( double v : std::as_const( rawDashVectorSizes ) )
697 {
698 dashVector << v *lineWidth;
699 }
700
701 break;
702 }
703
704 default:
705 context.pushWarning( QObject::tr( "%1: Skipping unsupported line-dasharray type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonLineDashArray.type() ) ) );
706 break;
707 }
708 }
709
710 Qt::PenCapStyle penCapStyle = Qt::FlatCap;
711 Qt::PenJoinStyle penJoinStyle = Qt::MiterJoin;
712 if ( jsonLayer.contains( QStringLiteral( "layout" ) ) )
713 {
714 const QVariantMap jsonLayout = jsonLayer.value( QStringLiteral( "layout" ) ).toMap();
715 if ( jsonLayout.contains( QStringLiteral( "line-cap" ) ) )
716 {
717 penCapStyle = parseCapStyle( jsonLayout.value( QStringLiteral( "line-cap" ) ).toString() );
718 }
719 if ( jsonLayout.contains( QStringLiteral( "line-join" ) ) )
720 {
721 penJoinStyle = parseJoinStyle( jsonLayout.value( QStringLiteral( "line-join" ) ).toString() );
722 }
723 }
724
725 std::unique_ptr< QgsSymbol > symbol( std::make_unique< QgsLineSymbol >() );
726 symbol->setOutputUnit( context.targetUnit() );
727
728 if ( !rasterLineSprite.isEmpty() )
729 {
730 QgsRasterLineSymbolLayer *lineSymbol = new QgsRasterLineSymbolLayer( rasterLineSprite );
731 lineSymbol->setOutputUnit( context.targetUnit() );
732 lineSymbol->setPenCapStyle( penCapStyle );
733 lineSymbol->setPenJoinStyle( penJoinStyle );
734 lineSymbol->setDataDefinedProperties( ddProperties );
735 lineSymbol->setOffset( lineOffset );
736 lineSymbol->setOffsetUnit( context.targetUnit() );
737
738 if ( lineOpacity != -1 )
739 {
740 symbol->setOpacity( lineOpacity );
741 }
742 if ( lineWidth != -1 )
743 {
744 lineSymbol->setWidth( lineWidth );
745 }
746 symbol->changeSymbolLayer( 0, lineSymbol );
747 }
748 else
749 {
750 QgsSimpleLineSymbolLayer *lineSymbol = dynamic_cast< QgsSimpleLineSymbolLayer * >( symbol->symbolLayer( 0 ) );
751 Q_ASSERT( lineSymbol ); // should not fail since QgsLineSymbol() constructor instantiates a QgsSimpleLineSymbolLayer
752
753 // set render units
754 lineSymbol->setOutputUnit( context.targetUnit() );
755 lineSymbol->setPenCapStyle( penCapStyle );
756 lineSymbol->setPenJoinStyle( penJoinStyle );
757 lineSymbol->setDataDefinedProperties( ddProperties );
758 lineSymbol->setOffset( lineOffset );
759 lineSymbol->setOffsetUnit( context.targetUnit() );
760
761 if ( lineOpacity != -1 )
762 {
763 symbol->setOpacity( lineOpacity );
764 }
765 if ( lineColor.isValid() )
766 {
767 lineSymbol->setColor( lineColor );
768 }
769 if ( lineWidth != -1 )
770 {
771 lineSymbol->setWidth( lineWidth );
772 }
773 if ( !dashVector.empty() )
774 {
775 lineSymbol->setUseCustomDashPattern( true );
776 lineSymbol->setCustomDashVector( dashVector );
777 }
778 }
779
780 style.setGeometryType( Qgis::GeometryType::Line );
781 style.setSymbol( symbol.release() );
782 return true;
783}
784
786{
787 if ( !jsonLayer.contains( QStringLiteral( "paint" ) ) )
788 {
789 context.pushWarning( QObject::tr( "%1: Style has no paint property, skipping" ).arg( context.layerId() ) );
790 return false;
791 }
792
793 const QVariantMap jsonPaint = jsonLayer.value( QStringLiteral( "paint" ) ).toMap();
794 QgsPropertyCollection ddProperties;
795
796 // circle color
797 QColor circleFillColor;
798 if ( jsonPaint.contains( QStringLiteral( "circle-color" ) ) )
799 {
800 const QVariant jsonCircleColor = jsonPaint.value( QStringLiteral( "circle-color" ) );
801 switch ( jsonCircleColor.type() )
802 {
803 case QVariant::Map:
804 ddProperties.setProperty( QgsSymbolLayer::PropertyFillColor, parseInterpolateColorByZoom( jsonCircleColor.toMap(), context, &circleFillColor ) );
805 break;
806
807 case QVariant::List:
808 case QVariant::StringList:
809 ddProperties.setProperty( QgsSymbolLayer::PropertyFillColor, parseValueList( jsonCircleColor.toList(), PropertyType::Color, context, 1, 255, &circleFillColor ) );
810 break;
811
812 case QVariant::String:
813 circleFillColor = parseColor( jsonCircleColor.toString(), context );
814 break;
815
816 default:
817 context.pushWarning( QObject::tr( "%1: Skipping unsupported circle-color type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonCircleColor.type() ) ) );
818 break;
819 }
820 }
821 else
822 {
823 // defaults to #000000
824 circleFillColor = QColor( 0, 0, 0 );
825 }
826
827 // circle radius
828 double circleDiameter = 10.0;
829 if ( jsonPaint.contains( QStringLiteral( "circle-radius" ) ) )
830 {
831 const QVariant jsonCircleRadius = jsonPaint.value( QStringLiteral( "circle-radius" ) );
832 switch ( jsonCircleRadius.type() )
833 {
834 case QVariant::Int:
835 case QVariant::LongLong:
836 case QVariant::Double:
837 circleDiameter = jsonCircleRadius.toDouble() * context.pixelSizeConversionFactor() * 2;
838 break;
839
840 case QVariant::Map:
841 circleDiameter = -1;
842 ddProperties.setProperty( QgsSymbolLayer::PropertyWidth, parseInterpolateByZoom( jsonCircleRadius.toMap(), context, context.pixelSizeConversionFactor() * 2, &circleDiameter ) );
843 break;
844
845 case QVariant::List:
846 case QVariant::StringList:
847 ddProperties.setProperty( QgsSymbolLayer::PropertyWidth, parseValueList( jsonCircleRadius.toList(), PropertyType::Numeric, context, context.pixelSizeConversionFactor() * 2, 255, nullptr, &circleDiameter ) );
848 break;
849
850 default:
851 context.pushWarning( QObject::tr( "%1: Skipping unsupported circle-radius type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonCircleRadius.type() ) ) );
852 break;
853 }
854 }
855
856 double circleOpacity = -1.0;
857 if ( jsonPaint.contains( QStringLiteral( "circle-opacity" ) ) )
858 {
859 const QVariant jsonCircleOpacity = jsonPaint.value( QStringLiteral( "circle-opacity" ) );
860 switch ( jsonCircleOpacity.type() )
861 {
862 case QVariant::Int:
863 case QVariant::LongLong:
864 case QVariant::Double:
865 circleOpacity = jsonCircleOpacity.toDouble();
866 break;
867
868 case QVariant::Map:
869 ddProperties.setProperty( QgsSymbolLayer::PropertyFillColor, parseInterpolateOpacityByZoom( jsonCircleOpacity.toMap(), circleFillColor.isValid() ? circleFillColor.alpha() : 255, &context ) );
870 break;
871
872 case QVariant::List:
873 case QVariant::StringList:
874 ddProperties.setProperty( QgsSymbolLayer::PropertyFillColor, parseValueList( jsonCircleOpacity.toList(), PropertyType::Opacity, context, 1, circleFillColor.isValid() ? circleFillColor.alpha() : 255 ) );
875 break;
876
877 default:
878 context.pushWarning( QObject::tr( "%1: Skipping unsupported circle-opacity type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonCircleOpacity.type() ) ) );
879 break;
880 }
881 }
882 if ( ( circleOpacity != -1 ) && circleFillColor.isValid() )
883 {
884 circleFillColor.setAlphaF( circleOpacity );
885 }
886
887 // circle stroke color
888 QColor circleStrokeColor;
889 if ( jsonPaint.contains( QStringLiteral( "circle-stroke-color" ) ) )
890 {
891 const QVariant jsonCircleStrokeColor = jsonPaint.value( QStringLiteral( "circle-stroke-color" ) );
892 switch ( jsonCircleStrokeColor.type() )
893 {
894 case QVariant::Map:
895 ddProperties.setProperty( QgsSymbolLayer::PropertyStrokeColor, parseInterpolateColorByZoom( jsonCircleStrokeColor.toMap(), context, &circleStrokeColor ) );
896 break;
897
898 case QVariant::List:
899 case QVariant::StringList:
900 ddProperties.setProperty( QgsSymbolLayer::PropertyStrokeColor, parseValueList( jsonCircleStrokeColor.toList(), PropertyType::Color, context, 1, 255, &circleStrokeColor ) );
901 break;
902
903 case QVariant::String:
904 circleStrokeColor = parseColor( jsonCircleStrokeColor.toString(), context );
905 break;
906
907 default:
908 context.pushWarning( QObject::tr( "%1: Skipping unsupported circle-stroke-color type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonCircleStrokeColor.type() ) ) );
909 break;
910 }
911 }
912
913 // circle stroke width
914 double circleStrokeWidth = -1.0;
915 if ( jsonPaint.contains( QStringLiteral( "circle-stroke-width" ) ) )
916 {
917 const QVariant circleStrokeWidthJson = jsonPaint.value( QStringLiteral( "circle-stroke-width" ) );
918 switch ( circleStrokeWidthJson.type() )
919 {
920 case QVariant::Int:
921 case QVariant::LongLong:
922 case QVariant::Double:
923 circleStrokeWidth = circleStrokeWidthJson.toDouble() * context.pixelSizeConversionFactor();
924 break;
925
926 case QVariant::Map:
927 circleStrokeWidth = -1.0;
928 ddProperties.setProperty( QgsSymbolLayer::PropertyStrokeWidth, parseInterpolateByZoom( circleStrokeWidthJson.toMap(), context, context.pixelSizeConversionFactor(), &circleStrokeWidth ) );
929 break;
930
931 case QVariant::List:
932 case QVariant::StringList:
933 ddProperties.setProperty( QgsSymbolLayer::PropertyStrokeWidth, parseValueList( circleStrokeWidthJson.toList(), PropertyType::Numeric, context, context.pixelSizeConversionFactor(), 255, nullptr, &circleStrokeWidth ) );
934 break;
935
936 default:
937 context.pushWarning( QObject::tr( "%1: Skipping unsupported circle-stroke-width type (%2)" ).arg( context.layerId(), QMetaType::typeName( circleStrokeWidthJson.type() ) ) );
938 break;
939 }
940 }
941
942 double circleStrokeOpacity = -1.0;
943 if ( jsonPaint.contains( QStringLiteral( "circle-stroke-opacity" ) ) )
944 {
945 const QVariant jsonCircleStrokeOpacity = jsonPaint.value( QStringLiteral( "circle-stroke-opacity" ) );
946 switch ( jsonCircleStrokeOpacity.type() )
947 {
948 case QVariant::Int:
949 case QVariant::LongLong:
950 case QVariant::Double:
951 circleStrokeOpacity = jsonCircleStrokeOpacity.toDouble();
952 break;
953
954 case QVariant::Map:
955 ddProperties.setProperty( QgsSymbolLayer::PropertyStrokeColor, parseInterpolateOpacityByZoom( jsonCircleStrokeOpacity.toMap(), circleStrokeColor.isValid() ? circleStrokeColor.alpha() : 255, &context ) );
956 break;
957
958 case QVariant::List:
959 case QVariant::StringList:
960 ddProperties.setProperty( QgsSymbolLayer::PropertyStrokeColor, parseValueList( jsonCircleStrokeOpacity.toList(), PropertyType::Opacity, context, 1, circleStrokeColor.isValid() ? circleStrokeColor.alpha() : 255 ) );
961 break;
962
963 default:
964 context.pushWarning( QObject::tr( "%1: Skipping unsupported circle-stroke-opacity type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonCircleStrokeOpacity.type() ) ) );
965 break;
966 }
967 }
968 if ( ( circleStrokeOpacity != -1 ) && circleStrokeColor.isValid() )
969 {
970 circleStrokeColor.setAlphaF( circleStrokeOpacity );
971 }
972
973 // translate
974 QPointF circleTranslate;
975 if ( jsonPaint.contains( QStringLiteral( "circle-translate" ) ) )
976 {
977 const QVariant jsonCircleTranslate = jsonPaint.value( QStringLiteral( "circle-translate" ) );
978 switch ( jsonCircleTranslate.type() )
979 {
980
981 case QVariant::Map:
982 ddProperties.setProperty( QgsSymbolLayer::PropertyOffset, parseInterpolatePointByZoom( jsonCircleTranslate.toMap(), context, context.pixelSizeConversionFactor(), &circleTranslate ) );
983 break;
984
985 case QVariant::List:
986 case QVariant::StringList:
987 circleTranslate = QPointF( jsonCircleTranslate.toList().value( 0 ).toDouble() * context.pixelSizeConversionFactor(),
988 jsonCircleTranslate.toList().value( 1 ).toDouble() * context.pixelSizeConversionFactor() );
989 break;
990
991 default:
992 context.pushWarning( QObject::tr( "%1: Skipping unsupported circle-translate type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonCircleTranslate.type() ) ) );
993 break;
994 }
995 }
996
997 std::unique_ptr< QgsSymbol > symbol( std::make_unique< QgsMarkerSymbol >() );
998 QgsSimpleMarkerSymbolLayer *markerSymbolLayer = dynamic_cast< QgsSimpleMarkerSymbolLayer * >( symbol->symbolLayer( 0 ) );
999 Q_ASSERT( markerSymbolLayer );
1000
1001 // set render units
1002 symbol->setOutputUnit( context.targetUnit() );
1003 symbol->setDataDefinedProperties( ddProperties );
1004
1005 if ( !circleTranslate.isNull() )
1006 {
1007 markerSymbolLayer->setOffset( circleTranslate );
1008 markerSymbolLayer->setOffsetUnit( context.targetUnit() );
1009 }
1010
1011 if ( circleFillColor.isValid() )
1012 {
1013 markerSymbolLayer->setFillColor( circleFillColor );
1014 }
1015 if ( circleDiameter != -1 )
1016 {
1017 markerSymbolLayer->setSize( circleDiameter );
1018 markerSymbolLayer->setSizeUnit( context.targetUnit() );
1019 }
1020 if ( circleStrokeColor.isValid() )
1021 {
1022 markerSymbolLayer->setStrokeColor( circleStrokeColor );
1023 }
1024 if ( circleStrokeWidth != -1 )
1025 {
1026 markerSymbolLayer->setStrokeWidth( circleStrokeWidth );
1027 markerSymbolLayer->setStrokeWidthUnit( context.targetUnit() );
1028 }
1029
1030 style.setGeometryType( Qgis::GeometryType::Point );
1031 style.setSymbol( symbol.release() );
1032 return true;
1033}
1034
1035void QgsMapBoxGlStyleConverter::parseSymbolLayer( const QVariantMap &jsonLayer, QgsVectorTileBasicRendererStyle &renderer, bool &hasRenderer, QgsVectorTileBasicLabelingStyle &labelingStyle, bool &hasLabeling, QgsMapBoxGlStyleConversionContext &context )
1036{
1037 hasLabeling = false;
1038 hasRenderer = false;
1039
1040 if ( !jsonLayer.contains( QStringLiteral( "layout" ) ) )
1041 {
1042 context.pushWarning( QObject::tr( "%1: Style layer has no layout property, skipping" ).arg( context.layerId() ) );
1043 return;
1044 }
1045 const QVariantMap jsonLayout = jsonLayer.value( QStringLiteral( "layout" ) ).toMap();
1046 if ( !jsonLayout.contains( QStringLiteral( "text-field" ) ) )
1047 {
1048 hasRenderer = parseSymbolLayerAsRenderer( jsonLayer, renderer, context );
1049 return;
1050 }
1051
1052 const QVariantMap jsonPaint = jsonLayer.value( QStringLiteral( "paint" ) ).toMap();
1053
1054 QgsPropertyCollection ddLabelProperties;
1055
1056 double textSize = 16.0 * context.pixelSizeConversionFactor();
1057 QgsProperty textSizeProperty;
1058 if ( jsonLayout.contains( QStringLiteral( "text-size" ) ) )
1059 {
1060 const QVariant jsonTextSize = jsonLayout.value( QStringLiteral( "text-size" ) );
1061 switch ( jsonTextSize.type() )
1062 {
1063 case QVariant::Int:
1064 case QVariant::LongLong:
1065 case QVariant::Double:
1066 textSize = jsonTextSize.toDouble() * context.pixelSizeConversionFactor();
1067 break;
1068
1069 case QVariant::Map:
1070 textSize = -1;
1071 textSizeProperty = parseInterpolateByZoom( jsonTextSize.toMap(), context, context.pixelSizeConversionFactor(), &textSize );
1072
1073 break;
1074
1075 case QVariant::List:
1076 case QVariant::StringList:
1077 textSize = -1;
1078 textSizeProperty = parseValueList( jsonTextSize.toList(), PropertyType::Numeric, context, context.pixelSizeConversionFactor(), 255, nullptr, &textSize );
1079 break;
1080
1081 default:
1082 context.pushWarning( QObject::tr( "%1: Skipping unsupported text-size type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonTextSize.type() ) ) );
1083 break;
1084 }
1085
1086 if ( textSizeProperty )
1087 {
1088 ddLabelProperties.setProperty( QgsPalLayerSettings::Size, textSizeProperty );
1089 }
1090 }
1091
1092 // a rough average of ems to character count conversion for a variety of fonts
1093 constexpr double EM_TO_CHARS = 2.0;
1094
1095 double textMaxWidth = -1;
1096 if ( jsonLayout.contains( QStringLiteral( "text-max-width" ) ) )
1097 {
1098 const QVariant jsonTextMaxWidth = jsonLayout.value( QStringLiteral( "text-max-width" ) );
1099 switch ( jsonTextMaxWidth.type() )
1100 {
1101 case QVariant::Int:
1102 case QVariant::LongLong:
1103 case QVariant::Double:
1104 textMaxWidth = jsonTextMaxWidth.toDouble() * EM_TO_CHARS;
1105 break;
1106
1107 case QVariant::Map:
1108 ddLabelProperties.setProperty( QgsPalLayerSettings::AutoWrapLength, parseInterpolateByZoom( jsonTextMaxWidth.toMap(), context, EM_TO_CHARS, &textMaxWidth ) );
1109 break;
1110
1111 case QVariant::List:
1112 case QVariant::StringList:
1113 ddLabelProperties.setProperty( QgsPalLayerSettings::AutoWrapLength, parseValueList( jsonTextMaxWidth.toList(), PropertyType::Numeric, context, EM_TO_CHARS, 255, nullptr, &textMaxWidth ) );
1114 break;
1115
1116 default:
1117 context.pushWarning( QObject::tr( "%1: Skipping unsupported text-max-width type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonTextMaxWidth.type() ) ) );
1118 break;
1119 }
1120 }
1121 else
1122 {
1123 // defaults to 10
1124 textMaxWidth = 10 * EM_TO_CHARS;
1125 }
1126
1127 double textLetterSpacing = -1;
1128 if ( jsonLayout.contains( QStringLiteral( "text-letter-spacing" ) ) )
1129 {
1130 const QVariant jsonTextLetterSpacing = jsonLayout.value( QStringLiteral( "text-letter-spacing" ) );
1131 switch ( jsonTextLetterSpacing.type() )
1132 {
1133 case QVariant::Int:
1134 case QVariant::LongLong:
1135 case QVariant::Double:
1136 textLetterSpacing = jsonTextLetterSpacing.toDouble();
1137 break;
1138
1139 case QVariant::Map:
1140 ddLabelProperties.setProperty( QgsPalLayerSettings::FontLetterSpacing, parseInterpolateByZoom( jsonTextLetterSpacing.toMap(), context, 1, &textLetterSpacing ) );
1141 break;
1142
1143 case QVariant::List:
1144 case QVariant::StringList:
1145 ddLabelProperties.setProperty( QgsPalLayerSettings::FontLetterSpacing, parseValueList( jsonTextLetterSpacing.toList(), PropertyType::Numeric, context, 1, 255, nullptr, &textLetterSpacing ) );
1146 break;
1147
1148 default:
1149 context.pushWarning( QObject::tr( "%1: Skipping unsupported text-letter-spacing type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonTextLetterSpacing.type() ) ) );
1150 break;
1151 }
1152 }
1153
1154 QFont textFont;
1155 bool foundFont = false;
1156 QString fontName;
1157 QString fontStyleName;
1158
1159 if ( jsonLayout.contains( QStringLiteral( "text-font" ) ) )
1160 {
1161 auto splitFontFamily = []( const QString & fontName, QString & family, QString & style ) -> bool
1162 {
1163 QString matchedFamily;
1164 const QStringList textFontParts = fontName.split( ' ' );
1165 for ( int i = textFontParts.size() - 1; i >= 1; --i )
1166 {
1167 const QString candidateFontFamily = textFontParts.mid( 0, i ).join( ' ' );
1168 const QString candidateFontStyle = textFontParts.mid( i ).join( ' ' );
1169
1170 const QString processedFontFamily = QgsApplication::fontManager()->processFontFamilyName( candidateFontFamily );
1171 if ( QgsFontUtils::fontFamilyHasStyle( processedFontFamily, candidateFontStyle ) )
1172 {
1173 family = processedFontFamily;
1174 style = candidateFontStyle;
1175 return true;
1176 }
1177 else if ( QgsApplication::fontManager()->tryToDownloadFontFamily( processedFontFamily, matchedFamily ) )
1178 {
1179 if ( processedFontFamily == matchedFamily )
1180 {
1181 family = processedFontFamily;
1182 style = candidateFontStyle;
1183 }
1184 else
1185 {
1186 family = matchedFamily;
1187 style = processedFontFamily;
1188 style.replace( matchedFamily, QString() );
1189 style = style.trimmed();
1190 if ( !style.isEmpty() && !candidateFontStyle.isEmpty() )
1191 {
1192 style += QStringLiteral( " %1" ).arg( candidateFontStyle );
1193 }
1194 }
1195 return true;
1196 }
1197 }
1198
1199 const QString processedFontFamily = QgsApplication::fontManager()->processFontFamilyName( fontName );
1200 if ( QFontDatabase().hasFamily( processedFontFamily ) )
1201 {
1202 // the json isn't following the spec correctly!!
1203 family = processedFontFamily;
1204 style.clear();
1205 return true;
1206 }
1207 else if ( QgsApplication::fontManager()->tryToDownloadFontFamily( processedFontFamily, matchedFamily ) )
1208 {
1209 family = matchedFamily;
1210 style.clear();
1211 return true;
1212 }
1213 return false;
1214 };
1215
1216 const QVariant jsonTextFont = jsonLayout.value( QStringLiteral( "text-font" ) );
1217 if ( jsonTextFont.type() != QVariant::List && jsonTextFont.type() != QVariant::StringList && jsonTextFont.type() != QVariant::String
1218 && jsonTextFont.type() != QVariant::Map )
1219 {
1220 context.pushWarning( QObject::tr( "%1: Skipping unsupported text-font type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonTextFont.type() ) ) );
1221 }
1222 else
1223 {
1224 switch ( jsonTextFont.type() )
1225 {
1226 case QVariant::List:
1227 case QVariant::StringList:
1228 fontName = jsonTextFont.toList().value( 0 ).toString();
1229 break;
1230
1231 case QVariant::String:
1232 fontName = jsonTextFont.toString();
1233 break;
1234
1235 case QVariant::Map:
1236 {
1237 QString familyCaseString = QStringLiteral( "CASE " );
1238 QString styleCaseString = QStringLiteral( "CASE " );
1239 QString fontFamily;
1240 const QVariantList stops = jsonTextFont.toMap().value( QStringLiteral( "stops" ) ).toList();
1241
1242 bool error = false;
1243 for ( int i = 0; i < stops.length() - 1; ++i )
1244 {
1245 // bottom zoom and value
1246 const QVariant bz = stops.value( i ).toList().value( 0 );
1247 const QString bv = stops.value( i ).toList().value( 1 ).type() == QVariant::String ? stops.value( i ).toList().value( 1 ).toString() : stops.value( i ).toList().value( 1 ).toList().value( 0 ).toString();
1248 if ( bz.type() == QVariant::List || bz.type() == QVariant::StringList )
1249 {
1250 context.pushWarning( QObject::tr( "%1: Expressions in interpolation function are not supported, skipping." ).arg( context.layerId() ) );
1251 error = true;
1252 break;
1253 }
1254
1255 // top zoom
1256 const QVariant tz = stops.value( i + 1 ).toList().value( 0 );
1257 if ( tz.type() == QVariant::List || tz.type() == QVariant::StringList )
1258 {
1259 context.pushWarning( QObject::tr( "%1: Expressions in interpolation function are not supported, skipping." ).arg( context.layerId() ) );
1260 error = true;
1261 break;
1262 }
1263
1264 if ( splitFontFamily( bv, fontFamily, fontStyleName ) )
1265 {
1266 familyCaseString += QStringLiteral( "WHEN @vector_tile_zoom > %1 AND @vector_tile_zoom <= %2 "
1267 "THEN %3 " ).arg( bz.toString(),
1268 tz.toString(),
1269 QgsExpression::quotedValue( fontFamily ) );
1270 styleCaseString += QStringLiteral( "WHEN @vector_tile_zoom > %1 AND @vector_tile_zoom <= %2 "
1271 "THEN %3 " ).arg( bz.toString(),
1272 tz.toString(),
1273 QgsExpression::quotedValue( fontStyleName ) );
1274 }
1275 else
1276 {
1277 context.pushWarning( QObject::tr( "%1: Referenced font %2 is not available on system" ).arg( context.layerId(), bv ) );
1278 }
1279 }
1280 if ( error )
1281 break;
1282
1283 const QString bv = stops.constLast().toList().value( 1 ).type() == QVariant::String ? stops.constLast().toList().value( 1 ).toString() : stops.constLast().toList().value( 1 ).toList().value( 0 ).toString();
1284 if ( splitFontFamily( bv, fontFamily, fontStyleName ) )
1285 {
1286 familyCaseString += QStringLiteral( "ELSE %1 END" ).arg( QgsExpression::quotedValue( fontFamily ) );
1287 styleCaseString += QStringLiteral( "ELSE %1 END" ).arg( QgsExpression::quotedValue( fontStyleName ) );
1288 }
1289 else
1290 {
1291 context.pushWarning( QObject::tr( "%1: Referenced font %2 is not available on system" ).arg( context.layerId(), bv ) );
1292 }
1293
1294 ddLabelProperties.setProperty( QgsPalLayerSettings::Family, QgsProperty::fromExpression( familyCaseString ) );
1295 ddLabelProperties.setProperty( QgsPalLayerSettings::FontStyle, QgsProperty::fromExpression( styleCaseString ) );
1296
1297 foundFont = true;
1298 fontName = fontFamily;
1299
1300 break;
1301 }
1302
1303 default:
1304 break;
1305 }
1306
1307 QString fontFamily;
1308 if ( splitFontFamily( fontName, fontFamily, fontStyleName ) )
1309 {
1310 textFont = QFont( fontFamily );
1311 if ( !fontStyleName.isEmpty() )
1312 textFont.setStyleName( fontStyleName );
1313 foundFont = true;
1314 }
1315 }
1316 }
1317 else
1318 {
1319 // Defaults to ["Open Sans Regular","Arial Unicode MS Regular"].
1320 if ( QgsFontUtils::fontFamilyHasStyle( QStringLiteral( "Open Sans" ), QStringLiteral( "Regular" ) ) )
1321 {
1322 fontName = QStringLiteral( "Open Sans" );
1323 textFont = QFont( fontName );
1324 textFont.setStyleName( QStringLiteral( "Regular" ) );
1325 fontStyleName = QStringLiteral( "Regular" );
1326 foundFont = true;
1327 }
1328 else if ( QgsFontUtils::fontFamilyHasStyle( QStringLiteral( "Arial Unicode MS" ), QStringLiteral( "Regular" ) ) )
1329 {
1330 fontName = QStringLiteral( "Arial Unicode MS" );
1331 textFont = QFont( fontName );
1332 textFont.setStyleName( QStringLiteral( "Regular" ) );
1333 fontStyleName = QStringLiteral( "Regular" );
1334 foundFont = true;
1335 }
1336 else
1337 {
1338 fontName = QStringLiteral( "Open Sans, Arial Unicode MS" );
1339 }
1340 }
1341 if ( !foundFont && !fontName.isEmpty() )
1342 {
1343 context.pushWarning( QObject::tr( "%1: Referenced font %2 is not available on system" ).arg( context.layerId(), fontName ) );
1344 }
1345
1346 // text color
1347 QColor textColor;
1348 if ( jsonPaint.contains( QStringLiteral( "text-color" ) ) )
1349 {
1350 const QVariant jsonTextColor = jsonPaint.value( QStringLiteral( "text-color" ) );
1351 switch ( jsonTextColor.type() )
1352 {
1353 case QVariant::Map:
1354 ddLabelProperties.setProperty( QgsPalLayerSettings::Color, parseInterpolateColorByZoom( jsonTextColor.toMap(), context, &textColor ) );
1355 break;
1356
1357 case QVariant::List:
1358 case QVariant::StringList:
1359 ddLabelProperties.setProperty( QgsPalLayerSettings::Color, parseValueList( jsonTextColor.toList(), PropertyType::Color, context, 1, 255, &textColor ) );
1360 break;
1361
1362 case QVariant::String:
1363 textColor = parseColor( jsonTextColor.toString(), context );
1364 break;
1365
1366 default:
1367 context.pushWarning( QObject::tr( "%1: Skipping unsupported text-color type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonTextColor.type() ) ) );
1368 break;
1369 }
1370 }
1371 else
1372 {
1373 // defaults to #000000
1374 textColor = QColor( 0, 0, 0 );
1375 }
1376
1377 // buffer color
1378 QColor bufferColor;
1379 if ( jsonPaint.contains( QStringLiteral( "text-halo-color" ) ) )
1380 {
1381 const QVariant jsonBufferColor = jsonPaint.value( QStringLiteral( "text-halo-color" ) );
1382 switch ( jsonBufferColor.type() )
1383 {
1384 case QVariant::Map:
1385 ddLabelProperties.setProperty( QgsPalLayerSettings::BufferColor, parseInterpolateColorByZoom( jsonBufferColor.toMap(), context, &bufferColor ) );
1386 break;
1387
1388 case QVariant::List:
1389 case QVariant::StringList:
1390 ddLabelProperties.setProperty( QgsPalLayerSettings::BufferColor, parseValueList( jsonBufferColor.toList(), PropertyType::Color, context, 1, 255, &bufferColor ) );
1391 break;
1392
1393 case QVariant::String:
1394 bufferColor = parseColor( jsonBufferColor.toString(), context );
1395 break;
1396
1397 default:
1398 context.pushWarning( QObject::tr( "%1: Skipping unsupported text-halo-color type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonBufferColor.type() ) ) );
1399 break;
1400 }
1401 }
1402
1403 double bufferSize = 0.0;
1404 // the pixel based text buffers appear larger when rendered on the web - so automatically scale
1405 // them up when converting to a QGIS style
1406 // (this number is based on trial-and-error comparisons only!)
1407 constexpr double BUFFER_SIZE_SCALE = 2.0;
1408 if ( jsonPaint.contains( QStringLiteral( "text-halo-width" ) ) )
1409 {
1410 const QVariant jsonHaloWidth = jsonPaint.value( QStringLiteral( "text-halo-width" ) );
1411 switch ( jsonHaloWidth.type() )
1412 {
1413 case QVariant::Int:
1414 case QVariant::LongLong:
1415 case QVariant::Double:
1416 bufferSize = jsonHaloWidth.toDouble() * context.pixelSizeConversionFactor() * BUFFER_SIZE_SCALE;
1417 break;
1418
1419 case QVariant::Map:
1420 bufferSize = 1;
1421 ddLabelProperties.setProperty( QgsPalLayerSettings::BufferSize, parseInterpolateByZoom( jsonHaloWidth.toMap(), context, context.pixelSizeConversionFactor() * BUFFER_SIZE_SCALE, &bufferSize ) );
1422 break;
1423
1424 case QVariant::List:
1425 case QVariant::StringList:
1426 bufferSize = 1;
1427 ddLabelProperties.setProperty( QgsPalLayerSettings::BufferSize, parseValueList( jsonHaloWidth.toList(), PropertyType::Numeric, context, context.pixelSizeConversionFactor() * BUFFER_SIZE_SCALE, 255, nullptr, &bufferSize ) );
1428 break;
1429
1430 default:
1431 context.pushWarning( QObject::tr( "%1: Skipping unsupported text-halo-width type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonHaloWidth.type() ) ) );
1432 break;
1433 }
1434 }
1435
1436 double haloBlurSize = 0;
1437 if ( jsonPaint.contains( QStringLiteral( "text-halo-blur" ) ) )
1438 {
1439 const QVariant jsonTextHaloBlur = jsonPaint.value( QStringLiteral( "text-halo-blur" ) );
1440 switch ( jsonTextHaloBlur.type() )
1441 {
1442 case QVariant::Int:
1443 case QVariant::LongLong:
1444 case QVariant::Double:
1445 {
1446 haloBlurSize = jsonTextHaloBlur.toDouble() * context.pixelSizeConversionFactor();
1447 break;
1448 }
1449
1450 default:
1451 context.pushWarning( QObject::tr( "%1: Skipping unsupported text-halo-blur type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonTextHaloBlur.type() ) ) );
1452 break;
1453 }
1454 }
1455
1456 QgsTextFormat format;
1457 format.setSizeUnit( context.targetUnit() );
1458 if ( textColor.isValid() )
1459 format.setColor( textColor );
1460 if ( textSize >= 0 )
1461 format.setSize( textSize );
1462 if ( foundFont )
1463 {
1464 format.setFont( textFont );
1465 if ( !fontStyleName.isEmpty() )
1466 format.setNamedStyle( fontStyleName );
1467 }
1468 if ( textLetterSpacing > 0 )
1469 {
1470 QFont f = format.font();
1471 f.setLetterSpacing( QFont::AbsoluteSpacing, textLetterSpacing );
1472 format.setFont( f );
1473 }
1474
1475 if ( bufferSize > 0 )
1476 {
1477 format.buffer().setEnabled( true );
1478 format.buffer().setSize( bufferSize );
1479 format.buffer().setSizeUnit( context.targetUnit() );
1480 format.buffer().setColor( bufferColor );
1481
1482 if ( haloBlurSize > 0 )
1483 {
1484 QgsEffectStack *stack = new QgsEffectStack();
1485 QgsBlurEffect *blur = new QgsBlurEffect() ;
1486 blur->setEnabled( true );
1487 blur->setBlurUnit( context.targetUnit() );
1488 blur->setBlurLevel( haloBlurSize );
1490 stack->appendEffect( blur );
1491 stack->setEnabled( true );
1492 format.buffer().setPaintEffect( stack );
1493 }
1494 }
1495
1496 QgsPalLayerSettings labelSettings;
1497
1498 if ( textMaxWidth > 0 )
1499 {
1500 labelSettings.autoWrapLength = textMaxWidth;
1501 }
1502
1503 // convert field name
1504 if ( jsonLayout.contains( QStringLiteral( "text-field" ) ) )
1505 {
1506 const QVariant jsonTextField = jsonLayout.value( QStringLiteral( "text-field" ) );
1507 switch ( jsonTextField.type() )
1508 {
1509 case QVariant::String:
1510 {
1511 labelSettings.fieldName = processLabelField( jsonTextField.toString(), labelSettings.isExpression );
1512 break;
1513 }
1514
1515 case QVariant::List:
1516 case QVariant::StringList:
1517 {
1518 const QVariantList textFieldList = jsonTextField.toList();
1519 /*
1520 * e.g.
1521 * "text-field": ["format",
1522 * "foo", { "font-scale": 1.2 },
1523 * "bar", { "font-scale": 0.8 }
1524 * ]
1525 */
1526 if ( textFieldList.size() > 2 && textFieldList.at( 0 ).toString() == QLatin1String( "format" ) )
1527 {
1528 QStringList parts;
1529 for ( int i = 1; i < textFieldList.size(); ++i )
1530 {
1531 bool isExpression = false;
1532 const QString part = processLabelField( textFieldList.at( i ).toString(), isExpression );
1533 if ( !isExpression )
1534 parts << QgsExpression::quotedColumnRef( part );
1535 else
1536 parts << part;
1537 // TODO -- we could also translate font color, underline, overline, strikethrough to HTML tags!
1538 i += 1;
1539 }
1540 labelSettings.fieldName = QStringLiteral( "concat(%1)" ).arg( parts.join( ',' ) );
1541 labelSettings.isExpression = true;
1542 }
1543 else
1544 {
1545 /*
1546 * e.g.
1547 * "text-field": ["to-string", ["get", "name"]]
1548 */
1549 labelSettings.fieldName = parseExpression( textFieldList, context );
1550 labelSettings.isExpression = true;
1551 }
1552 break;
1553 }
1554
1555 case QVariant::Map:
1556 {
1557 const QVariantList stops = jsonTextField.toMap().value( QStringLiteral( "stops" ) ).toList();
1558 if ( !stops.empty() )
1559 {
1560 labelSettings.fieldName = parseLabelStops( stops, context );
1561 labelSettings.isExpression = true;
1562 }
1563 else
1564 {
1565 context.pushWarning( QObject::tr( "%1: Skipping unsupported text-field dictionary" ).arg( context.layerId() ) );
1566 }
1567 break;
1568 }
1569
1570 default:
1571 context.pushWarning( QObject::tr( "%1: Skipping unsupported text-field type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonTextField.type() ) ) );
1572 break;
1573 }
1574 }
1575
1576 if ( jsonLayout.contains( QStringLiteral( "text-transform" ) ) )
1577 {
1578 const QString textTransform = jsonLayout.value( QStringLiteral( "text-transform" ) ).toString();
1579 if ( textTransform == QLatin1String( "uppercase" ) )
1580 {
1581 labelSettings.fieldName = QStringLiteral( "upper(%1)" ).arg( labelSettings.isExpression ? labelSettings.fieldName : QgsExpression::quotedColumnRef( labelSettings.fieldName ) );
1582 }
1583 else if ( textTransform == QLatin1String( "lowercase" ) )
1584 {
1585 labelSettings.fieldName = QStringLiteral( "lower(%1)" ).arg( labelSettings.isExpression ? labelSettings.fieldName : QgsExpression::quotedColumnRef( labelSettings.fieldName ) );
1586 }
1587 labelSettings.isExpression = true;
1588 }
1589
1591 Qgis::GeometryType geometryType = Qgis::GeometryType::Point;
1592 if ( jsonLayout.contains( QStringLiteral( "symbol-placement" ) ) )
1593 {
1594 const QString symbolPlacement = jsonLayout.value( QStringLiteral( "symbol-placement" ) ).toString();
1595 if ( symbolPlacement == QLatin1String( "line" ) )
1596 {
1599 geometryType = Qgis::GeometryType::Line;
1600
1601 if ( jsonLayout.contains( QStringLiteral( "text-rotation-alignment" ) ) )
1602 {
1603 const QString textRotationAlignment = jsonLayout.value( QStringLiteral( "text-rotation-alignment" ) ).toString();
1604 if ( textRotationAlignment == QLatin1String( "viewport" ) )
1605 {
1607 }
1608 }
1609
1610 if ( labelSettings.placement == Qgis::LabelPlacement::Curved )
1611 {
1612 QPointF textOffset;
1613 QgsProperty textOffsetProperty;
1614 if ( jsonLayout.contains( QStringLiteral( "text-offset" ) ) )
1615 {
1616 const QVariant jsonTextOffset = jsonLayout.value( QStringLiteral( "text-offset" ) );
1617
1618 // units are ems!
1619 switch ( jsonTextOffset.type() )
1620 {
1621 case QVariant::Map:
1622 textOffsetProperty = parseInterpolatePointByZoom( jsonTextOffset.toMap(), context, !textSizeProperty ? textSize : 1.0, &textOffset );
1623 if ( !textSizeProperty )
1624 {
1625 ddLabelProperties.setProperty( QgsPalLayerSettings::LabelDistance, QStringLiteral( "abs(array_get(%1,1))-%2" ).arg( textOffsetProperty ).arg( textSize ) );
1626 }
1627 else
1628 {
1629 ddLabelProperties.setProperty( QgsPalLayerSettings::LabelDistance, QStringLiteral( "with_variable('text_size',%2,abs(array_get(%1,1))*@text_size-@text_size)" ).arg( textOffsetProperty.asExpression(), textSizeProperty.asExpression() ) );
1630 }
1631 ddLabelProperties.setProperty( QgsPalLayerSettings::LinePlacementOptions, QStringLiteral( "if(array_get(%1,1)>0,'BL','AL')" ).arg( textOffsetProperty ) );
1632 break;
1633
1634 case QVariant::List:
1635 case QVariant::StringList:
1636 textOffset = QPointF( jsonTextOffset.toList().value( 0 ).toDouble() * textSize,
1637 jsonTextOffset.toList().value( 1 ).toDouble() * textSize );
1638 break;
1639
1640 default:
1641 context.pushWarning( QObject::tr( "%1: Skipping unsupported text-offset type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonTextOffset.type() ) ) );
1642 break;
1643 }
1644
1645 if ( !textOffset.isNull() )
1646 {
1647 labelSettings.distUnits = context.targetUnit();
1648 labelSettings.dist = std::abs( textOffset.y() ) - textSize;
1649 labelSettings.lineSettings().setPlacementFlags( textOffset.y() > 0.0 ? QgsLabeling::BelowLine : QgsLabeling::AboveLine );
1650 if ( textSizeProperty && !textOffsetProperty )
1651 {
1652 ddLabelProperties.setProperty( QgsPalLayerSettings::LabelDistance, QStringLiteral( "with_variable('text_size',%2,%1*@text_size-@text_size)" ).arg( std::abs( textOffset.y() / textSize ) ).arg( textSizeProperty.asExpression() ) );
1653 }
1654 }
1655 }
1656
1657 if ( textOffset.isNull() )
1658 {
1660 }
1661 }
1662 }
1663 }
1664
1665 if ( jsonLayout.contains( QStringLiteral( "text-justify" ) ) )
1666 {
1667 const QVariant jsonTextJustify = jsonLayout.value( QStringLiteral( "text-justify" ) );
1668
1669 // default is center
1670 QString textAlign = QStringLiteral( "center" );
1671
1672 const QVariantMap conversionMap
1673 {
1674 { QStringLiteral( "left" ), QStringLiteral( "left" ) },
1675 { QStringLiteral( "center" ), QStringLiteral( "center" ) },
1676 { QStringLiteral( "right" ), QStringLiteral( "right" ) },
1677 { QStringLiteral( "auto" ), QStringLiteral( "follow" ) }
1678 };
1679
1680 switch ( jsonTextJustify.type() )
1681 {
1682 case QVariant::String:
1683 textAlign = jsonTextJustify.toString();
1684 break;
1685
1686 case QVariant::List:
1687 ddLabelProperties.setProperty( QgsPalLayerSettings::OffsetQuad, QgsProperty::fromExpression( parseStringStops( jsonTextJustify.toList(), context, conversionMap, &textAlign ) ) );
1688 break;
1689
1690 case QVariant::Map:
1691 ddLabelProperties.setProperty( QgsPalLayerSettings::OffsetQuad, parseInterpolateStringByZoom( jsonTextJustify.toMap(), context, conversionMap, &textAlign ) );
1692 break;
1693
1694 default:
1695 context.pushWarning( QObject::tr( "%1: Skipping unsupported text-justify type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonTextJustify.type() ) ) );
1696 break;
1697 }
1698
1699 if ( textAlign == QLatin1String( "left" ) )
1700 labelSettings.multilineAlign = Qgis::LabelMultiLineAlignment::Left;
1701 else if ( textAlign == QLatin1String( "right" ) )
1702 labelSettings.multilineAlign = Qgis::LabelMultiLineAlignment::Right;
1703 else if ( textAlign == QLatin1String( "center" ) )
1704 labelSettings.multilineAlign = Qgis::LabelMultiLineAlignment::Center;
1705 else if ( textAlign == QLatin1String( "follow" ) )
1706 labelSettings.multilineAlign = Qgis::LabelMultiLineAlignment::FollowPlacement;
1707 }
1708 else
1709 {
1710 labelSettings.multilineAlign = Qgis::LabelMultiLineAlignment::Center;
1711 }
1712
1713 if ( labelSettings.placement == Qgis::LabelPlacement::OverPoint )
1714 {
1715 if ( jsonLayout.contains( QStringLiteral( "text-anchor" ) ) )
1716 {
1717 const QVariant jsonTextAnchor = jsonLayout.value( QStringLiteral( "text-anchor" ) );
1718 QString textAnchor;
1719
1720 const QVariantMap conversionMap
1721 {
1722 { QStringLiteral( "center" ), 4 },
1723 { QStringLiteral( "left" ), 5 },
1724 { QStringLiteral( "right" ), 3 },
1725 { QStringLiteral( "top" ), 7 },
1726 { QStringLiteral( "bottom" ), 1 },
1727 { QStringLiteral( "top-left" ), 8 },
1728 { QStringLiteral( "top-right" ), 6 },
1729 { QStringLiteral( "bottom-left" ), 2 },
1730 { QStringLiteral( "bottom-right" ), 0 },
1731 };
1732
1733 switch ( jsonTextAnchor.type() )
1734 {
1735 case QVariant::String:
1736 textAnchor = jsonTextAnchor.toString();
1737 break;
1738
1739 case QVariant::List:
1740 ddLabelProperties.setProperty( QgsPalLayerSettings::OffsetQuad, QgsProperty::fromExpression( parseStringStops( jsonTextAnchor.toList(), context, conversionMap, &textAnchor ) ) );
1741 break;
1742
1743 case QVariant::Map:
1744 ddLabelProperties.setProperty( QgsPalLayerSettings::OffsetQuad, parseInterpolateStringByZoom( jsonTextAnchor.toMap(), context, conversionMap, &textAnchor ) );
1745 break;
1746
1747 default:
1748 context.pushWarning( QObject::tr( "%1: Skipping unsupported text-anchor type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonTextAnchor.type() ) ) );
1749 break;
1750 }
1751
1752 if ( textAnchor == QLatin1String( "center" ) )
1753 labelSettings.quadOffset = Qgis::LabelQuadrantPosition::Over;
1754 else if ( textAnchor == QLatin1String( "left" ) )
1755 labelSettings.quadOffset = Qgis::LabelQuadrantPosition::Right;
1756 else if ( textAnchor == QLatin1String( "right" ) )
1757 labelSettings.quadOffset = Qgis::LabelQuadrantPosition::Left;
1758 else if ( textAnchor == QLatin1String( "top" ) )
1759 labelSettings.quadOffset = Qgis::LabelQuadrantPosition::Below;
1760 else if ( textAnchor == QLatin1String( "bottom" ) )
1761 labelSettings.quadOffset = Qgis::LabelQuadrantPosition::Above;
1762 else if ( textAnchor == QLatin1String( "top-left" ) )
1763 labelSettings.quadOffset = Qgis::LabelQuadrantPosition::BelowRight;
1764 else if ( textAnchor == QLatin1String( "top-right" ) )
1765 labelSettings.quadOffset = Qgis::LabelQuadrantPosition::BelowLeft;
1766 else if ( textAnchor == QLatin1String( "bottom-left" ) )
1767 labelSettings.quadOffset = Qgis::LabelQuadrantPosition::AboveRight;
1768 else if ( textAnchor == QLatin1String( "bottom-right" ) )
1769 labelSettings.quadOffset = Qgis::LabelQuadrantPosition::AboveLeft;
1770 }
1771
1772 QPointF textOffset;
1773 if ( jsonLayout.contains( QStringLiteral( "text-offset" ) ) )
1774 {
1775 const QVariant jsonTextOffset = jsonLayout.value( QStringLiteral( "text-offset" ) );
1776
1777 // units are ems!
1778 switch ( jsonTextOffset.type() )
1779 {
1780 case QVariant::Map:
1781 ddLabelProperties.setProperty( QgsPalLayerSettings::OffsetXY, parseInterpolatePointByZoom( jsonTextOffset.toMap(), context, textSize, &textOffset ) );
1782 break;
1783
1784 case QVariant::List:
1785 case QVariant::StringList:
1786 textOffset = QPointF( jsonTextOffset.toList().value( 0 ).toDouble() * textSize,
1787 jsonTextOffset.toList().value( 1 ).toDouble() * textSize );
1788 break;
1789
1790 default:
1791 context.pushWarning( QObject::tr( "%1: Skipping unsupported text-offset type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonTextOffset.type() ) ) );
1792 break;
1793 }
1794
1795 if ( !textOffset.isNull() )
1796 {
1797 labelSettings.offsetUnits = context.targetUnit();
1798 labelSettings.xOffset = textOffset.x();
1799 labelSettings.yOffset = textOffset.y();
1800 }
1801 }
1802 }
1803
1804 if ( jsonLayout.contains( QStringLiteral( "icon-image" ) ) &&
1805 ( labelSettings.placement == Qgis::LabelPlacement::Horizontal || labelSettings.placement == Qgis::LabelPlacement::Curved ) )
1806 {
1807 QSize spriteSize;
1808 QString spriteProperty, spriteSizeProperty;
1809 const QString sprite = retrieveSpriteAsBase64( jsonLayout.value( QStringLiteral( "icon-image" ) ), context, spriteSize, spriteProperty, spriteSizeProperty );
1810 if ( !sprite.isEmpty() )
1811 {
1813 markerLayer->setPath( sprite );
1814 markerLayer->setSize( spriteSize.width() );
1815 markerLayer->setSizeUnit( context.targetUnit() );
1816
1817 if ( !spriteProperty.isEmpty() )
1818 {
1819 QgsPropertyCollection markerDdProperties;
1820 markerDdProperties.setProperty( QgsSymbolLayer::PropertyName, QgsProperty::fromExpression( spriteProperty ) );
1821 markerLayer->setDataDefinedProperties( markerDdProperties );
1822
1823 ddLabelProperties.setProperty( QgsPalLayerSettings::ShapeSizeX, QgsProperty::fromExpression( spriteSizeProperty ) );
1824 }
1825
1826 QgsTextBackgroundSettings backgroundSettings;
1827 backgroundSettings.setEnabled( true );
1829 backgroundSettings.setSize( spriteSize );
1830 backgroundSettings.setSizeUnit( context.targetUnit() );
1832 backgroundSettings.setMarkerSymbol( new QgsMarkerSymbol( QgsSymbolLayerList() << markerLayer ) );
1833 format.setBackground( backgroundSettings );
1834 }
1835 }
1836
1837 if ( textSize >= 0 )
1838 {
1839 // TODO -- this probably needs revisiting -- it was copied from the MapTiler code, but may be wrong...
1840 labelSettings.priority = std::min( textSize / ( context.pixelSizeConversionFactor() * 3 ), 10.0 );
1841 }
1842
1843 labelSettings.setFormat( format );
1844
1845 // use a low obstacle weight for layers by default -- we'd rather have more labels for these layers, even if placement isn't ideal
1846 labelSettings.obstacleSettings().setFactor( 0.1 );
1847
1848 labelSettings.setDataDefinedProperties( ddLabelProperties );
1849
1850 labelingStyle.setGeometryType( geometryType );
1851 labelingStyle.setLabelSettings( labelSettings );
1852
1853 hasLabeling = true;
1854
1855 hasRenderer = parseSymbolLayerAsRenderer( jsonLayer, renderer, context );
1856}
1857
1859{
1860 if ( !jsonLayer.contains( QStringLiteral( "layout" ) ) )
1861 {
1862 context.pushWarning( QObject::tr( "%1: Style layer has no layout property, skipping" ).arg( context.layerId() ) );
1863 return false;
1864 }
1865 const QVariantMap jsonLayout = jsonLayer.value( QStringLiteral( "layout" ) ).toMap();
1866
1867 if ( jsonLayout.value( QStringLiteral( "symbol-placement" ) ).toString() == QLatin1String( "line" ) && !jsonLayout.contains( QStringLiteral( "text-field" ) ) )
1868 {
1869 QgsPropertyCollection ddProperties;
1870
1871 double spacing = -1.0;
1872 if ( jsonLayout.contains( QStringLiteral( "symbol-spacing" ) ) )
1873 {
1874 const QVariant jsonSpacing = jsonLayout.value( QStringLiteral( "symbol-spacing" ) );
1875 switch ( jsonSpacing.type() )
1876 {
1877 case QVariant::Int:
1878 case QVariant::LongLong:
1879 case QVariant::Double:
1880 spacing = jsonSpacing.toDouble() * context.pixelSizeConversionFactor();
1881 break;
1882
1883 case QVariant::Map:
1884 ddProperties.setProperty( QgsSymbolLayer::PropertyInterval, parseInterpolateByZoom( jsonSpacing.toMap(), context, context.pixelSizeConversionFactor(), &spacing ) );
1885 break;
1886
1887 case QVariant::List:
1888 case QVariant::StringList:
1889 ddProperties.setProperty( QgsSymbolLayer::PropertyInterval, parseValueList( jsonSpacing.toList(), PropertyType::Numeric, context, context.pixelSizeConversionFactor(), 255, nullptr, &spacing ) );
1890 break;
1891
1892 default:
1893 context.pushWarning( QObject::tr( "%1: Skipping unsupported symbol-spacing type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonSpacing.type() ) ) );
1894 break;
1895 }
1896 }
1897 else
1898 {
1899 // defaults to 250
1900 spacing = 250 * context.pixelSizeConversionFactor();
1901 }
1902
1903 bool rotateMarkers = true;
1904 if ( jsonLayout.contains( QStringLiteral( "icon-rotation-alignment" ) ) )
1905 {
1906 const QString alignment = jsonLayout.value( QStringLiteral( "icon-rotation-alignment" ) ).toString();
1907 if ( alignment == QLatin1String( "map" ) || alignment == QLatin1String( "auto" ) )
1908 {
1909 rotateMarkers = true;
1910 }
1911 else if ( alignment == QLatin1String( "viewport" ) )
1912 {
1913 rotateMarkers = false;
1914 }
1915 }
1916
1917 QgsPropertyCollection markerDdProperties;
1918 double rotation = 0.0;
1919 if ( jsonLayout.contains( QStringLiteral( "icon-rotate" ) ) )
1920 {
1921 const QVariant jsonIconRotate = jsonLayout.value( QStringLiteral( "icon-rotate" ) );
1922 switch ( jsonIconRotate.type() )
1923 {
1924 case QVariant::Int:
1925 case QVariant::LongLong:
1926 case QVariant::Double:
1927 rotation = jsonIconRotate.toDouble();
1928 break;
1929
1930 case QVariant::Map:
1931 markerDdProperties.setProperty( QgsSymbolLayer::PropertyAngle, parseInterpolateByZoom( jsonIconRotate.toMap(), context, context.pixelSizeConversionFactor(), &rotation ) );
1932 break;
1933
1934 case QVariant::List:
1935 case QVariant::StringList:
1936 markerDdProperties.setProperty( QgsSymbolLayer::PropertyAngle, parseValueList( jsonIconRotate.toList(), PropertyType::Numeric, context, context.pixelSizeConversionFactor(), 255, nullptr, &rotation ) );
1937 break;
1938
1939 default:
1940 context.pushWarning( QObject::tr( "%1: Skipping unsupported icon-rotate type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonIconRotate.type() ) ) );
1941 break;
1942 }
1943 }
1944
1945 QgsMarkerLineSymbolLayer *lineSymbol = new QgsMarkerLineSymbolLayer( rotateMarkers, spacing > 0 ? spacing : 1 );
1946 lineSymbol->setOutputUnit( context.targetUnit() );
1947 lineSymbol->setDataDefinedProperties( ddProperties );
1948 if ( spacing < 1 )
1949 {
1950 // if spacing isn't specified, it's a central point marker only
1952 }
1953
1955 QSize spriteSize;
1956 QString spriteProperty, spriteSizeProperty;
1957 const QString sprite = retrieveSpriteAsBase64( jsonLayout.value( QStringLiteral( "icon-image" ) ), context, spriteSize, spriteProperty, spriteSizeProperty );
1958 if ( !sprite.isNull() )
1959 {
1960 markerLayer->setPath( sprite );
1961 markerLayer->setSize( spriteSize.width() );
1962 markerLayer->setSizeUnit( context.targetUnit() );
1963
1964 if ( !spriteProperty.isEmpty() )
1965 {
1966 markerDdProperties.setProperty( QgsSymbolLayer::PropertyName, QgsProperty::fromExpression( spriteProperty ) );
1967 markerDdProperties.setProperty( QgsSymbolLayer::PropertyWidth, QgsProperty::fromExpression( spriteSizeProperty ) );
1968 }
1969 }
1970
1971 if ( jsonLayout.contains( QStringLiteral( "icon-size" ) ) )
1972 {
1973 const QVariant jsonIconSize = jsonLayout.value( QStringLiteral( "icon-size" ) );
1974 double size = 1.0;
1975 QgsProperty property;
1976 switch ( jsonIconSize.type() )
1977 {
1978 case QVariant::Int:
1979 case QVariant::LongLong:
1980 case QVariant::Double:
1981 {
1982 size = jsonIconSize.toDouble();
1983 if ( !spriteSizeProperty.isEmpty() )
1984 {
1985 markerDdProperties.setProperty( QgsSymbolLayer::PropertyWidth,
1986 QgsProperty::fromExpression( QStringLiteral( "with_variable('marker_size',%1,%2*@marker_size)" ).arg( spriteSizeProperty ).arg( size ) ) );
1987 }
1988 break;
1989 }
1990
1991 case QVariant::Map:
1992 property = parseInterpolateByZoom( jsonIconSize.toMap(), context, 1, &size );
1993 break;
1994
1995 case QVariant::List:
1996 case QVariant::StringList:
1997 default:
1998 context.pushWarning( QObject::tr( "%1: Skipping non-implemented icon-size type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonIconSize.type() ) ) );
1999 break;
2000 }
2001 markerLayer->setSize( size * spriteSize.width() );
2002 if ( !property.expressionString().isEmpty() )
2003 {
2004 if ( !spriteSizeProperty.isEmpty() )
2005 {
2006 markerDdProperties.setProperty( QgsSymbolLayer::PropertyWidth,
2007 QgsProperty::fromExpression( QStringLiteral( "with_variable('marker_size',%1,(%2)*@marker_size)" ).arg( spriteSizeProperty ).arg( property.expressionString() ) ) );
2008 }
2009 else
2010 {
2011 markerDdProperties.setProperty( QgsSymbolLayer::PropertyWidth,
2012 QgsProperty::fromExpression( QStringLiteral( "(%2)*%1" ).arg( spriteSize.width() ).arg( property.expressionString() ) ) );
2013 }
2014 }
2015 }
2016
2017 markerLayer->setDataDefinedProperties( markerDdProperties );
2018 markerLayer->setAngle( rotation );
2019 lineSymbol->setSubSymbol( new QgsMarkerSymbol( QgsSymbolLayerList() << markerLayer ) );
2020
2021 std::unique_ptr< QgsSymbol > symbol = std::make_unique< QgsLineSymbol >( QgsSymbolLayerList() << lineSymbol );
2022
2023 // set render units
2024 symbol->setOutputUnit( context.targetUnit() );
2025 lineSymbol->setOutputUnit( context.targetUnit() );
2026
2027 rendererStyle.setGeometryType( Qgis::GeometryType::Line );
2028 rendererStyle.setSymbol( symbol.release() );
2029 return true;
2030 }
2031 else if ( jsonLayout.contains( QStringLiteral( "icon-image" ) ) )
2032 {
2033 const QVariantMap jsonPaint = jsonLayer.value( QStringLiteral( "paint" ) ).toMap();
2034
2035 QSize spriteSize;
2036 QString spriteProperty, spriteSizeProperty;
2037 const QString sprite = retrieveSpriteAsBase64( jsonLayout.value( QStringLiteral( "icon-image" ) ), context, spriteSize, spriteProperty, spriteSizeProperty );
2038 if ( !sprite.isEmpty() )
2039 {
2041 rasterMarker->setPath( sprite );
2042 rasterMarker->setSize( spriteSize.width() );
2043 rasterMarker->setSizeUnit( context.targetUnit() );
2044
2045 QgsPropertyCollection markerDdProperties;
2046 if ( !spriteProperty.isEmpty() )
2047 {
2048 markerDdProperties.setProperty( QgsSymbolLayer::PropertyName, QgsProperty::fromExpression( spriteProperty ) );
2049 markerDdProperties.setProperty( QgsSymbolLayer::PropertyWidth, QgsProperty::fromExpression( spriteSizeProperty ) );
2050 }
2051
2052 if ( jsonLayout.contains( QStringLiteral( "icon-size" ) ) )
2053 {
2054 const QVariant jsonIconSize = jsonLayout.value( QStringLiteral( "icon-size" ) );
2055 double size = 1.0;
2056 QgsProperty property;
2057 switch ( jsonIconSize.type() )
2058 {
2059 case QVariant::Int:
2060 case QVariant::LongLong:
2061 case QVariant::Double:
2062 {
2063 size = jsonIconSize.toDouble();
2064 if ( !spriteSizeProperty.isEmpty() )
2065 {
2066 markerDdProperties.setProperty( QgsSymbolLayer::PropertyWidth,
2067 QgsProperty::fromExpression( QStringLiteral( "with_variable('marker_size',%1,%2*@marker_size)" ).arg( spriteSizeProperty ).arg( size ) ) );
2068 }
2069 break;
2070 }
2071
2072 case QVariant::Map:
2073 property = parseInterpolateByZoom( jsonIconSize.toMap(), context, 1, &size );
2074 break;
2075
2076 case QVariant::List:
2077 case QVariant::StringList:
2078 default:
2079 context.pushWarning( QObject::tr( "%1: Skipping non-implemented icon-size type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonIconSize.type() ) ) );
2080 break;
2081 }
2082 rasterMarker->setSize( size * spriteSize.width() );
2083 if ( !property.expressionString().isEmpty() )
2084 {
2085 if ( !spriteSizeProperty.isEmpty() )
2086 {
2087 markerDdProperties.setProperty( QgsSymbolLayer::PropertyWidth,
2088 QgsProperty::fromExpression( QStringLiteral( "with_variable('marker_size',%1,(%2)*@marker_size)" ).arg( spriteSizeProperty ).arg( property.expressionString() ) ) );
2089 }
2090 else
2091 {
2092 markerDdProperties.setProperty( QgsSymbolLayer::PropertyWidth,
2093 QgsProperty::fromExpression( QStringLiteral( "(%2)*%1" ).arg( spriteSize.width() ).arg( property.expressionString() ) ) );
2094 }
2095 }
2096 }
2097
2098 double rotation = 0.0;
2099 if ( jsonLayout.contains( QStringLiteral( "icon-rotate" ) ) )
2100 {
2101 const QVariant jsonIconRotate = jsonLayout.value( QStringLiteral( "icon-rotate" ) );
2102 switch ( jsonIconRotate.type() )
2103 {
2104 case QVariant::Int:
2105 case QVariant::LongLong:
2106 case QVariant::Double:
2107 rotation = jsonIconRotate.toDouble();
2108 break;
2109
2110 case QVariant::Map:
2111 markerDdProperties.setProperty( QgsSymbolLayer::PropertyAngle, parseInterpolateByZoom( jsonIconRotate.toMap(), context, context.pixelSizeConversionFactor(), &rotation ) );
2112 break;
2113
2114 case QVariant::List:
2115 case QVariant::StringList:
2116 markerDdProperties.setProperty( QgsSymbolLayer::PropertyAngle, parseValueList( jsonIconRotate.toList(), PropertyType::Numeric, context, context.pixelSizeConversionFactor(), 255, nullptr, &rotation ) );
2117 break;
2118
2119 default:
2120 context.pushWarning( QObject::tr( "%1: Skipping unsupported icon-rotate type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonIconRotate.type() ) ) );
2121 break;
2122 }
2123 }
2124
2125 double iconOpacity = -1.0;
2126 if ( jsonPaint.contains( QStringLiteral( "icon-opacity" ) ) )
2127 {
2128 const QVariant jsonIconOpacity = jsonPaint.value( QStringLiteral( "icon-opacity" ) );
2129 switch ( jsonIconOpacity.type() )
2130 {
2131 case QVariant::Int:
2132 case QVariant::LongLong:
2133 case QVariant::Double:
2134 iconOpacity = jsonIconOpacity.toDouble();
2135 break;
2136
2137 case QVariant::Map:
2138 markerDdProperties.setProperty( QgsSymbolLayer::PropertyOpacity, parseInterpolateByZoom( jsonIconOpacity.toMap(), context, 100, &iconOpacity ) );
2139 break;
2140
2141 case QVariant::List:
2142 case QVariant::StringList:
2143 markerDdProperties.setProperty( QgsSymbolLayer::PropertyOpacity, parseValueList( jsonIconOpacity.toList(), PropertyType::Numeric, context, 100, 255, nullptr, &iconOpacity ) );
2144 break;
2145
2146 default:
2147 context.pushWarning( QObject::tr( "%1: Skipping unsupported icon-opacity type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonIconOpacity.type() ) ) );
2148 break;
2149 }
2150 }
2151
2152 rasterMarker->setDataDefinedProperties( markerDdProperties );
2153 rasterMarker->setAngle( rotation );
2154 if ( iconOpacity >= 0 )
2155 rasterMarker->setOpacity( iconOpacity );
2156
2157 QgsMarkerSymbol *markerSymbol = new QgsMarkerSymbol( QgsSymbolLayerList() << rasterMarker );
2158 rendererStyle.setSymbol( markerSymbol );
2159 rendererStyle.setGeometryType( Qgis::GeometryType::Point );
2160 return true;
2161 }
2162 }
2163
2164 return false;
2165}
2166
2168{
2169 const double base = json.value( QStringLiteral( "base" ), QStringLiteral( "1" ) ).toDouble();
2170 const QVariantList stops = json.value( QStringLiteral( "stops" ) ).toList();
2171 if ( stops.empty() )
2172 return QgsProperty();
2173
2174 QString caseString = QStringLiteral( "CASE " );
2175 const QString colorComponent( "color_part(%1,'%2')" );
2176
2177 for ( int i = 0; i < stops.length() - 1; ++i )
2178 {
2179 // step bottom zoom
2180 const QString bz = stops.at( i ).toList().value( 0 ).toString();
2181 // step top zoom
2182 const QString tz = stops.at( i + 1 ).toList().value( 0 ).toString();
2183
2184 const QVariant bcVariant = stops.at( i ).toList().value( 1 );
2185 const QVariant tcVariant = stops.at( i + 1 ).toList().value( 1 );
2186
2187 const QColor bottomColor = parseColor( bcVariant.toString(), context );
2188 const QColor topColor = parseColor( tcVariant.toString(), context );
2189
2190 if ( i == 0 && bottomColor.isValid() )
2191 {
2192 int bcHue;
2193 int bcSat;
2194 int bcLight;
2195 int bcAlpha;
2196 colorAsHslaComponents( bottomColor, bcHue, bcSat, bcLight, bcAlpha );
2197 caseString += QStringLiteral( "WHEN @vector_tile_zoom < %1 THEN color_hsla(%2, %3, %4, %5) " )
2198 .arg( bz ).arg( bcHue ).arg( bcSat ).arg( bcLight ).arg( bcAlpha );
2199 }
2200
2201 if ( bottomColor.isValid() && topColor.isValid() )
2202 {
2203 int bcHue;
2204 int bcSat;
2205 int bcLight;
2206 int bcAlpha;
2207 colorAsHslaComponents( bottomColor, bcHue, bcSat, bcLight, bcAlpha );
2208 int tcHue;
2209 int tcSat;
2210 int tcLight;
2211 int tcAlpha;
2212 colorAsHslaComponents( topColor, tcHue, tcSat, tcLight, tcAlpha );
2213 caseString += QStringLiteral( "WHEN @vector_tile_zoom >= %1 AND @vector_tile_zoom < %2 THEN color_hsla("
2214 "%3, %4, %5, %6) " ).arg( bz, tz,
2215 interpolateExpression( bz.toDouble(), tz.toDouble(), bcHue, tcHue, base, 1, &context ),
2216 interpolateExpression( bz.toDouble(), tz.toDouble(), bcSat, tcSat, base, 1, &context ),
2217 interpolateExpression( bz.toDouble(), tz.toDouble(), bcLight, tcLight, base, 1, &context ),
2218 interpolateExpression( bz.toDouble(), tz.toDouble(), bcAlpha, tcAlpha, base, 1, &context ) );
2219 }
2220 else
2221 {
2222 const QString bottomColorExpr = parseColorExpression( bcVariant, context );
2223 const QString topColorExpr = parseColorExpression( tcVariant, context );
2224
2225 caseString += QStringLiteral( "WHEN @vector_tile_zoom >= %1 AND @vector_tile_zoom < %2 THEN color_hsla("
2226 "%3, %4, %5, %6) " ).arg( bz, tz,
2227 interpolateExpression( bz.toDouble(), tz.toDouble(), colorComponent.arg( bottomColorExpr ).arg( "hsl_hue" ), colorComponent.arg( topColorExpr ).arg( "hsl_hue" ), base, 1, &context ),
2228 interpolateExpression( bz.toDouble(), tz.toDouble(), colorComponent.arg( bottomColorExpr ).arg( "hsl_saturation" ), colorComponent.arg( topColorExpr ).arg( "hsl_saturation" ), base, 1, &context ),
2229 interpolateExpression( bz.toDouble(), tz.toDouble(), colorComponent.arg( bottomColorExpr ).arg( "lightness" ), colorComponent.arg( topColorExpr ).arg( "lightness" ), base, 1, &context ),
2230 interpolateExpression( bz.toDouble(), tz.toDouble(), colorComponent.arg( bottomColorExpr ).arg( "alpha" ), colorComponent.arg( topColorExpr ).arg( "alpha" ), base, 1, &context ) );
2231 }
2232 }
2233
2234 // top color
2235 const QString tz = stops.last().toList().value( 0 ).toString();
2236 const QVariant tcVariant = stops.last().toList().value( 1 );
2237 const QColor topColor = parseColor( stops.last().toList().value( 1 ), context );
2238 if ( topColor.isValid() )
2239 {
2240 int tcHue;
2241 int tcSat;
2242 int tcLight;
2243 int tcAlpha;
2244 colorAsHslaComponents( topColor, tcHue, tcSat, tcLight, tcAlpha );
2245 caseString += QStringLiteral( "WHEN @vector_tile_zoom >= %1 THEN color_hsla(%2, %3, %4, %5) "
2246 "ELSE color_hsla(%2, %3, %4, %5) END" ).arg( tz ).arg( tcHue ).arg( tcSat ).arg( tcLight ).arg( tcAlpha );
2247 }
2248 else
2249 {
2250 const QString topColorExpr = parseColorExpression( tcVariant, context );
2251
2252 caseString += QStringLiteral( "WHEN @vector_tile_zoom >= %1 THEN color_hsla(%2, %3, %4, %5) "
2253 "ELSE color_hsla(%2, %3, %4, %5) END" ).arg( tz )
2254 .arg( colorComponent.arg( topColorExpr ).arg( "hsl_hue" ) ).arg( colorComponent.arg( topColorExpr ).arg( "hsl_saturation" ) ).arg( colorComponent.arg( topColorExpr ).arg( "lightness" ) ).arg( colorComponent.arg( topColorExpr ).arg( "alpha" ) );
2255 }
2256
2257 if ( !stops.empty() && defaultColor )
2258 *defaultColor = parseColor( stops.value( 0 ).toList().value( 1 ).toString(), context );
2259
2260 return QgsProperty::fromExpression( caseString );
2261}
2262
2263QgsProperty QgsMapBoxGlStyleConverter::parseInterpolateByZoom( const QVariantMap &json, QgsMapBoxGlStyleConversionContext &context, double multiplier, double *defaultNumber )
2264{
2265 const double base = json.value( QStringLiteral( "base" ), QStringLiteral( "1" ) ).toDouble();
2266 const QVariantList stops = json.value( QStringLiteral( "stops" ) ).toList();
2267 if ( stops.empty() )
2268 return QgsProperty();
2269
2270 QString scaleExpression;
2271 if ( stops.size() <= 2 )
2272 {
2273 scaleExpression = interpolateExpression( stops.value( 0 ).toList().value( 0 ).toDouble(),
2274 stops.last().toList().value( 0 ).toDouble(),
2275 stops.value( 0 ).toList().value( 1 ),
2276 stops.last().toList().value( 1 ), base, multiplier, &context );
2277 }
2278 else
2279 {
2280 scaleExpression = parseStops( base, stops, multiplier, context );
2281 }
2282
2283 if ( !stops.empty() && defaultNumber )
2284 *defaultNumber = stops.value( 0 ).toList().value( 1 ).toDouble() * multiplier;
2285
2286 return QgsProperty::fromExpression( scaleExpression );
2287}
2288
2290{
2292 if ( contextPtr )
2293 {
2294 context = *contextPtr;
2295 }
2296 const double base = json.value( QStringLiteral( "base" ), QStringLiteral( "1" ) ).toDouble();
2297 const QVariantList stops = json.value( QStringLiteral( "stops" ) ).toList();
2298 if ( stops.empty() )
2299 return QgsProperty();
2300
2301 QString scaleExpression;
2302 if ( stops.length() <= 2 )
2303 {
2304 const QVariant bv = stops.value( 0 ).toList().value( 1 );
2305 const QVariant tv = stops.last().toList().value( 1 );
2306 double bottom = 0.0;
2307 double top = 0.0;
2308 const bool numeric = numericArgumentsOnly( bv, tv, bottom, top );
2309 scaleExpression = QStringLiteral( "set_color_part(@symbol_color, 'alpha', %1)" )
2310 .arg( interpolateExpression( stops.value( 0 ).toList().value( 0 ).toDouble(),
2311 stops.last().toList().value( 0 ).toDouble(),
2312 numeric ? QString::number( bottom * maxOpacity ) : QString( "(%1) * %2" ).arg( parseValue( bv, context ) ).arg( maxOpacity ),
2313 numeric ? QString::number( top * maxOpacity ) : QString( "(%1) * %2" ).arg( parseValue( tv, context ) ).arg( maxOpacity ), base, 1, &context ) );
2314 }
2315 else
2316 {
2317 scaleExpression = parseOpacityStops( base, stops, maxOpacity, context );
2318 }
2319 return QgsProperty::fromExpression( scaleExpression );
2320}
2321
2322QString QgsMapBoxGlStyleConverter::parseOpacityStops( double base, const QVariantList &stops, int maxOpacity, QgsMapBoxGlStyleConversionContext &context )
2323{
2324 QString caseString = QStringLiteral( "CASE WHEN @vector_tile_zoom < %1 THEN set_color_part(@symbol_color, 'alpha', %2)" )
2325 .arg( stops.value( 0 ).toList().value( 0 ).toString() )
2326 .arg( stops.value( 0 ).toList().value( 1 ).toDouble() * maxOpacity );
2327
2328 for ( int i = 0; i < stops.size() - 1; ++i )
2329 {
2330 const QVariant bv = stops.value( i ).toList().value( 1 );
2331 const QVariant tv = stops.value( i + 1 ).toList().value( 1 );
2332 double bottom = 0.0;
2333 double top = 0.0;
2334 const bool numeric = numericArgumentsOnly( bv, tv, bottom, top );
2335
2336 caseString += QStringLiteral( " WHEN @vector_tile_zoom >= %1 AND @vector_tile_zoom < %2 "
2337 "THEN set_color_part(@symbol_color, 'alpha', %3)" )
2338 .arg( stops.value( i ).toList().value( 0 ).toString(),
2339 stops.value( i + 1 ).toList().value( 0 ).toString(),
2340 interpolateExpression( stops.value( i ).toList().value( 0 ).toDouble(),
2341 stops.value( i + 1 ).toList().value( 0 ).toDouble(),
2342 numeric ? QString::number( bottom * maxOpacity ) : QString( "(%1) * %2" ).arg( parseValue( bv, context ) ).arg( maxOpacity ),
2343 numeric ? QString::number( top * maxOpacity ) : QString( "(%1) * %2" ).arg( parseValue( tv, context ) ).arg( maxOpacity ),
2344 base, 1, &context ) );
2345 }
2346
2347 caseString += QStringLiteral( " WHEN @vector_tile_zoom >= %1 "
2348 "THEN set_color_part(@symbol_color, 'alpha', %2) END" )
2349 .arg( stops.last().toList().value( 0 ).toString() )
2350 .arg( stops.last().toList().value( 1 ).toDouble() * maxOpacity );
2351 return caseString;
2352}
2353
2354QgsProperty QgsMapBoxGlStyleConverter::parseInterpolatePointByZoom( const QVariantMap &json, QgsMapBoxGlStyleConversionContext &context, double multiplier, QPointF *defaultPoint )
2355{
2356 const double base = json.value( QStringLiteral( "base" ), QStringLiteral( "1" ) ).toDouble();
2357 const QVariantList stops = json.value( QStringLiteral( "stops" ) ).toList();
2358 if ( stops.empty() )
2359 return QgsProperty();
2360
2361 QString scaleExpression;
2362 if ( stops.size() <= 2 )
2363 {
2364 scaleExpression = QStringLiteral( "array(%1,%2)" ).arg( interpolateExpression( stops.value( 0 ).toList().value( 0 ).toDouble(),
2365 stops.last().toList().value( 0 ).toDouble(),
2366 stops.value( 0 ).toList().value( 1 ).toList().value( 0 ),
2367 stops.last().toList().value( 1 ).toList().value( 0 ), base, multiplier, &context ),
2368 interpolateExpression( stops.value( 0 ).toList().value( 0 ).toDouble(),
2369 stops.last().toList().value( 0 ).toDouble(),
2370 stops.value( 0 ).toList().value( 1 ).toList().value( 1 ),
2371 stops.last().toList().value( 1 ).toList().value( 1 ), base, multiplier, &context )
2372 );
2373 }
2374 else
2375 {
2376 scaleExpression = parsePointStops( base, stops, context, multiplier );
2377 }
2378
2379 if ( !stops.empty() && defaultPoint )
2380 *defaultPoint = QPointF( stops.value( 0 ).toList().value( 1 ).toList().value( 0 ).toDouble() * multiplier,
2381 stops.value( 0 ).toList().value( 1 ).toList().value( 1 ).toDouble() * multiplier );
2382
2383 return QgsProperty::fromExpression( scaleExpression );
2384}
2385
2387 const QVariantMap &conversionMap, QString *defaultString )
2388{
2389 const QVariantList stops = json.value( QStringLiteral( "stops" ) ).toList();
2390 if ( stops.empty() )
2391 return QgsProperty();
2392
2393 const QString scaleExpression = parseStringStops( stops, context, conversionMap, defaultString );
2394
2395 return QgsProperty::fromExpression( scaleExpression );
2396}
2397
2398QString QgsMapBoxGlStyleConverter::parsePointStops( double base, const QVariantList &stops, QgsMapBoxGlStyleConversionContext &context, double multiplier )
2399{
2400 QString caseString = QStringLiteral( "CASE " );
2401
2402 for ( int i = 0; i < stops.length() - 1; ++i )
2403 {
2404 // bottom zoom and value
2405 const QVariant bz = stops.value( i ).toList().value( 0 );
2406 const QVariant bv = stops.value( i ).toList().value( 1 );
2407 if ( bv.type() != QVariant::List && bv.type() != QVariant::StringList )
2408 {
2409 context.pushWarning( QObject::tr( "%1: Skipping unsupported offset interpolation type (%2)." ).arg( context.layerId(), QMetaType::typeName( bz.type() ) ) );
2410 return QString();
2411 }
2412
2413 // top zoom and value
2414 const QVariant tz = stops.value( i + 1 ).toList().value( 0 );
2415 const QVariant tv = stops.value( i + 1 ).toList().value( 1 );
2416 if ( tv.type() != QVariant::List && tv.type() != QVariant::StringList )
2417 {
2418 context.pushWarning( QObject::tr( "%1: Skipping unsupported offset interpolation type (%2)." ).arg( context.layerId(), QMetaType::typeName( tz.type() ) ) );
2419 return QString();
2420 }
2421
2422 caseString += QStringLiteral( "WHEN @vector_tile_zoom > %1 AND @vector_tile_zoom <= %2 "
2423 "THEN array(%3,%4)" ).arg( bz.toString(),
2424 tz.toString(),
2425 interpolateExpression( bz.toDouble(), tz.toDouble(), bv.toList().value( 0 ), tv.toList().value( 0 ), base, multiplier, &context ),
2426 interpolateExpression( bz.toDouble(), tz.toDouble(), bv.toList().value( 1 ), tv.toList().value( 1 ), base, multiplier, &context ) );
2427 }
2428 caseString += QLatin1String( "END" );
2429 return caseString;
2430}
2431
2432QString QgsMapBoxGlStyleConverter::parseArrayStops( const QVariantList &stops, QgsMapBoxGlStyleConversionContext &, double multiplier )
2433{
2434 if ( stops.length() < 2 )
2435 return QString();
2436
2437 QString caseString = QStringLiteral( "CASE " );
2438
2439 for ( int i = 0; i < stops.length() - 1; ++i )
2440 {
2441 // bottom zoom and value
2442 const QVariant bz = stops.value( i ).toList().value( 0 );
2443 const QList<QVariant> bv = stops.value( i ).toList().value( 1 ).toList();
2444 QStringList bl;
2445 bool ok = false;
2446 for ( const QVariant &value : bv )
2447 {
2448 const double number = value.toDouble( &ok );
2449 if ( ok )
2450 bl << QString::number( number * multiplier );
2451 }
2452
2453 // top zoom and value
2454 const QVariant tz = stops.value( i + 1 ).toList().value( 0 );
2455 caseString += QStringLiteral( "WHEN @vector_tile_zoom > %1 AND @vector_tile_zoom <= %2 "
2456 "THEN array(%3) " ).arg( bz.toString(),
2457 tz.toString(),
2458 bl.join( ',' ) );
2459 }
2460 const QVariant lz = stops.value( stops.length() - 1 ).toList().value( 0 );
2461 const QList<QVariant> lv = stops.value( stops.length() - 1 ).toList().value( 1 ).toList();
2462 QStringList ll;
2463 bool ok = false;
2464 for ( const QVariant &value : lv )
2465 {
2466 const double number = value.toDouble( &ok );
2467 if ( ok )
2468 ll << QString::number( number * multiplier );
2469 }
2470 caseString += QStringLiteral( "WHEN @vector_tile_zoom > %1 "
2471 "THEN array(%2) " ).arg( lz.toString(),
2472 ll.join( ',' ) );
2473 caseString += QLatin1String( "END" );
2474 return caseString;
2475}
2476
2477QString QgsMapBoxGlStyleConverter::parseStops( double base, const QVariantList &stops, double multiplier, QgsMapBoxGlStyleConversionContext &context )
2478{
2479 QString caseString = QStringLiteral( "CASE " );
2480
2481 for ( int i = 0; i < stops.length() - 1; ++i )
2482 {
2483 // bottom zoom and value
2484 const QVariant bz = stops.value( i ).toList().value( 0 );
2485 const QVariant bv = stops.value( i ).toList().value( 1 );
2486 if ( bz.type() == QVariant::List || bz.type() == QVariant::StringList )
2487 {
2488 context.pushWarning( QObject::tr( "%1: Expressions in interpolation function are not supported, skipping." ).arg( context.layerId() ) );
2489 return QString();
2490 }
2491
2492 // top zoom and value
2493 const QVariant tz = stops.value( i + 1 ).toList().value( 0 );
2494 const QVariant tv = stops.value( i + 1 ).toList().value( 1 );
2495 if ( tz.type() == QVariant::List || tz.type() == QVariant::StringList )
2496 {
2497 context.pushWarning( QObject::tr( "%1: Expressions in interpolation function are not supported, skipping." ).arg( context.layerId() ) );
2498 return QString();
2499 }
2500
2501 const QString lowerComparator = i == 0 ? QStringLiteral( ">=" ) : QStringLiteral( ">" );
2502
2503 caseString += QStringLiteral( "WHEN @vector_tile_zoom %1 %2 AND @vector_tile_zoom <= %3 "
2504 "THEN %4 " ).arg( lowerComparator,
2505 bz.toString(),
2506 tz.toString(),
2507 interpolateExpression( bz.toDouble(), tz.toDouble(), bv, tv, base, multiplier, &context ) );
2508 }
2509
2510 const QVariant z = stops.last().toList().value( 0 );
2511 const QVariant v = stops.last().toList().value( 1 );
2512 QString vStr = v.toString();
2513 if ( ( QMetaType::Type )v.type() == QMetaType::QVariantList )
2514 {
2515 vStr = parseExpression( v.toList(), context );
2516 caseString += QStringLiteral( "WHEN @vector_tile_zoom > %1 "
2517 "THEN ( ( %2 ) * %3 ) END" ).arg( z.toString() ).arg( vStr ).arg( multiplier );
2518 }
2519 else
2520 {
2521 caseString += QStringLiteral( "WHEN @vector_tile_zoom > %1 "
2522 "THEN %2 END" ).arg( z.toString() ).arg( v.toDouble() * multiplier );
2523 }
2524
2525 return caseString;
2526}
2527
2528QString QgsMapBoxGlStyleConverter::parseStringStops( const QVariantList &stops, QgsMapBoxGlStyleConversionContext &context, const QVariantMap &conversionMap, QString *defaultString )
2529{
2530 QString caseString = QStringLiteral( "CASE " );
2531
2532 for ( int i = 0; i < stops.length() - 1; ++i )
2533 {
2534 // bottom zoom and value
2535 const QVariant bz = stops.value( i ).toList().value( 0 );
2536 const QString bv = stops.value( i ).toList().value( 1 ).toString();
2537 if ( bz.type() == QVariant::List || bz.type() == QVariant::StringList )
2538 {
2539 context.pushWarning( QObject::tr( "%1: Expressions in interpolation function are not supported, skipping." ).arg( context.layerId() ) );
2540 return QString();
2541 }
2542
2543 // top zoom
2544 const QVariant tz = stops.value( i + 1 ).toList().value( 0 );
2545 if ( tz.type() == QVariant::List || tz.type() == QVariant::StringList )
2546 {
2547 context.pushWarning( QObject::tr( "%1: Expressions in interpolation function are not supported, skipping." ).arg( context.layerId() ) );
2548 return QString();
2549 }
2550
2551 caseString += QStringLiteral( "WHEN @vector_tile_zoom > %1 AND @vector_tile_zoom <= %2 "
2552 "THEN %3 " ).arg( bz.toString(),
2553 tz.toString(),
2554 QgsExpression::quotedValue( conversionMap.value( bv, bv ) ) );
2555 }
2556 caseString += QStringLiteral( "ELSE %1 END" ).arg( QgsExpression::quotedValue( conversionMap.value( stops.constLast().toList().value( 1 ).toString(),
2557 stops.constLast().toList().value( 1 ) ) ) );
2558 if ( defaultString )
2559 *defaultString = stops.constLast().toList().value( 1 ).toString();
2560 return caseString;
2561}
2562
2564{
2565 QString caseString = QStringLiteral( "CASE " );
2566
2567 bool isExpression = false;
2568 for ( int i = 0; i < stops.length() - 1; ++i )
2569 {
2570 // bottom zoom and value
2571 const QVariant bz = stops.value( i ).toList().value( 0 );
2572 if ( bz.type() == QVariant::List || bz.type() == QVariant::StringList )
2573 {
2574 context.pushWarning( QObject::tr( "%1: Lists in label interpolation function are not supported, skipping." ).arg( context.layerId() ) );
2575 return QString();
2576 }
2577
2578 // top zoom
2579 const QVariant tz = stops.value( i + 1 ).toList().value( 0 );
2580 if ( tz.type() == QVariant::List || tz.type() == QVariant::StringList )
2581 {
2582 context.pushWarning( QObject::tr( "%1: Lists in label interpolation function are not supported, skipping." ).arg( context.layerId() ) );
2583 return QString();
2584 }
2585
2586 QString fieldPart = processLabelField( stops.constLast().toList().value( 1 ).toString(), isExpression );
2587 if ( fieldPart.isEmpty() )
2588 fieldPart = QStringLiteral( "''" );
2589 else if ( !isExpression )
2590 fieldPart = QgsExpression::quotedColumnRef( fieldPart );
2591
2592 caseString += QStringLiteral( "WHEN @vector_tile_zoom > %1 AND @vector_tile_zoom < %2 "
2593 "THEN %3 " ).arg( bz.toString(),
2594 tz.toString(),
2595 fieldPart ) ;
2596 }
2597
2598 {
2599 const QVariant bz = stops.constLast().toList().value( 0 );
2600 if ( bz.type() == QVariant::List || bz.type() == QVariant::StringList )
2601 {
2602 context.pushWarning( QObject::tr( "%1: Lists in label interpolation function are not supported, skipping." ).arg( context.layerId() ) );
2603 return QString();
2604 }
2605
2606 QString fieldPart = processLabelField( stops.constLast().toList().value( 1 ).toString(), isExpression );
2607 if ( fieldPart.isEmpty() )
2608 fieldPart = QStringLiteral( "''" );
2609 else if ( !isExpression )
2610 fieldPart = QgsExpression::quotedColumnRef( fieldPart );
2611
2612 caseString += QStringLiteral( "WHEN @vector_tile_zoom >= %1 "
2613 "THEN %3 " ).arg( bz.toString(),
2614 fieldPart ) ;
2615 }
2616
2617 QString defaultPart = processLabelField( stops.constFirst().toList().value( 1 ).toString(), isExpression );
2618 if ( defaultPart.isEmpty() )
2619 defaultPart = QStringLiteral( "''" );
2620 else if ( !isExpression )
2621 defaultPart = QgsExpression::quotedColumnRef( defaultPart );
2622 caseString += QStringLiteral( "ELSE %1 END" ).arg( defaultPart );
2623
2624 return caseString;
2625}
2626
2627QgsProperty QgsMapBoxGlStyleConverter::parseValueList( const QVariantList &json, QgsMapBoxGlStyleConverter::PropertyType type, QgsMapBoxGlStyleConversionContext &context, double multiplier, int maxOpacity, QColor *defaultColor, double *defaultNumber )
2628{
2629 const QString method = json.value( 0 ).toString();
2630 if ( method == QLatin1String( "interpolate" ) )
2631 {
2632 return parseInterpolateListByZoom( json, type, context, multiplier, maxOpacity, defaultColor, defaultNumber );
2633 }
2634 else if ( method == QLatin1String( "match" ) )
2635 {
2636 return parseMatchList( json, type, context, multiplier, maxOpacity, defaultColor, defaultNumber );
2637 }
2638 else
2639 {
2640 return QgsProperty::fromExpression( parseExpression( json, context ) );
2641 }
2642}
2643
2644QgsProperty QgsMapBoxGlStyleConverter::parseMatchList( const QVariantList &json, QgsMapBoxGlStyleConverter::PropertyType type, QgsMapBoxGlStyleConversionContext &context, double multiplier, int maxOpacity, QColor *defaultColor, double *defaultNumber )
2645{
2646 const QString attribute = parseExpression( json.value( 1 ).toList(), context );
2647 if ( attribute.isEmpty() )
2648 {
2649 context.pushWarning( QObject::tr( "%1: Could not interpret match list" ).arg( context.layerId() ) );
2650 return QgsProperty();
2651 }
2652
2653 QString caseString = QStringLiteral( "CASE " );
2654
2655 for ( int i = 2; i < json.length() - 1; i += 2 )
2656 {
2657 const QVariantList keys = json.value( i ).toList();
2658
2659 QStringList matchString;
2660 for ( const QVariant &key : keys )
2661 {
2662 matchString << QgsExpression::quotedValue( key );
2663 }
2664
2665 const QVariant value = json.value( i + 1 );
2666
2667 QString valueString;
2668 switch ( type )
2669 {
2671 {
2672 const QColor color = parseColor( value, context );
2673 valueString = QgsExpression::quotedString( color.name() );
2674 break;
2675 }
2676
2678 {
2679 const double v = value.toDouble() * multiplier;
2680 valueString = QString::number( v );
2681 break;
2682 }
2683
2685 {
2686 const double v = value.toDouble() * maxOpacity;
2687 valueString = QString::number( v );
2688 break;
2689 }
2690
2692 {
2693 valueString = QStringLiteral( "array(%1,%2)" ).arg( value.toList().value( 0 ).toDouble() * multiplier,
2694 value.toList().value( 0 ).toDouble() * multiplier );
2695 break;
2696 }
2697
2698 }
2699
2700 caseString += QStringLiteral( "WHEN %1 IN (%2) THEN %3 " ).arg( attribute,
2701 matchString.join( ',' ), valueString );
2702 }
2703
2704
2705 QString elseValue;
2706 switch ( type )
2707 {
2709 {
2710 const QColor color = parseColor( json.constLast(), context );
2711 if ( defaultColor )
2712 *defaultColor = color;
2713
2714 elseValue = QgsExpression::quotedString( color.name() );
2715 break;
2716 }
2717
2719 {
2720 const double v = json.constLast().toDouble() * multiplier;
2721 if ( defaultNumber )
2722 *defaultNumber = v;
2723 elseValue = QString::number( v );
2724 break;
2725 }
2726
2728 {
2729 const double v = json.constLast().toDouble() * maxOpacity;
2730 if ( defaultNumber )
2731 *defaultNumber = v;
2732 elseValue = QString::number( v );
2733 break;
2734 }
2735
2737 {
2738 elseValue = QStringLiteral( "array(%1,%2)" ).arg( json.constLast().toList().value( 0 ).toDouble() * multiplier,
2739 json.constLast().toList().value( 0 ).toDouble() * multiplier );
2740 break;
2741 }
2742
2743 }
2744
2745 caseString += QStringLiteral( "ELSE %1 END" ).arg( elseValue );
2746 return QgsProperty::fromExpression( caseString );
2747}
2748
2749QgsProperty QgsMapBoxGlStyleConverter::parseInterpolateListByZoom( const QVariantList &json, PropertyType type, QgsMapBoxGlStyleConversionContext &context, double multiplier, int maxOpacity, QColor *defaultColor, double *defaultNumber )
2750{
2751 if ( json.value( 0 ).toString() != QLatin1String( "interpolate" ) )
2752 {
2753 context.pushWarning( QObject::tr( "%1: Could not interpret value list" ).arg( context.layerId() ) );
2754 return QgsProperty();
2755 }
2756
2757 double base = 1;
2758 const QString technique = json.value( 1 ).toList().value( 0 ).toString();
2759 if ( technique == QLatin1String( "linear" ) )
2760 base = 1;
2761 else if ( technique == QLatin1String( "exponential" ) )
2762 base = json.value( 1 ).toList(). value( 1 ).toDouble();
2763 else if ( technique == QLatin1String( "cubic-bezier" ) )
2764 {
2765 context.pushWarning( QObject::tr( "%1: Cubic-bezier interpolation is not supported, linear used instead." ).arg( context.layerId() ) );
2766 base = 1;
2767 }
2768 else
2769 {
2770 context.pushWarning( QObject::tr( "%1: Skipping not implemented interpolation method %2" ).arg( context.layerId(), technique ) );
2771 return QgsProperty();
2772 }
2773
2774 if ( json.value( 2 ).toList().value( 0 ).toString() != QLatin1String( "zoom" ) )
2775 {
2776 context.pushWarning( QObject::tr( "%1: Skipping not implemented interpolation input %2" ).arg( context.layerId(), json.value( 2 ).toString() ) );
2777 return QgsProperty();
2778 }
2779
2780 // Convert stops into list of lists
2781 QVariantList stops;
2782 for ( int i = 3; i < json.length(); i += 2 )
2783 {
2784 stops.push_back( QVariantList() << json.value( i ).toString() << json.value( i + 1 ) );
2785 }
2786
2787 QVariantMap props;
2788 props.insert( QStringLiteral( "stops" ), stops );
2789 props.insert( QStringLiteral( "base" ), base );
2790 switch ( type )
2791 {
2793 return parseInterpolateColorByZoom( props, context, defaultColor );
2794
2796 return parseInterpolateByZoom( props, context, multiplier, defaultNumber );
2797
2799 return parseInterpolateOpacityByZoom( props, maxOpacity, &context );
2800
2802 return parseInterpolatePointByZoom( props, context, multiplier );
2803 }
2804 return QgsProperty();
2805}
2806
2808{
2809 if ( ( QMetaType::Type )colorExpression.type() == QMetaType::QVariantList )
2810 {
2811 return parseExpression( colorExpression.toList(), context, true );
2812 }
2813 return parseValue( colorExpression, context, true );
2814}
2815
2817{
2818 if ( color.type() != QVariant::String )
2819 {
2820 context.pushWarning( QObject::tr( "%1: Could not parse non-string color %2, skipping" ).arg( context.layerId(), color.toString() ) );
2821 return QColor();
2822 }
2823
2824 return QgsSymbolLayerUtils::parseColor( color.toString() );
2825}
2826
2827void QgsMapBoxGlStyleConverter::colorAsHslaComponents( const QColor &color, int &hue, int &saturation, int &lightness, int &alpha )
2828{
2829 hue = std::max( 0, color.hslHue() );
2830 saturation = color.hslSaturation() / 255.0 * 100;
2831 lightness = color.lightness() / 255.0 * 100;
2832 alpha = color.alpha();
2833}
2834
2835QString QgsMapBoxGlStyleConverter::interpolateExpression( double zoomMin, double zoomMax, QVariant valueMin, QVariant valueMax, double base, double multiplier, QgsMapBoxGlStyleConversionContext *contextPtr )
2836{
2838 if ( contextPtr )
2839 {
2840 context = *contextPtr;
2841 }
2842
2843 // special case!
2844 if ( valueMin.canConvert( QMetaType::Double ) && valueMax.canConvert( QMetaType::Double ) )
2845 {
2846 bool minDoubleOk = true;
2847 const double min = valueMin.toDouble( &minDoubleOk );
2848 bool maxDoubleOk = true;
2849 const double max = valueMax.toDouble( &maxDoubleOk );
2850 if ( minDoubleOk && maxDoubleOk && qgsDoubleNear( min, max ) )
2851 {
2852 return QString::number( min * multiplier );
2853 }
2854 }
2855
2856 QString minValueExpr = valueMin.toString();
2857 QString maxValueExpr = valueMax.toString();
2858 if ( ( QMetaType::Type )valueMin.type() == QMetaType::QVariantList )
2859 {
2860 minValueExpr = parseExpression( valueMin.toList(), context );
2861 }
2862 if ( ( QMetaType::Type )valueMax.type() == QMetaType::QVariantList )
2863 {
2864 maxValueExpr = parseExpression( valueMax.toList(), context );
2865 }
2866
2867 if ( minValueExpr == maxValueExpr )
2868 {
2869 return minValueExpr;
2870 }
2871
2872 QString expression;
2873 if ( base == 1 )
2874 {
2875 expression = QStringLiteral( "scale_linear(@vector_tile_zoom,%1,%2,%3,%4)" ).arg( zoomMin )
2876 .arg( zoomMax )
2877 .arg( minValueExpr )
2878 .arg( maxValueExpr );
2879 }
2880 else
2881 {
2882 expression = QStringLiteral( "scale_exp(@vector_tile_zoom,%1,%2,%3,%4,%5)" ).arg( zoomMin )
2883 .arg( zoomMax )
2884 .arg( minValueExpr )
2885 .arg( maxValueExpr )
2886 .arg( base );
2887 }
2888
2889 if ( multiplier != 1 )
2890 return QStringLiteral( "%1 * %2" ).arg( expression ).arg( multiplier );
2891 else
2892 return expression;
2893}
2894
2895Qt::PenCapStyle QgsMapBoxGlStyleConverter::parseCapStyle( const QString &style )
2896{
2897 if ( style == QLatin1String( "round" ) )
2898 return Qt::RoundCap;
2899 else if ( style == QLatin1String( "square" ) )
2900 return Qt::SquareCap;
2901 else
2902 return Qt::FlatCap; // "butt" is default
2903}
2904
2905Qt::PenJoinStyle QgsMapBoxGlStyleConverter::parseJoinStyle( const QString &style )
2906{
2907 if ( style == QLatin1String( "bevel" ) )
2908 return Qt::BevelJoin;
2909 else if ( style == QLatin1String( "round" ) )
2910 return Qt::RoundJoin;
2911 else
2912 return Qt::MiterJoin; // "miter" is default
2913}
2914
2915QString QgsMapBoxGlStyleConverter::parseExpression( const QVariantList &expression, QgsMapBoxGlStyleConversionContext &context, bool colorExpected )
2916{
2917 QString op = expression.value( 0 ).toString();
2918 if ( op == QLatin1String( "%" ) && expression.size() >= 3 )
2919 {
2920 return QStringLiteral( "%1 %2 %3" ).arg( parseValue( expression.value( 1 ), context ) ).arg( op ).arg( parseValue( expression.value( 2 ), context ) );
2921 }
2922 else if ( op == QLatin1String( "to-number" ) )
2923 {
2924 return QStringLiteral( "to_real(%1)" ).arg( parseValue( expression.value( 1 ), context ) );
2925 }
2926 if ( op == QLatin1String( "literal" ) )
2927 {
2928 return expression.value( 1 ).toString();
2929 }
2930 else if ( op == QLatin1String( "all" )
2931 || op == QLatin1String( "any" )
2932 || op == QLatin1String( "none" ) )
2933 {
2934 QStringList parts;
2935 for ( int i = 1; i < expression.size(); ++i )
2936 {
2937 const QString part = parseValue( expression.at( i ), context );
2938 if ( part.isEmpty() )
2939 {
2940 context.pushWarning( QObject::tr( "%1: Skipping unsupported expression" ).arg( context.layerId() ) );
2941 return QString();
2942 }
2943 parts << part;
2944 }
2945
2946 if ( op == QLatin1String( "none" ) )
2947 return QStringLiteral( "NOT (%1)" ).arg( parts.join( QLatin1String( ") AND NOT (" ) ) );
2948
2949 QString operatorString;
2950 if ( op == QLatin1String( "all" ) )
2951 operatorString = QStringLiteral( ") AND (" );
2952 else if ( op == QLatin1String( "any" ) )
2953 operatorString = QStringLiteral( ") OR (" );
2954
2955 return QStringLiteral( "(%1)" ).arg( parts.join( operatorString ) );
2956 }
2957 else if ( op == '!' )
2958 {
2959 // ! inverts next expression's meaning
2960 QVariantList contraJsonExpr = expression.value( 1 ).toList();
2961 contraJsonExpr[0] = QString( op + contraJsonExpr[0].toString() );
2962 // ['!', ['has', 'level']] -> ['!has', 'level']
2963 return parseKey( contraJsonExpr, context );
2964 }
2965 else if ( op == QLatin1String( "==" )
2966 || op == QLatin1String( "!=" )
2967 || op == QLatin1String( ">=" )
2968 || op == '>'
2969 || op == QLatin1String( "<=" )
2970 || op == '<' )
2971 {
2972 // use IS and NOT IS instead of = and != because they can deal with NULL values
2973 if ( op == QLatin1String( "==" ) )
2974 op = QStringLiteral( "IS" );
2975 else if ( op == QLatin1String( "!=" ) )
2976 op = QStringLiteral( "IS NOT" );
2977 return QStringLiteral( "%1 %2 %3" ).arg( parseKey( expression.value( 1 ), context ),
2978 op, parseValue( expression.value( 2 ), context ) );
2979 }
2980 else if ( op == QLatin1String( "has" ) )
2981 {
2982 return parseKey( expression.value( 1 ), context ) + QStringLiteral( " IS NOT NULL" );
2983 }
2984 else if ( op == QLatin1String( "!has" ) )
2985 {
2986 return parseKey( expression.value( 1 ), context ) + QStringLiteral( " IS NULL" );
2987 }
2988 else if ( op == QLatin1String( "in" ) || op == QLatin1String( "!in" ) )
2989 {
2990 const QString key = parseKey( expression.value( 1 ), context );
2991 QStringList parts;
2992 for ( int i = 2; i < expression.size(); ++i )
2993 {
2994 const QString part = parseValue( expression.at( i ), context );
2995 if ( part.isEmpty() )
2996 {
2997 context.pushWarning( QObject::tr( "%1: Skipping unsupported expression" ).arg( context.layerId() ) );
2998 return QString();
2999 }
3000 parts << part;
3001 }
3002 if ( op == QLatin1String( "in" ) )
3003 return QStringLiteral( "%1 IN (%2)" ).arg( key, parts.join( QLatin1String( ", " ) ) );
3004 else
3005 return QStringLiteral( "(%1 IS NULL OR %1 NOT IN (%2))" ).arg( key, parts.join( QLatin1String( ", " ) ) );
3006 }
3007 else if ( op == QLatin1String( "get" ) )
3008 {
3009 return parseKey( expression.value( 1 ), context );
3010 }
3011 else if ( op == QLatin1String( "match" ) )
3012 {
3013 const QString attribute = expression.value( 1 ).toList().value( 1 ).toString();
3014
3015 if ( expression.size() == 5
3016 && expression.at( 3 ).type() == QVariant::Bool && expression.at( 3 ).toBool() == true
3017 && expression.at( 4 ).type() == QVariant::Bool && expression.at( 4 ).toBool() == false )
3018 {
3019 // simple case, make a nice simple expression instead of a CASE statement
3020 if ( expression.at( 2 ).type() == QVariant::List || expression.at( 2 ).type() == QVariant::StringList )
3021 {
3022 QStringList parts;
3023 for ( const QVariant &p : expression.at( 2 ).toList() )
3024 {
3025 parts << parseValue( p, context );
3026 }
3027
3028 if ( parts.size() > 1 )
3029 return QStringLiteral( "%1 IN (%2)" ).arg( QgsExpression::quotedColumnRef( attribute ), parts.join( ", " ) );
3030 else
3031 return QgsExpression::createFieldEqualityExpression( attribute, expression.at( 2 ).toList().value( 0 ) );
3032 }
3033 else if ( expression.at( 2 ).type() == QVariant::String || expression.at( 2 ).type() == QVariant::Int
3034 || expression.at( 2 ).type() == QVariant::Double || expression.at( 2 ).type() == QVariant::LongLong )
3035 {
3036 return QgsExpression::createFieldEqualityExpression( attribute, expression.at( 2 ) );
3037 }
3038 else
3039 {
3040 context.pushWarning( QObject::tr( "%1: Skipping unsupported expression" ).arg( context.layerId() ) );
3041 return QString();
3042 }
3043 }
3044 else
3045 {
3046 QString caseString = QStringLiteral( "CASE " );
3047 for ( int i = 2; i < expression.size() - 2; i += 2 )
3048 {
3049 if ( expression.at( i ).type() == QVariant::List || expression.at( i ).type() == QVariant::StringList )
3050 {
3051 QStringList parts;
3052 for ( const QVariant &p : expression.at( i ).toList() )
3053 {
3054 parts << QgsExpression::quotedValue( p );
3055 }
3056
3057 if ( parts.size() > 1 )
3058 caseString += QStringLiteral( "WHEN %1 IN (%2) " ).arg( QgsExpression::quotedColumnRef( attribute ), parts.join( ", " ) );
3059 else
3060 caseString += QStringLiteral( "WHEN %1 " ).arg( QgsExpression::createFieldEqualityExpression( attribute, expression.at( i ).toList().value( 0 ) ) );
3061 }
3062 else if ( expression.at( i ).type() == QVariant::String || expression.at( i ).type() == QVariant::Int
3063 || expression.at( i ).type() == QVariant::Double || expression.at( i ).type() == QVariant::LongLong )
3064 {
3065 caseString += QStringLiteral( "WHEN (%1) " ).arg( QgsExpression::createFieldEqualityExpression( attribute, expression.at( i ) ) );
3066 }
3067
3068 caseString += QStringLiteral( "THEN %1 " ).arg( parseValue( expression.at( i + 1 ), context, colorExpected ) );
3069 }
3070 caseString += QStringLiteral( "ELSE %1 END" ).arg( parseValue( expression.last(), context, colorExpected ) );
3071 return caseString;
3072 }
3073 }
3074 else if ( op == QLatin1String( "to-string" ) )
3075 {
3076 return QStringLiteral( "to_string(%1)" ).arg( parseExpression( expression.value( 1 ).toList(), context ) );
3077 }
3078 else
3079 {
3080 context.pushWarning( QObject::tr( "%1: Skipping unsupported expression" ).arg( context.layerId() ) );
3081 return QString();
3082 }
3083}
3084
3085QImage QgsMapBoxGlStyleConverter::retrieveSprite( const QString &name, QgsMapBoxGlStyleConversionContext &context, QSize &spriteSize )
3086{
3087 if ( context.spriteImage().isNull() )
3088 {
3089 context.pushWarning( QObject::tr( "%1: Could not retrieve sprite '%2'" ).arg( context.layerId(), name ) );
3090 return QImage();
3091 }
3092
3093 const QVariantMap spriteDefinition = context.spriteDefinitions().value( name ).toMap();
3094 if ( spriteDefinition.size() == 0 )
3095 {
3096 context.pushWarning( QObject::tr( "%1: Could not retrieve sprite '%2'" ).arg( context.layerId(), name ) );
3097 return QImage();
3098 }
3099
3100 const QImage sprite = context.spriteImage().copy( spriteDefinition.value( QStringLiteral( "x" ) ).toInt(),
3101 spriteDefinition.value( QStringLiteral( "y" ) ).toInt(),
3102 spriteDefinition.value( QStringLiteral( "width" ) ).toInt(),
3103 spriteDefinition.value( QStringLiteral( "height" ) ).toInt() );
3104 if ( sprite.isNull() )
3105 {
3106 context.pushWarning( QObject::tr( "%1: Could not retrieve sprite '%2'" ).arg( context.layerId(), name ) );
3107 return QImage();
3108 }
3109
3110 spriteSize = sprite.size() / spriteDefinition.value( QStringLiteral( "pixelRatio" ) ).toDouble() * context.pixelSizeConversionFactor();
3111 return sprite;
3112}
3113
3114QString QgsMapBoxGlStyleConverter::retrieveSpriteAsBase64( const QVariant &value, QgsMapBoxGlStyleConversionContext &context, QSize &spriteSize, QString &spriteProperty, QString &spriteSizeProperty )
3115{
3116 QString spritePath;
3117
3118 auto prepareBase64 = []( const QImage & sprite )
3119 {
3120 QString path;
3121 if ( !sprite.isNull() )
3122 {
3123 QByteArray blob;
3124 QBuffer buffer( &blob );
3125 buffer.open( QIODevice::WriteOnly );
3126 sprite.save( &buffer, "PNG" );
3127 buffer.close();
3128 const QByteArray encoded = blob.toBase64();
3129 path = QString( encoded );
3130 path.prepend( QLatin1String( "base64:" ) );
3131 }
3132 return path;
3133 };
3134
3135 switch ( value.type() )
3136 {
3137 case QVariant::String:
3138 {
3139 QString spriteName = value.toString();
3140 const QRegularExpression fieldNameMatch( QStringLiteral( "{([^}]+)}" ) );
3141 QRegularExpressionMatch match = fieldNameMatch.match( spriteName );
3142 if ( match.hasMatch() )
3143 {
3144 const QString fieldName = match.captured( 1 );
3145 spriteProperty = QStringLiteral( "CASE" );
3146 spriteSizeProperty = QStringLiteral( "CASE" );
3147
3148 spriteName.replace( "(", QLatin1String( "\\(" ) );
3149 spriteName.replace( ")", QLatin1String( "\\)" ) );
3150 spriteName.replace( fieldNameMatch, QStringLiteral( "([^\\/\\\\]+)" ) );
3151 const QRegularExpression fieldValueMatch( spriteName );
3152 const QStringList spriteNames = context.spriteDefinitions().keys();
3153 for ( const QString &name : spriteNames )
3154 {
3155 match = fieldValueMatch.match( name );
3156 if ( match.hasMatch() )
3157 {
3158 QSize size;
3159 QString path;
3160 const QString fieldValue = match.captured( 1 );
3161 const QImage sprite = retrieveSprite( name, context, size );
3162 path = prepareBase64( sprite );
3163 if ( spritePath.isEmpty() && !path.isEmpty() )
3164 {
3165 spritePath = path;
3166 spriteSize = size;
3167 }
3168
3169 spriteProperty += QStringLiteral( " WHEN \"%1\" = '%2' THEN '%3'" )
3170 .arg( fieldName, fieldValue, path );
3171 spriteSizeProperty += QStringLiteral( " WHEN \"%1\" = '%2' THEN %3" )
3172 .arg( fieldName ).arg( fieldValue ).arg( size.width() );
3173 }
3174 }
3175
3176 spriteProperty += QLatin1String( " END" );
3177 spriteSizeProperty += QLatin1String( " END" );
3178 }
3179 else
3180 {
3181 spriteProperty.clear();
3182 spriteSizeProperty.clear();
3183 const QImage sprite = retrieveSprite( spriteName, context, spriteSize );
3184 spritePath = prepareBase64( sprite );
3185 }
3186 break;
3187 }
3188
3189 case QVariant::Map:
3190 {
3191 const QVariantList stops = value.toMap().value( QStringLiteral( "stops" ) ).toList();
3192 if ( stops.size() == 0 )
3193 break;
3194
3195 QString path;
3196 QSize size;
3197 QImage sprite;
3198
3199 sprite = retrieveSprite( stops.value( 0 ).toList().value( 1 ).toString(), context, spriteSize );
3200 spritePath = prepareBase64( sprite );
3201
3202 spriteProperty = QStringLiteral( "CASE WHEN @vector_tile_zoom < %1 THEN '%2'" )
3203 .arg( stops.value( 0 ).toList().value( 0 ).toString() )
3204 .arg( spritePath );
3205 spriteSizeProperty = QStringLiteral( "CASE WHEN @vector_tile_zoom < %1 THEN %2" )
3206 .arg( stops.value( 0 ).toList().value( 0 ).toString() )
3207 .arg( spriteSize.width() );
3208
3209 for ( int i = 0; i < stops.size() - 1; ++i )
3210 {
3211 ;
3212 sprite = retrieveSprite( stops.value( 0 ).toList().value( 1 ).toString(), context, size );
3213 path = prepareBase64( sprite );
3214
3215 spriteProperty += QStringLiteral( " WHEN @vector_tile_zoom >= %1 AND @vector_tile_zoom < %2 "
3216 "THEN '%3'" )
3217 .arg( stops.value( i ).toList().value( 0 ).toString(),
3218 stops.value( i + 1 ).toList().value( 0 ).toString(),
3219 path );
3220 spriteSizeProperty += QStringLiteral( " WHEN @vector_tile_zoom >= %1 AND @vector_tile_zoom < %2 "
3221 "THEN %3" )
3222 .arg( stops.value( i ).toList().value( 0 ).toString(),
3223 stops.value( i + 1 ).toList().value( 0 ).toString() )
3224 .arg( size.width() );
3225 }
3226 sprite = retrieveSprite( stops.last().toList().value( 1 ).toString(), context, size );
3227 path = prepareBase64( sprite );
3228
3229 spriteProperty += QStringLiteral( " WHEN @vector_tile_zoom >= %1 "
3230 "THEN '%2' END" )
3231 .arg( stops.last().toList().value( 0 ).toString() )
3232 .arg( path );
3233 spriteSizeProperty += QStringLiteral( " WHEN @vector_tile_zoom >= %1 "
3234 "THEN %2 END" )
3235 .arg( stops.last().toList().value( 0 ).toString() )
3236 .arg( size.width() );
3237 break;
3238 }
3239
3240 case QVariant::List:
3241 {
3242 const QVariantList json = value.toList();
3243 const QString method = json.value( 0 ).toString();
3244 if ( method != QLatin1String( "match" ) )
3245 {
3246 context.pushWarning( QObject::tr( "%1: Could not interpret sprite value list with method %2" ).arg( context.layerId(), method ) );
3247 break;
3248 }
3249
3250 const QString attribute = parseExpression( json.value( 1 ).toList(), context );
3251 if ( attribute.isEmpty() )
3252 {
3253 context.pushWarning( QObject::tr( "%1: Could not interpret match list" ).arg( context.layerId() ) );
3254 break;
3255 }
3256
3257 spriteProperty = QStringLiteral( "CASE " );
3258 spriteSizeProperty = QStringLiteral( "CASE " );
3259
3260 for ( int i = 2; i < json.length() - 1; i += 2 )
3261 {
3262 const QVariantList keys = json.value( i ).toList();
3263
3264 QStringList matchString;
3265 for ( const QVariant &key : keys )
3266 {
3267 matchString << QgsExpression::quotedValue( key );
3268 }
3269
3270 const QVariant value = json.value( i + 1 );
3271
3272 const QImage sprite = retrieveSprite( value.toString(), context, spriteSize );
3273 spritePath = prepareBase64( sprite );
3274
3275 spriteProperty += QStringLiteral( " WHEN %1 IN (%2) "
3276 "THEN '%3' " ).arg( attribute,
3277 matchString.join( ',' ),
3278 spritePath );
3279
3280 spriteSizeProperty += QStringLiteral( " WHEN %1 IN (%2) "
3281 "THEN %3 " ).arg( attribute,
3282 matchString.join( ',' ) ).arg( spriteSize.width() );
3283 }
3284
3285 const QImage sprite = retrieveSprite( json.constLast().toString(), context, spriteSize );
3286 spritePath = prepareBase64( sprite );
3287
3288 spriteProperty += QStringLiteral( "ELSE %1 END" ).arg( spritePath );
3289 spriteSizeProperty += QStringLiteral( "ELSE %3 END" ).arg( spriteSize.width() );
3290 break;
3291 }
3292
3293 default:
3294 context.pushWarning( QObject::tr( "%1: Skipping unsupported sprite type (%2)." ).arg( context.layerId(), QMetaType::typeName( value.type() ) ) );
3295 break;
3296 }
3297
3298 return spritePath;
3299}
3300
3301QString QgsMapBoxGlStyleConverter::parseValue( const QVariant &value, QgsMapBoxGlStyleConversionContext &context, bool colorExpected )
3302{
3303 QColor c;
3304 switch ( value.type() )
3305 {
3306 case QVariant::List:
3307 case QVariant::StringList:
3308 return parseExpression( value.toList(), context, colorExpected );
3309
3310 case QVariant::Bool:
3311 case QVariant::String:
3312 if ( colorExpected )
3313 {
3314 QColor c = parseColor( value, context );
3315 if ( c.isValid() )
3316 {
3317 return parseValue( c, context );
3318 }
3319 }
3320 return QgsExpression::quotedValue( value );
3321
3322 case QVariant::Int:
3323 case QVariant::LongLong:
3324 case QVariant::Double:
3325 return value.toString();
3326
3327 case QVariant::Color:
3328 c = value.value<QColor>();
3329 return QString( "color_rgba(%1,%2,%3,%4)" ).arg( c.red() ).arg( c.green() ).arg( c.blue() ).arg( c.alpha() );
3330
3331 default:
3332 context.pushWarning( QObject::tr( "%1: Skipping unsupported expression part" ).arg( context.layerId() ) );
3333 break;
3334 }
3335 return QString();
3336}
3337
3338QString QgsMapBoxGlStyleConverter::parseKey( const QVariant &value, QgsMapBoxGlStyleConversionContext &context )
3339{
3340 if ( value.toString() == QLatin1String( "$type" ) )
3341 {
3342 return QStringLiteral( "_geom_type" );
3343 }
3344 if ( value.toString() == QLatin1String( "level" ) )
3345 {
3346 return QStringLiteral( "level" );
3347 }
3348 else if ( ( value.type() == QVariant::List && value.toList().size() == 1 ) || value.type() == QVariant::StringList )
3349 {
3350 if ( value.toList().size() > 1 )
3351 return value.toList().at( 1 ).toString();
3352 else
3353 {
3354 QString valueString = value.toList().value( 0 ).toString();
3355 if ( valueString == QLatin1String( "geometry-type" ) )
3356 {
3357 return QStringLiteral( "_geom_type" );
3358 }
3359 return valueString;
3360 }
3361 }
3362 else if ( value.type() == QVariant::List && value.toList().size() > 1 )
3363 {
3364 return parseExpression( value.toList(), context );
3365 }
3366 return QgsExpression::quotedColumnRef( value.toString() );
3367}
3368
3369QString QgsMapBoxGlStyleConverter::processLabelField( const QString &string, bool &isExpression )
3370{
3371 // {field_name} is permitted in string -- if multiple fields are present, convert them to an expression
3372 // but if single field is covered in {}, return it directly
3373 const QRegularExpression singleFieldRx( QStringLiteral( "^{([^}]+)}$" ) );
3374 const QRegularExpressionMatch match = singleFieldRx.match( string );
3375 if ( match.hasMatch() )
3376 {
3377 isExpression = false;
3378 return match.captured( 1 );
3379 }
3380
3381 const QRegularExpression multiFieldRx( QStringLiteral( "(?={[^}]+})" ) );
3382 const QStringList parts = string.split( multiFieldRx );
3383 if ( parts.size() > 1 )
3384 {
3385 isExpression = true;
3386
3387 QStringList res;
3388 for ( const QString &part : parts )
3389 {
3390 if ( part.isEmpty() )
3391 continue;
3392
3393 if ( !part.contains( '{' ) )
3394 {
3395 res << QgsExpression::quotedValue( part );
3396 continue;
3397 }
3398
3399 // part will start at a {field} reference
3400 const QStringList split = part.split( '}' );
3401 res << QgsExpression::quotedColumnRef( split.at( 0 ).mid( 1 ) );
3402 if ( !split.at( 1 ).isEmpty() )
3403 res << QgsExpression::quotedValue( split.at( 1 ) );
3404 }
3405 return QStringLiteral( "concat(%1)" ).arg( res.join( ',' ) );
3406 }
3407 else
3408 {
3409 isExpression = false;
3410 return string;
3411 }
3412}
3413
3415{
3416 return mRenderer ? mRenderer->clone() : nullptr;
3417}
3418
3420{
3421 return mLabeling ? mLabeling->clone() : nullptr;
3422}
3423
3424QList<QgsMapBoxGlStyleAbstractSource *> QgsMapBoxGlStyleConverter::sources()
3425{
3426 return mSources;
3427}
3428
3429QList<QgsMapBoxGlStyleRasterSubLayer> QgsMapBoxGlStyleConverter::rasterSubLayers() const
3430{
3431 return mRasterSubLayers;
3432}
3433
3435{
3436 QList<QgsMapLayer *> subLayers;
3437 for ( const QgsMapBoxGlStyleRasterSubLayer &subLayer : mRasterSubLayers )
3438 {
3439 const QString sourceName = subLayer.source();
3440 std::unique_ptr< QgsRasterLayer > rl;
3441 for ( const QgsMapBoxGlStyleAbstractSource *source : mSources )
3442 {
3443 if ( source->type() == Qgis::MapBoxGlStyleSourceType::Raster && source->name() == sourceName )
3444 {
3445 const QgsMapBoxGlStyleRasterSource *rasterSource = qgis::down_cast< const QgsMapBoxGlStyleRasterSource * >( source );
3446 rl.reset( rasterSource->toRasterLayer() );
3447 rl->pipe()->setDataDefinedProperties( subLayer.dataDefinedProperties() );
3448 break;
3449 }
3450 }
3451
3452 if ( rl )
3453 {
3454 subLayers.append( rl.release() );
3455 }
3456 }
3457 return subLayers;
3458}
3459
3460
3462{
3463 std::unique_ptr< QgsMapBoxGlStyleConversionContext > tmpContext;
3464 if ( !context )
3465 {
3466 tmpContext = std::make_unique< QgsMapBoxGlStyleConversionContext >();
3467 context = tmpContext.get();
3468 }
3469
3470 auto typeFromString = [context]( const QString & string, const QString & name )->Qgis::MapBoxGlStyleSourceType
3471 {
3472 if ( string.compare( QLatin1String( "vector" ), Qt::CaseInsensitive ) == 0 )
3474 else if ( string.compare( QLatin1String( "raster" ), Qt::CaseInsensitive ) == 0 )
3476 else if ( string.compare( QLatin1String( "raster-dem" ), Qt::CaseInsensitive ) == 0 )
3478 else if ( string.compare( QLatin1String( "geojson" ), Qt::CaseInsensitive ) == 0 )
3480 else if ( string.compare( QLatin1String( "image" ), Qt::CaseInsensitive ) == 0 )
3482 else if ( string.compare( QLatin1String( "video" ), Qt::CaseInsensitive ) == 0 )
3484 context->pushWarning( QObject::tr( "Invalid source type \"%1\" for source \"%2\"" ).arg( string, name ) );
3486 };
3487
3488 for ( auto it = sources.begin(); it != sources.end(); ++it )
3489 {
3490 const QString name = it.key();
3491 const QVariantMap jsonSource = it.value().toMap();
3492 const QString typeString = jsonSource.value( QStringLiteral( "type" ) ).toString();
3493
3494 const Qgis::MapBoxGlStyleSourceType type = typeFromString( typeString, name );
3495
3496 switch ( type )
3497 {
3499 parseRasterSource( jsonSource, name, context );
3500 break;
3507 QgsDebugMsg( QStringLiteral( "Ignoring vector tile style source %1 (%2)" ).arg( name, qgsEnumValueToKey( type ) ) );
3508 continue;
3509 }
3510 }
3511}
3512
3513void QgsMapBoxGlStyleConverter::parseRasterSource( const QVariantMap &source, const QString &name, QgsMapBoxGlStyleConversionContext *context )
3514{
3515 std::unique_ptr< QgsMapBoxGlStyleConversionContext > tmpContext;
3516 if ( !context )
3517 {
3518 tmpContext = std::make_unique< QgsMapBoxGlStyleConversionContext >();
3519 context = tmpContext.get();
3520 }
3521
3522 std::unique_ptr< QgsMapBoxGlStyleRasterSource > raster = std::make_unique< QgsMapBoxGlStyleRasterSource >( name );
3523 if ( raster->setFromJson( source, context ) )
3524 mSources.append( raster.release() );
3525}
3526
3527bool QgsMapBoxGlStyleConverter::numericArgumentsOnly( const QVariant &bottomVariant, const QVariant &topVariant, double &bottom, double &top )
3528{
3529 if ( bottomVariant.canConvert( QMetaType::Double ) && topVariant.canConvert( QMetaType::Double ) )
3530 {
3531 bool bDoubleOk, tDoubleOk;
3532 bottom = bottomVariant.toDouble( &bDoubleOk );
3533 top = topVariant.toDouble( &tDoubleOk );
3534 return ( bDoubleOk && tDoubleOk );
3535 }
3536 return false;
3537}
3538
3539//
3540// QgsMapBoxGlStyleConversionContext
3541//
3543{
3544 QgsDebugMsg( warning );
3545 mWarnings << warning;
3546}
3547
3549{
3550 return mTargetUnit;
3551}
3552
3554{
3555 mTargetUnit = targetUnit;
3556}
3557
3559{
3560 return mSizeConversionFactor;
3561}
3562
3564{
3565 mSizeConversionFactor = sizeConversionFactor;
3566}
3567
3569{
3570 return mSpriteImage;
3571}
3572
3574{
3575 return mSpriteDefinitions;
3576}
3577
3578void QgsMapBoxGlStyleConversionContext::setSprites( const QImage &image, const QVariantMap &definitions )
3579{
3580 mSpriteImage = image;
3581 mSpriteDefinitions = definitions;
3582}
3583
3584void QgsMapBoxGlStyleConversionContext::setSprites( const QImage &image, const QString &definitions )
3585{
3586 setSprites( image, QgsJsonUtils::parseJson( definitions ).toMap() );
3587}
3588
3590{
3591 return mLayerId;
3592}
3593
3595{
3596 mLayerId = value;
3597}
3598
3599//
3600// QgsMapBoxGlStyleAbstractSource
3601//
3603 : mName( name )
3604{
3605}
3606
3608{
3609 return mName;
3610}
3611
3613
3614//
3615// QgsMapBoxGlStyleRasterSource
3616//
3617
3620{
3621
3622}
3623
3625{
3627}
3628
3630{
3631 mAttribution = json.value( QStringLiteral( "attribution" ) ).toString();
3632
3633 const QString scheme = json.value( QStringLiteral( "scheme" ), QStringLiteral( "xyz" ) ).toString();
3634 if ( scheme.compare( QLatin1String( "xyz" ) ) == 0 )
3635 {
3636 // xyz scheme is supported
3637 }
3638 else
3639 {
3640 context->pushWarning( QObject::tr( "%1 scheme is not supported for raster source %2" ).arg( scheme, name() ) );
3641 return false;
3642 }
3643
3644 mMinZoom = json.value( QStringLiteral( "minzoom" ), QStringLiteral( "0" ) ).toInt();
3645 mMaxZoom = json.value( QStringLiteral( "maxzoom" ), QStringLiteral( "22" ) ).toInt();
3646 mTileSize = json.value( QStringLiteral( "tileSize" ), QStringLiteral( "512" ) ).toInt();
3647
3648 const QVariantList tiles = json.value( QStringLiteral( "tiles" ) ).toList();
3649 for ( const QVariant &tile : tiles )
3650 {
3651 mTiles.append( tile.toString() );
3652 }
3653
3654 return true;
3655}
3656
3658{
3659 QVariantMap parts;
3660 parts.insert( QStringLiteral( "type" ), QStringLiteral( "xyz" ) );
3661 parts.insert( QStringLiteral( "url" ), mTiles.value( 0 ) );
3662
3663 if ( mTileSize == 256 )
3664 parts.insert( QStringLiteral( "tilePixelRation" ), QStringLiteral( "1" ) );
3665 else if ( mTileSize == 512 )
3666 parts.insert( QStringLiteral( "tilePixelRation" ), QStringLiteral( "2" ) );
3667
3668 parts.insert( QStringLiteral( "zmax" ), QString::number( mMaxZoom ) );
3669 parts.insert( QStringLiteral( "zmin" ), QString::number( mMinZoom ) );
3670
3671 std::unique_ptr< QgsRasterLayer > rl = std::make_unique< QgsRasterLayer >( QgsProviderRegistry::instance()->encodeUri( QStringLiteral( "wms" ), parts ), name(), QStringLiteral( "wms" ) );
3672 return rl.release();
3673}
3674
3675//
3676// QgsMapBoxGlStyleRasterSubLayer
3677//
3679 : mId( id )
3680 , mSource( source )
3681{
3682
3683}
@ CentralPoint
Place symbols at the mid point of the line.
@ OverPoint
Arranges candidates over a point (or centroid of a polygon), or at a preset offset from the point....
@ Curved
Arranges candidates following the curvature of a line feature. Applies to line layers only.
@ Horizontal
Arranges horizontal candidates scattered throughout a polygon feature. Applies to polygon layers only...
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
Definition: qgis.h:228
RenderUnit
Rendering size units.
Definition: qgis.h:3176
MapBoxGlStyleSourceType
Flags which control project capabilities.
Definition: qgis.h:2590
@ RasterDem
Raster DEM source.
@ Unknown
Other/unknown source type.
@ Viewport
Relative to the whole viewport/output device.
void setPenJoinStyle(Qt::PenJoinStyle style)
Sets the pen join style used to render the line (e.g.
void setPenCapStyle(Qt::PenCapStyle style)
Sets the pen cap style used to render the line (e.g.
static QgsFontManager * fontManager()
Returns the application font manager, which manages available fonts and font installation for the QGI...
A paint effect which blurs a source picture, using a number of different blur methods.
Definition: qgsblureffect.h:38
void setBlurUnit(const Qgis::RenderUnit unit)
Sets the units used for the blur level (radius).
Definition: qgsblureffect.h:96
@ StackBlur
Stack blur, a fast but low quality blur. Valid blur level values are between 0 - 16.
Definition: qgsblureffect.h:45
void setBlurMethod(const BlurMethod method)
Sets the blur method (algorithm) to use for performing the blur.
void setBlurLevel(const double level)
Sets blur level (radius)
Definition: qgsblureffect.h:75
A paint effect which consists of a stack of other chained paint effects.
void appendEffect(QgsPaintEffect *effect)
Appends an effect to the end of the stack.
static QString quotedValue(const QVariant &value)
Returns a string representation of a literal value, including appropriate quotations where required.
static QString quotedString(QString text)
Returns a quoted version of a string (in single quotes)
static QString createFieldEqualityExpression(const QString &fieldName, const QVariant &value, QVariant::Type fieldType=QVariant::Type::Invalid)
Create an expression allowing to evaluate if a field is equal to a value.
static QString quotedColumnRef(QString name)
Returns a quoted column reference (in double quotes)
QString processFontFamilyName(const QString &name) const
Processes a font family name, applying any matching fontFamilyReplacements() to the name.
static bool fontFamilyHasStyle(const QString &family, const QString &style)
Check whether font family on system has specific style.
static QVariant parseJson(const std::string &jsonString)
Converts JSON jsonString to a QVariant, in case of parsing error an invalid QVariant is returned and ...
void setPlacementFlags(QgsLabeling::LinePlacementFlags flags)
Returns the line placement flags, which dictate how line labels can be placed above or below the line...
void setFactor(double factor)
Sets the obstacle factor, where 1.0 = default, < 1.0 more likely to be covered by labels,...
@ AboveLine
Labels can be placed above a line feature. Unless MapOrientation is also specified this mode respects...
Definition: qgslabeling.h:41
@ OnLine
Labels can be placed directly over a line feature.
Definition: qgslabeling.h:40
@ BelowLine
Labels can be placed below a line feature. Unless MapOrientation is also specified this mode respects...
Definition: qgslabeling.h:42
virtual void setWidth(double width)
Sets the width of the line symbol layer.
void setOffset(double offset)
Sets the line's offset.
void setOffsetUnit(Qgis::RenderUnit unit)
Sets the unit for the line's offset.
Abstract base class for MapBox GL style sources.
QString name() const
Returns the source's name.
QgsMapBoxGlStyleAbstractSource(const QString &name)
Constructor for QgsMapBoxGlStyleAbstractSource.
Context for a MapBox GL style conversion operation.
void setLayerId(const QString &value)
Sets the layer ID of the layer currently being converted.
QStringList warnings() const
Returns a list of warning messages generated during the conversion.
void pushWarning(const QString &warning)
Pushes a warning message generated during the conversion.
double pixelSizeConversionFactor() const
Returns the pixel size conversion factor, used to scale the original pixel sizes when converting styl...
void setTargetUnit(Qgis::RenderUnit targetUnit)
Sets the target unit type.
void setPixelSizeConversionFactor(double sizeConversionFactor)
Sets the pixel size conversion factor, used to scale the original pixel sizes when converting styles.
Qgis::RenderUnit targetUnit() const
Returns the target unit type.
void setSprites(const QImage &image, const QVariantMap &definitions)
Sets the sprite image and definitions JSON to use during conversion.
QString layerId() const
Returns the layer ID of the layer currently being converted.
QImage spriteImage() const
Returns the sprite image to use during conversion, or an invalid image if this is not set.
void clearWarnings()
Clears the list of warning messages.
QVariantMap spriteDefinitions() const
Returns the sprite definitions to use during conversion.
static QString parseOpacityStops(double base, const QVariantList &stops, int maxOpacity, QgsMapBoxGlStyleConversionContext &context)
Takes values from stops and uses either scale_linear() or scale_exp() functions to interpolate alpha ...
static QString parseColorExpression(const QVariant &colorExpression, QgsMapBoxGlStyleConversionContext &context)
Converts an expression representing a color to a string (can be color string or an expression where a...
static QString parseStops(double base, const QVariantList &stops, double multiplier, QgsMapBoxGlStyleConversionContext &context)
Parses a list of interpolation stops.
QgsVectorTileRenderer * renderer() const
Returns a new instance of a vector tile renderer representing the converted style,...
static QString parseExpression(const QVariantList &expression, QgsMapBoxGlStyleConversionContext &context, bool colorExpected=false)
Converts a MapBox GL expression to a QGIS expression.
PropertyType
Property types, for interpolated value conversion.
@ Numeric
Numeric property (e.g. line width, text size)
QList< QgsMapBoxGlStyleAbstractSource * > sources()
Returns the list of converted sources.
QgsVectorTileLabeling * labeling() const
Returns a new instance of a vector tile labeling representing the converted style,...
QList< QgsMapBoxGlStyleRasterSubLayer > rasterSubLayers() const
Returns a list of raster sub layers contained in the style.
static QgsProperty parseInterpolateByZoom(const QVariantMap &json, QgsMapBoxGlStyleConversionContext &context, double multiplier=1, double *defaultNumber=nullptr)
Parses a numeric value which is interpolated by zoom range.
static Qt::PenJoinStyle parseJoinStyle(const QString &style)
Converts a value to Qt::PenJoinStyle enum from JSON value.
static QgsProperty parseInterpolateStringByZoom(const QVariantMap &json, QgsMapBoxGlStyleConversionContext &context, const QVariantMap &conversionMap, QString *defaultString=nullptr)
Interpolates a string by zoom.
static QString interpolateExpression(double zoomMin, double zoomMax, QVariant valueMin, QVariant valueMax, double base, double multiplier=1, QgsMapBoxGlStyleConversionContext *contextPtr=0)
Generates an interpolation for values between valueMin and valueMax, scaled between the ranges zoomMi...
static QgsProperty parseInterpolatePointByZoom(const QVariantMap &json, QgsMapBoxGlStyleConversionContext &context, double multiplier=1, QPointF *defaultPoint=nullptr)
Interpolates a point/offset with either scale_linear() or scale_exp() (depending on base value).
static bool parseCircleLayer(const QVariantMap &jsonLayer, QgsVectorTileBasicRendererStyle &style, QgsMapBoxGlStyleConversionContext &context)
Parses a circle layer.
Result convert(const QVariantMap &style, QgsMapBoxGlStyleConversionContext *context=nullptr)
Converts a JSON style map, and returns the resultant status of the conversion.
static QgsProperty parseInterpolateListByZoom(const QVariantList &json, PropertyType type, QgsMapBoxGlStyleConversionContext &context, double multiplier=1, int maxOpacity=255, QColor *defaultColor=nullptr, double *defaultNumber=nullptr)
Interpolates a list which starts with the interpolate function.
QList< QgsMapLayer * > createSubLayers() const
Returns a list of new map layers corresponding to sublayers of the style, e.g.
@ Success
Conversion was successful.
@ NoLayerList
No layer list was found in JSON input.
QgsMapBoxGlStyleConverter()
Constructor for QgsMapBoxGlStyleConverter.
static QImage retrieveSprite(const QString &name, QgsMapBoxGlStyleConversionContext &context, QSize &spriteSize)
Retrieves the sprite image with the specified name, taken from the specified context.
static QString parseLabelStops(const QVariantList &stops, QgsMapBoxGlStyleConversionContext &context)
Parses a list of interpolation stops containing label values.
void parseLayers(const QVariantList &layers, QgsMapBoxGlStyleConversionContext *context=nullptr)
Parse list of layers from JSON.
static QgsProperty parseInterpolateColorByZoom(const QVariantMap &json, QgsMapBoxGlStyleConversionContext &context, QColor *defaultColor=nullptr)
Parses a color value which is interpolated by zoom range.
static QgsProperty parseInterpolateOpacityByZoom(const QVariantMap &json, int maxOpacity, QgsMapBoxGlStyleConversionContext *contextPtr=0)
Interpolates opacity with either scale_linear() or scale_exp() (depending on base value).
static QString retrieveSpriteAsBase64(const QVariant &value, QgsMapBoxGlStyleConversionContext &context, QSize &spriteSize, QString &spriteProperty, QString &spriteSizeProperty)
Retrieves the sprite image with the specified name, taken from the specified context as a base64 enco...
void parseSources(const QVariantMap &sources, QgsMapBoxGlStyleConversionContext *context=nullptr)
Parse list of sources from JSON.
static QColor parseColor(const QVariant &color, QgsMapBoxGlStyleConversionContext &context)
Parses a color in one of these supported formats:
static bool parseSymbolLayerAsRenderer(const QVariantMap &jsonLayer, QgsVectorTileBasicRendererStyle &rendererStyle, QgsMapBoxGlStyleConversionContext &context)
Parses a symbol layer as a renderer.
static bool parseFillLayer(const QVariantMap &jsonLayer, QgsVectorTileBasicRendererStyle &style, QgsMapBoxGlStyleConversionContext &context, bool isBackgroundStyle=false)
Parses a fill layer.
static void parseSymbolLayer(const QVariantMap &jsonLayer, QgsVectorTileBasicRendererStyle &rendererStyle, bool &hasRenderer, QgsVectorTileBasicLabelingStyle &labelingStyle, bool &hasLabeling, QgsMapBoxGlStyleConversionContext &context)
Parses a symbol layer as renderer or labeling.
static bool parseLineLayer(const QVariantMap &jsonLayer, QgsVectorTileBasicRendererStyle &style, QgsMapBoxGlStyleConversionContext &context)
Parses a line layer.
void parseRasterSource(const QVariantMap &source, const QString &name, QgsMapBoxGlStyleConversionContext *context=nullptr)
Parse a raster source from JSON.
static void colorAsHslaComponents(const QColor &color, int &hue, int &saturation, int &lightness, int &alpha)
Takes a QColor object and returns HSLA components in required format for QGIS color_hsla() expression...
static Qt::PenCapStyle parseCapStyle(const QString &style)
Converts a value to Qt::PenCapStyle enum from JSON value.
static QString parseStringStops(const QVariantList &stops, QgsMapBoxGlStyleConversionContext &context, const QVariantMap &conversionMap, QString *defaultString=nullptr)
Parses a list of interpolation stops containing string values.
static QgsProperty parseMatchList(const QVariantList &json, PropertyType type, QgsMapBoxGlStyleConversionContext &context, double multiplier=1, int maxOpacity=255, QColor *defaultColor=nullptr, double *defaultNumber=nullptr)
Parses and converts a match function value list.
static QgsProperty parseValueList(const QVariantList &json, PropertyType type, QgsMapBoxGlStyleConversionContext &context, double multiplier=1, int maxOpacity=255, QColor *defaultColor=nullptr, double *defaultNumber=nullptr)
Parses and converts a value list (e.g.
static QString parseArrayStops(const QVariantList &stops, QgsMapBoxGlStyleConversionContext &context, double multiplier=1)
Takes numerical arrays from stops.
static QString parsePointStops(double base, const QVariantList &stops, QgsMapBoxGlStyleConversionContext &context, double multiplier=1)
Takes values from stops and uses either scale_linear() or scale_exp() functions to interpolate point/...
Encapsulates a MapBox GL style raster source.
Qgis::MapBoxGlStyleSourceType type() const override
Returns the source type.
QgsMapBoxGlStyleRasterSource(const QString &name)
Constructor for QgsMapBoxGlStyleRasterSource.
QgsRasterLayer * toRasterLayer() const
Returns a new raster layer representing the raster source, or nullptr if the source cannot be represe...
bool setFromJson(const QVariantMap &json, QgsMapBoxGlStyleConversionContext *context) override
Sets the source's state from a json map.
QStringList tiles() const
Returns the list of tile sources.
Encapsulates a MapBox GL style raster sub layer.
QgsPropertyCollection & dataDefinedProperties()
Returns a reference to the layer's data defined properties.
QgsMapBoxGlStyleRasterSubLayer(const QString &id, const QString &source)
Constructor for QgsMapBoxGlStyleRasterSubLayer, with the given id and source.
Line symbol layer type which draws repeating marker symbols along a line feature.
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
bool setSubSymbol(QgsSymbol *symbol) override
Sets layer's subsymbol. takes ownership of the passed symbol.
virtual void setSize(double size)
Sets the symbol size.
void setOffsetUnit(Qgis::RenderUnit unit)
Sets the units for the symbol's offset.
void setAngle(double angle)
Sets the rotation angle for the marker.
void setOffset(QPointF offset)
Sets the marker's offset, which is the horizontal and vertical displacement which the rendered marker...
void setSizeUnit(Qgis::RenderUnit unit)
Sets the units for the symbol's size.
A marker symbol type, for rendering Point and MultiPoint geometries.
void setEnabled(bool enabled)
Sets whether the effect is enabled.
Contains settings for how a map layer will be labeled.
double yOffset
Vertical offset of label.
const QgsLabelObstacleSettings & obstacleSettings() const
Returns the label obstacle settings.
void setFormat(const QgsTextFormat &format)
Sets the label text formatting settings, e.g., font settings, buffer settings, etc.
double xOffset
Horizontal offset of label.
Qgis::LabelPlacement placement
Label placement mode.
Qgis::LabelQuadrantPosition quadOffset
Sets the quadrant in which to offset labels from feature.
Qgis::LabelMultiLineAlignment multilineAlign
Horizontal alignment of multi-line labels.
int priority
Label priority.
@ FontStyle
Font style name.
@ FontLetterSpacing
Letter spacing.
@ Family
Font family.
@ LinePlacementOptions
Line placement flags.
Qgis::RenderUnit offsetUnits
Units for offsets of label.
void setDataDefinedProperties(const QgsPropertyCollection &collection)
Sets the label's property collection, used for data defined overrides.
bool isExpression
true if this label is made from a expression string, e.g., FieldName || 'mm'
const QgsLabelLineSettings & lineSettings() const
Returns the label line settings, which contain settings related to how the label engine places and fo...
double dist
Distance from feature to the label.
Qgis::RenderUnit distUnits
Units the distance from feature to the label.
QString fieldName
Name of field (or an expression) to use for label text.
int autoWrapLength
If non-zero, indicates that label text should be automatically wrapped to (ideally) the specified num...
A grouped map of multiple QgsProperty objects, each referenced by a integer key value.
QgsProperty property(int key) const override
Returns a matching property from the collection, if one exists.
QVariant value(int key, const QgsExpressionContext &context, const QVariant &defaultValue=QVariant()) const override
Returns the calculated value of the property with the specified key from within the collection.
void setProperty(int key, const QgsProperty &property)
Adds a property to the collection and takes ownership of it.
bool isActive(int key) const override
Returns true if the collection contains an active property with the specified key.
A store for object properties.
Definition: qgsproperty.h:230
QString asExpression() const
Returns an expression string representing the state of the property, or an empty string if the proper...
QString expressionString() const
Returns the expression used for the property value.
QVariant value(const QgsExpressionContext &context, const QVariant &defaultValue=QVariant(), bool *ok=nullptr) const
Calculates the current value of the property, including any transforms which are set for the property...
static QgsProperty fromExpression(const QString &expression, bool isActive=true)
Returns a new ExpressionBasedProperty created from the specified expression.
static QgsProviderRegistry * instance(const QString &pluginPath=QString())
Means of accessing canonical single instance.
A class for filling symbols with a repeated raster image.
void setWidthUnit(const Qgis::RenderUnit unit)
Sets the units for the image's width.
void setWidth(const double width)
Sets the width for scaling the image used in the fill.
void setOpacity(double opacity)
Sets the opacity for the raster image used in the fill.
void setImageFilePath(const QString &imagePath)
Sets the path to the raster image used for the fill.
void setCoordinateMode(Qgis::SymbolCoordinateReference mode)
Set the coordinate mode for fill.
Represents a raster layer.
Line symbol layer type which draws line sections using a raster image file.
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
Raster marker symbol layer class.
void setOpacity(double opacity)
Set the marker opacity.
void setPath(const QString &path)
Set the marker raster image path.
void setBrushStyle(Qt::BrushStyle style)
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
void setStrokeStyle(Qt::PenStyle strokeStyle)
void setOffsetUnit(Qgis::RenderUnit unit)
Sets the unit for the fill's offset.
void setFillColor(const QColor &color) override
Sets the fill color for the symbol layer.
void setOffset(QPointF offset)
Sets an offset by which polygons will be translated during rendering.
void setStrokeColor(const QColor &strokeColor) override
Sets the stroke color for the symbol layer.
A simple line symbol layer, which renders lines using a line in a variety of styles (e....
void setPenCapStyle(Qt::PenCapStyle style)
Sets the pen cap style used to render the line (e.g.
void setUseCustomDashPattern(bool b)
Sets whether the line uses a custom dash pattern.
void setCustomDashVector(const QVector< qreal > &vector)
Sets the custom dash vector, which is the pattern of alternating drawn/skipped lengths used while ren...
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
void setPenJoinStyle(Qt::PenJoinStyle style)
Sets the pen join style used to render the line (e.g.
Simple marker symbol layer, consisting of a rendered shape with solid fill color and an stroke.
void setFillColor(const QColor &color) override
Sets the fill color for the symbol layer.
void setStrokeWidthUnit(Qgis::RenderUnit u)
Sets the unit for the width of the marker's stroke.
void setStrokeWidth(double w)
Sets the width of the marker's stroke.
void setStrokeColor(const QColor &color) override
Sets the marker's stroke color.
static QColor parseColor(const QString &colorStr, bool strictEval=false)
Attempts to parse a string as a color using a variety of common formats, including hex codes,...
@ PropertyFile
Filename, eg for svg files.
@ PropertyAngle
Symbol angle.
@ PropertyCustomDash
Custom dash pattern.
@ PropertyOpacity
Opacity.
@ PropertyOffset
Symbol offset.
@ PropertyStrokeWidth
Stroke width.
@ PropertyFillColor
Fill color.
@ PropertyName
Name, eg shape name for simple markers.
@ PropertyInterval
Line marker interval.
@ PropertyStrokeColor
Stroke color.
@ PropertyWidth
Symbol width.
virtual void setColor(const QColor &color)
Sets the "representative" color for the symbol layer.
void setDataDefinedProperties(const QgsPropertyCollection &collection)
Sets the symbol layer's property collection, used for data defined overrides.
@ PropertyOpacity
Opacity.
Definition: qgssymbol.h:131
void setPlacements(Qgis::MarkerLinePlacements placements)
Sets the placement of the symbols.
Container for settings relating to a text background object.
void setMarkerSymbol(QgsMarkerSymbol *symbol)
Sets the current marker symbol for the background shape.
void setSizeType(SizeType type)
Sets the method used to determine the size of the background shape (e.g., fixed size or buffer around...
void setSizeUnit(Qgis::RenderUnit unit)
Sets the units used for the shape's size.
void setType(ShapeType type)
Sets the type of background shape to draw (e.g., square, ellipse, SVG).
void setEnabled(bool enabled)
Sets whether the text background will be drawn.
void setSize(QSizeF size)
Sets the size of the background shape.
void setColor(const QColor &color)
Sets the color for the buffer.
void setSizeUnit(Qgis::RenderUnit unit)
Sets the units used for the buffer size.
void setEnabled(bool enabled)
Sets whether the text buffer will be drawn.
void setPaintEffect(QgsPaintEffect *effect)
Sets the current paint effect for the buffer.
void setSize(double size)
Sets the size of the buffer.
Container for all settings relating to text rendering.
Definition: qgstextformat.h:41
void setColor(const QColor &color)
Sets the color that text will be rendered in.
void setSize(double size)
Sets the size for rendered text.
void setFont(const QFont &font)
Sets the font used for rendering text.
void setSizeUnit(Qgis::RenderUnit unit)
Sets the units for the size of rendered text.
void setBackground(const QgsTextBackgroundSettings &backgroundSettings)
Sets the text's background settings.q.
void setNamedStyle(const QString &style)
Sets the named style for the font used for rendering text.
QFont font() const
Returns the font used for rendering text.
QgsTextBufferSettings & buffer()
Returns a reference to the text buffer settings.
Configuration of a single style within QgsVectorTileBasicLabeling.
void setLayerName(const QString &name)
Sets name of the sub-layer to render (empty layer means that all layers match)
void setMinZoomLevel(int minZoom)
Sets minimum zoom level index (negative number means no limit)
void setFilterExpression(const QString &expr)
Sets filter expression (empty filter means that all features match)
void setMaxZoomLevel(int maxZoom)
Sets maximum zoom level index (negative number means no limit)
void setStyleName(const QString &name)
Sets human readable name of this style.
void setGeometryType(Qgis::GeometryType geomType)
Sets type of the geometry that will be used (point / line / polygon)
void setLabelSettings(const QgsPalLayerSettings &settings)
Sets labeling configuration of this style.
void setEnabled(bool enabled)
Sets whether this style is enabled (used for rendering)
Basic labeling configuration for vector tile layers.
Definition of map rendering of a subset of vector tile data.
void setEnabled(bool enabled)
Sets whether this style is enabled (used for rendering)
void setMinZoomLevel(int minZoom)
Sets minimum zoom level index (negative number means no limit)
void setLayerName(const QString &name)
Sets name of the sub-layer to render (empty layer means that all layers match)
void setFilterExpression(const QString &expr)
Sets filter expression (empty filter means that all features match)
void setSymbol(QgsSymbol *sym)
Sets symbol for rendering. Takes ownership of the symbol.
void setStyleName(const QString &name)
Sets human readable name of this style.
void setMaxZoomLevel(int maxZoom)
Sets maximum zoom level index (negative number means no limit)
void setGeometryType(Qgis::GeometryType geomType)
Sets type of the geometry that will be used (point / line / polygon)
The default vector tile renderer implementation.
Base class for labeling configuration classes for vector tile layers.
Abstract base class for all vector tile renderer implementations.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition: qgis.h:3448
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition: qgis.h:3713
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:3509
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QList< QgsSymbolLayer * > QgsSymbolLayerList
Definition: qgssymbol.h:29
const QString & typeName