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