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