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