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