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