QGIS API Documentation 4.1.0-Master (26185ffb827)
Loading...
Searching...
No Matches
qgssymbolconverteresrirest.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgssymbolconverteresrirest.cpp
3 ----------------------
4 begin : February 2026
5 copyright : (C) 2026 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
17
18#include "qgsfillsymbol.h"
19#include "qgsfillsymbollayer.h"
20#include "qgsjsonutils.h"
21#include "qgslinesymbol.h"
22#include "qgslinesymbollayer.h"
23#include "qgsmarkersymbol.h"
25
26#include <QString>
27
28using namespace Qt::StringLiterals;
29
34
36{
37 return u"esri_rest"_s;
38}
39
41{
42 return QObject::tr( "ESRI REST JSON" );
43}
44
46{
47 throw QgsNotSupportedException( u"This symbol converter does not support serialization of symbols"_s );
48}
49
50std::unique_ptr< QgsSymbol > QgsSymbolConverterEsriRest::createSymbol( const QVariant &variant, QgsSymbolConverterContext & ) const
51{
52 QVariantMap symbolData;
53 if ( variant.type() == QVariant::Map )
54 {
55 symbolData = variant.toMap();
56 }
57 else if ( variant.type() == QVariant::String )
58 {
59 const QVariant v = QgsJsonUtils::parseJson( variant.toString() );
60 if ( v.type() == QVariant::Map )
61 {
62 symbolData = v.toMap();
63 }
64 }
65 if ( symbolData.isEmpty() )
66 return nullptr;
67
68 const QString type = symbolData.value( u"type"_s ).toString();
69 if ( type == "esriSMS"_L1 )
70 {
71 // marker symbol
72 return parseEsriMarkerSymbolJson( symbolData );
73 }
74 else if ( type == "esriSLS"_L1 )
75 {
76 // line symbol
77 return parseEsriLineSymbolJson( symbolData );
78 }
79 else if ( type == "esriSFS"_L1 )
80 {
81 // fill symbol
82 return parseEsriFillSymbolJson( symbolData );
83 }
84 else if ( type == "esriPFS"_L1 )
85 {
86 return parseEsriPictureFillSymbolJson( symbolData );
87 }
88 else if ( type == "esriPMS"_L1 )
89 {
90 // picture marker
91 return parseEsriPictureMarkerSymbolJson( symbolData );
92 }
93 else if ( type == "esriTS"_L1 )
94 {
95 return parseEsriTextMarkerSymbolJson( symbolData );
96 }
97 return nullptr;
98}
99
100std::unique_ptr<QgsLineSymbol> QgsSymbolConverterEsriRest::parseEsriLineSymbolJson( const QVariantMap &symbolData )
101{
102 const QColor lineColor = convertColor( symbolData.value( u"color"_s ) );
103 if ( !lineColor.isValid() )
104 return nullptr;
105
106 bool ok = false;
107 const double widthInPoints = symbolData.value( u"width"_s ).toDouble( &ok );
108 if ( !ok )
109 return nullptr;
110
111 QgsSymbolLayerList layers;
112 const Qt::PenStyle penStyle = convertLineStyle( symbolData.value( u"style"_s ).toString() );
113 auto lineLayer = std::make_unique< QgsSimpleLineSymbolLayer >( lineColor, widthInPoints, penStyle );
114 lineLayer->setWidthUnit( Qgis::RenderUnit::Points );
115 layers.append( lineLayer.release() );
116
117 auto symbol = std::make_unique< QgsLineSymbol >( layers );
118 return symbol;
119}
120
121std::unique_ptr<QgsFillSymbol> QgsSymbolConverterEsriRest::parseEsriFillSymbolJson( const QVariantMap &symbolData )
122{
123 const QColor fillColor = convertColor( symbolData.value( u"color"_s ) );
124 const Qt::BrushStyle brushStyle = convertFillStyle( symbolData.value( u"style"_s ).toString() );
125
126 QColor lineColor;
127 Qt::PenStyle penStyle = Qt::NoPen;
128 double penWidthInPoints = 0;
129
130 // Check if outline is null - ArcGIS services may return "outline": null
131 const QVariant outlineVariant = symbolData.value( u"outline"_s );
132 if ( !QgsVariantUtils::isNull( outlineVariant ) )
133 {
134 const QVariantMap outlineData = outlineVariant.toMap();
135 lineColor = convertColor( outlineData.value( u"color"_s ) );
136 penStyle = convertLineStyle( outlineData.value( u"style"_s ).toString() );
137 bool ok = false;
138 penWidthInPoints = outlineData.value( u"width"_s ).toDouble( &ok );
139 }
140
141 QgsSymbolLayerList layers;
142 auto fillLayer = std::make_unique< QgsSimpleFillSymbolLayer >( fillColor, brushStyle, lineColor, penStyle, penWidthInPoints );
143 fillLayer->setStrokeWidthUnit( Qgis::RenderUnit::Points );
144 layers.append( fillLayer.release() );
145
146 auto symbol = std::make_unique< QgsFillSymbol >( layers );
147 return symbol;
148}
149
150std::unique_ptr<QgsFillSymbol> QgsSymbolConverterEsriRest::parseEsriPictureFillSymbolJson( const QVariantMap &symbolData )
151{
152 bool ok = false;
153
154 double widthInPixels = symbolData.value( u"width"_s ).toInt( &ok );
155 if ( !ok )
156 return nullptr;
157
158 const double xScale = symbolData.value( u"xscale"_s ).toDouble( &ok );
159 if ( !qgsDoubleNear( xScale, 0.0 ) )
160 widthInPixels *= xScale;
161
162 const double angleCCW = symbolData.value( u"angle"_s ).toDouble( &ok );
163 double angleCW = 0;
164 if ( ok )
165 angleCW = -angleCCW;
166
167 const double xOffset = symbolData.value( u"xoffset"_s ).toDouble();
168 const double yOffset = symbolData.value( u"yoffset"_s ).toDouble();
169
170 QString symbolPath( symbolData.value( u"imageData"_s ).toString() );
171 symbolPath.prepend( "base64:"_L1 );
172
173 QgsSymbolLayerList layers;
174 auto fillLayer = std::make_unique< QgsRasterFillSymbolLayer >( symbolPath );
175 fillLayer->setWidth( widthInPixels );
176 fillLayer->setAngle( angleCW );
177 fillLayer->setSizeUnit( Qgis::RenderUnit::Points );
178 fillLayer->setOffset( QPointF( xOffset, yOffset ) );
179 fillLayer->setOffsetUnit( Qgis::RenderUnit::Points );
180 layers.append( fillLayer.release() );
181
182 // Check if outline is null - ArcGIS services may return "outline": null
183 const QVariant outlineVariant = symbolData.value( u"outline"_s );
184 if ( !QgsVariantUtils::isNull( outlineVariant ) )
185 {
186 const QVariantMap outlineData = outlineVariant.toMap();
187 const QColor lineColor = convertColor( outlineData.value( u"color"_s ) );
188 const Qt::PenStyle penStyle = convertLineStyle( outlineData.value( u"style"_s ).toString() );
189 const double penWidthInPoints = outlineData.value( u"width"_s ).toDouble( &ok );
190
191 auto lineLayer = std::make_unique< QgsSimpleLineSymbolLayer >( lineColor, penWidthInPoints, penStyle );
192 lineLayer->setWidthUnit( Qgis::RenderUnit::Points );
193 layers.append( lineLayer.release() );
194 }
195
196 auto symbol = std::make_unique< QgsFillSymbol >( layers );
197 return symbol;
198}
199
200Qgis::MarkerShape QgsSymbolConverterEsriRest::parseEsriMarkerShape( const QString &style )
201{
202 if ( style == "esriSMSCircle"_L1 )
204 else if ( style == "esriSMSCross"_L1 )
206 else if ( style == "esriSMSDiamond"_L1 )
208 else if ( style == "esriSMSSquare"_L1 )
210 else if ( style == "esriSMSX"_L1 )
212 else if ( style == "esriSMSTriangle"_L1 )
214 else
216}
217
218std::unique_ptr<QgsMarkerSymbol> QgsSymbolConverterEsriRest::parseEsriMarkerSymbolJson( const QVariantMap &symbolData )
219{
220 QColor fillColor = convertColor( symbolData.value( u"color"_s ) );
221 bool ok = false;
222 const double sizeInPoints = symbolData.value( u"size"_s ).toDouble( &ok );
223 if ( !ok )
224 return nullptr;
225 const double angleCCW = symbolData.value( u"angle"_s ).toDouble( &ok );
226 double angleCW = 0;
227 if ( ok )
228 angleCW = -angleCCW;
229
230 const Qgis::MarkerShape shape = parseEsriMarkerShape( symbolData.value( u"style"_s ).toString() );
231
232 const double xOffset = symbolData.value( u"xoffset"_s ).toDouble();
233 const double yOffset = symbolData.value( u"yoffset"_s ).toDouble();
234
235 QColor lineColor;
236 Qt::PenStyle penStyle = Qt::NoPen;
237 double penWidthInPoints = 0;
238
239 // Check if outline is null - ArcGIS services may return "outline": null
240 const QVariant outlineVariant = symbolData.value( u"outline"_s );
241 if ( !QgsVariantUtils::isNull( outlineVariant ) )
242 {
243 const QVariantMap outlineData = outlineVariant.toMap();
244 lineColor = convertColor( outlineData.value( u"color"_s ) );
245 penStyle = convertLineStyle( outlineData.value( u"style"_s ).toString() );
246 penWidthInPoints = outlineData.value( u"width"_s ).toDouble( &ok );
247 }
248
249 QgsSymbolLayerList layers;
250 auto markerLayer = std::make_unique< QgsSimpleMarkerSymbolLayer >( shape, sizeInPoints, angleCW, Qgis::ScaleMethod::ScaleArea, fillColor, lineColor );
251 markerLayer->setSizeUnit( Qgis::RenderUnit::Points );
252 markerLayer->setStrokeWidthUnit( Qgis::RenderUnit::Points );
253 markerLayer->setStrokeStyle( penStyle );
254 markerLayer->setStrokeWidth( penWidthInPoints );
255 markerLayer->setOffset( QPointF( xOffset, yOffset ) );
256 markerLayer->setOffsetUnit( Qgis::RenderUnit::Points );
257 layers.append( markerLayer.release() );
258
259 auto symbol = std::make_unique< QgsMarkerSymbol >( layers );
260 return symbol;
261}
262
263std::unique_ptr<QgsMarkerSymbol> QgsSymbolConverterEsriRest::parseEsriPictureMarkerSymbolJson( const QVariantMap &symbolData )
264{
265 bool ok = false;
266 const double widthInPixels = symbolData.value( u"width"_s ).toInt( &ok );
267 if ( !ok )
268 return nullptr;
269 const double heightInPixels = symbolData.value( u"height"_s ).toInt( &ok );
270 if ( !ok )
271 return nullptr;
272
273 const double angleCCW = symbolData.value( u"angle"_s ).toDouble( &ok );
274 double angleCW = 0;
275 if ( ok )
276 angleCW = -angleCCW;
277
278 const double xOffset = symbolData.value( u"xoffset"_s ).toDouble();
279 const double yOffset = symbolData.value( u"yoffset"_s ).toDouble();
280
281 QString symbolPath( symbolData.value( u"imageData"_s ).toString() );
282 symbolPath.prepend( "base64:"_L1 );
283
284 QgsSymbolLayerList layers;
285 auto markerLayer = std::make_unique< QgsRasterMarkerSymbolLayer >( symbolPath, widthInPixels, angleCW, Qgis::ScaleMethod::ScaleArea );
286 markerLayer->setSizeUnit( Qgis::RenderUnit::Points );
287
288 // only change the default aspect ratio if the server height setting requires this
289 if ( !qgsDoubleNear( static_cast< double >( heightInPixels ) / widthInPixels, markerLayer->defaultAspectRatio() ) )
290 markerLayer->setFixedAspectRatio( static_cast< double >( heightInPixels ) / widthInPixels );
291
292 markerLayer->setOffset( QPointF( xOffset, yOffset ) );
293 markerLayer->setOffsetUnit( Qgis::RenderUnit::Points );
294 layers.append( markerLayer.release() );
295
296 auto symbol = std::make_unique< QgsMarkerSymbol >( layers );
297 return symbol;
298}
299
300std::unique_ptr<QgsMarkerSymbol> QgsSymbolConverterEsriRest::parseEsriTextMarkerSymbolJson( const QVariantMap &symbolData )
301{
302 QgsSymbolLayerList layers;
303
304 const QString fontFamily = symbolData.value( u"font"_s ).toMap().value( u"family"_s ).toString();
305 const QString chr = symbolData.value( u"text"_s ).toString();
306 const double pointSize = symbolData.value( u"font"_s ).toMap().value( u"size"_s ).toDouble();
307 const QColor color = convertColor( symbolData.value( u"color"_s ) );
308 const double esriAngle = symbolData.value( u"angle"_s ).toDouble();
309 const double angle = 90.0 - esriAngle;
310
311 auto markerLayer = std::make_unique< QgsFontMarkerSymbolLayer >( fontFamily, chr, pointSize, color, angle );
312
313 QColor strokeColor = convertColor( symbolData.value( u"borderLineColor"_s ) );
314 markerLayer->setStrokeColor( strokeColor );
315
316 double borderLineSize = symbolData.value( u"borderLineSize"_s ).toDouble();
317 markerLayer->setStrokeWidth( borderLineSize );
318
319 const QString fontStyle = symbolData.value( u"font"_s ).toMap().value( u"style"_s ).toString();
320 markerLayer->setFontStyle( fontStyle );
321
322 double xOffset = symbolData.value( u"xoffset"_s ).toDouble();
323 double yOffset = symbolData.value( u"yoffset"_s ).toDouble();
324
325 markerLayer->setOffset( QPointF( xOffset, yOffset ) );
326 markerLayer->setOffsetUnit( Qgis::RenderUnit::Points );
327
328 markerLayer->setSizeUnit( Qgis::RenderUnit::Points );
329 markerLayer->setStrokeWidthUnit( Qgis::RenderUnit::Points );
330
333
334 QString horizontalAnchorPoint = symbolData.value( u"horizontalAlignment"_s ).toString();
335 QString verticalAnchorPoint = symbolData.value( u"verticalAlignment"_s ).toString();
336
337 if ( horizontalAnchorPoint == QString( "center" ) )
338 {
340 }
341 else if ( horizontalAnchorPoint == QString( "left" ) )
342 {
344 }
345 else if ( horizontalAnchorPoint == QString( "right" ) )
346 {
348 }
349
350 if ( verticalAnchorPoint == QString( "center" ) )
351 {
353 }
354 else if ( verticalAnchorPoint == QString( "top" ) )
355 {
357 }
358 else if ( verticalAnchorPoint == QString( "bottom" ) )
359 {
361 }
362
363 markerLayer->setHorizontalAnchorPoint( hAlign );
364 markerLayer->setVerticalAnchorPoint( vAlign );
365
366 layers.append( markerLayer.release() );
367
368 auto symbol = std::make_unique< QgsMarkerSymbol >( layers );
369 return symbol;
370}
371
372QColor QgsSymbolConverterEsriRest::convertColor( const QVariant &colorData )
373{
374 const QVariantList colorParts = colorData.toList();
375 if ( colorParts.count() < 4 )
376 return QColor();
377
378 const int red = colorParts.at( 0 ).toInt();
379 const int green = colorParts.at( 1 ).toInt();
380 const int blue = colorParts.at( 2 ).toInt();
381 const int alpha = colorParts.at( 3 ).toInt();
382 return QColor( red, green, blue, alpha );
383}
384
385Qt::PenStyle QgsSymbolConverterEsriRest::convertLineStyle( const QString &style )
386{
387 if ( style == "esriSLSSolid"_L1 )
388 return Qt::SolidLine;
389 else if ( style == "esriSLSDash"_L1 )
390 return Qt::DashLine;
391 else if ( style == "esriSLSDashDot"_L1 )
392 return Qt::DashDotLine;
393 else if ( style == "esriSLSDashDotDot"_L1 )
394 return Qt::DashDotDotLine;
395 else if ( style == "esriSLSDot"_L1 )
396 return Qt::DotLine;
397 else if ( style == "esriSLSNull"_L1 )
398 return Qt::NoPen;
399 else
400 return Qt::SolidLine;
401}
402
403Qt::BrushStyle QgsSymbolConverterEsriRest::convertFillStyle( const QString &style )
404{
405 if ( style == "esriSFSBackwardDiagonal"_L1 )
406 return Qt::BDiagPattern;
407 else if ( style == "esriSFSCross"_L1 )
408 return Qt::CrossPattern;
409 else if ( style == "esriSFSDiagonalCross"_L1 )
410 return Qt::DiagCrossPattern;
411 else if ( style == "esriSFSForwardDiagonal"_L1 )
412 return Qt::FDiagPattern;
413 else if ( style == "esriSFSHorizontal"_L1 )
414 return Qt::HorPattern;
415 else if ( style == "esriSFSNull"_L1 )
416 return Qt::NoBrush;
417 else if ( style == "esriSFSSolid"_L1 )
418 return Qt::SolidPattern;
419 else if ( style == "esriSFSVertical"_L1 )
420 return Qt::VerPattern;
421 else
422 return Qt::SolidPattern;
423}
@ ScaleArea
Calculate scale by the area.
Definition qgis.h:652
MarkerShape
Marker shapes.
Definition qgis.h:3261
@ Circle
Circle.
Definition qgis.h:3270
@ Triangle
Triangle.
Definition qgis.h:3266
@ Cross2
Rotated cross (lines only), 'x' shape.
Definition qgis.h:3273
@ Diamond
Diamond.
Definition qgis.h:3263
@ Square
Square.
Definition qgis.h:3262
@ Cross
Cross (lines only).
Definition qgis.h:3271
VerticalAnchorPoint
Marker symbol vertical anchor points.
Definition qgis.h:862
@ Bottom
Align to bottom of symbol.
Definition qgis.h:865
@ Center
Align to vertical center of symbol.
Definition qgis.h:864
@ Top
Align to top of symbol.
Definition qgis.h:863
QFlags< SymbolConverterCapability > SymbolConverterCapabilities
Symbol converter capabilities.
Definition qgis.h:825
@ Points
Points (e.g., for font sizes).
Definition qgis.h:5591
@ ReadSymbol
Allows reading symbols from variants.
Definition qgis.h:814
HorizontalAnchorPoint
Marker symbol horizontal anchor points.
Definition qgis.h:848
@ Center
Align to horizontal center of symbol.
Definition qgis.h:850
@ Right
Align to right side of symbol.
Definition qgis.h:851
@ Left
Align to left side of symbol.
Definition qgis.h:849
static QVariant parseJson(const std::string &jsonString)
Converts JSON jsonString to a QVariant, in case of parsing error an invalid QVariant is returned and ...
Custom exception class which is raised when an operation is not supported.
Represents the context in which a QgsSymbolConverter conversion occurs.
QString formatName() const override
Returns a translated, user-friendly name for the converter's data format.
static Qt::PenStyle convertLineStyle(const QString &style)
Converts an ESRI line style to a Qt pen style.
std::unique_ptr< QgsSymbol > createSymbol(const QVariant &variant, QgsSymbolConverterContext &context) const override
Creates a new QgsSymbol from a QVariant representation.
QString name() const override
Returns the unique name for the converter.
static Qt::BrushStyle convertFillStyle(const QString &style)
Converts an ESRI fill style to a Qt brush style.
QVariant toVariant(const QgsSymbol *symbol, QgsSymbolConverterContext &context) const override
Converts a symbol into a QVariant representation.
static QColor convertColor(const QVariant &data)
Converts ESRI JSON color data to a QColor object.
Qgis::SymbolConverterCapabilities capabilities() const override
Returns the capabilities of the converter.
Abstract base class for all rendered symbols.
Definition qgssymbol.h:227
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
double ANALYSIS_EXPORT angle(QgsPoint *p1, QgsPoint *p2, QgsPoint *p3, QgsPoint *p4)
Calculates the angle between two segments (in 2 dimension, z-values are ignored).
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:7257
QList< QgsSymbolLayer * > QgsSymbolLayerList
Definition qgssymbol.h:30