QGIS API Documentation 4.1.0-Master (3b8ef1f72a3)
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 const QVariantMap outlineData = symbolData.value( u"outline"_s ).toMap();
127 const QColor lineColor = convertColor( outlineData.value( u"color"_s ) );
128 const Qt::PenStyle penStyle = convertLineStyle( outlineData.value( u"style"_s ).toString() );
129 bool ok = false;
130 const double penWidthInPoints = outlineData.value( u"width"_s ).toDouble( &ok );
131
132 QgsSymbolLayerList layers;
133 auto fillLayer = std::make_unique< QgsSimpleFillSymbolLayer >( fillColor, brushStyle, lineColor, penStyle, penWidthInPoints );
134 fillLayer->setStrokeWidthUnit( Qgis::RenderUnit::Points );
135 layers.append( fillLayer.release() );
136
137 auto symbol = std::make_unique< QgsFillSymbol >( layers );
138 return symbol;
139}
140
141std::unique_ptr<QgsFillSymbol> QgsSymbolConverterEsriRest::parseEsriPictureFillSymbolJson( const QVariantMap &symbolData )
142{
143 bool ok = false;
144
145 double widthInPixels = symbolData.value( u"width"_s ).toInt( &ok );
146 if ( !ok )
147 return nullptr;
148
149 const double xScale = symbolData.value( u"xscale"_s ).toDouble( &ok );
150 if ( !qgsDoubleNear( xScale, 0.0 ) )
151 widthInPixels *= xScale;
152
153 const double angleCCW = symbolData.value( u"angle"_s ).toDouble( &ok );
154 double angleCW = 0;
155 if ( ok )
156 angleCW = -angleCCW;
157
158 const double xOffset = symbolData.value( u"xoffset"_s ).toDouble();
159 const double yOffset = symbolData.value( u"yoffset"_s ).toDouble();
160
161 QString symbolPath( symbolData.value( u"imageData"_s ).toString() );
162 symbolPath.prepend( "base64:"_L1 );
163
164 QgsSymbolLayerList layers;
165 auto fillLayer = std::make_unique< QgsRasterFillSymbolLayer >( symbolPath );
166 fillLayer->setWidth( widthInPixels );
167 fillLayer->setAngle( angleCW );
168 fillLayer->setSizeUnit( Qgis::RenderUnit::Points );
169 fillLayer->setOffset( QPointF( xOffset, yOffset ) );
170 fillLayer->setOffsetUnit( Qgis::RenderUnit::Points );
171 layers.append( fillLayer.release() );
172
173 const QVariantMap outlineData = symbolData.value( u"outline"_s ).toMap();
174 const QColor lineColor = convertColor( outlineData.value( u"color"_s ) );
175 const Qt::PenStyle penStyle = convertLineStyle( outlineData.value( u"style"_s ).toString() );
176 const double penWidthInPoints = outlineData.value( u"width"_s ).toDouble( &ok );
177
178 auto lineLayer = std::make_unique< QgsSimpleLineSymbolLayer >( lineColor, penWidthInPoints, penStyle );
179 lineLayer->setWidthUnit( Qgis::RenderUnit::Points );
180 layers.append( lineLayer.release() );
181
182 auto symbol = std::make_unique< QgsFillSymbol >( layers );
183 return symbol;
184}
185
186Qgis::MarkerShape QgsSymbolConverterEsriRest::parseEsriMarkerShape( const QString &style )
187{
188 if ( style == "esriSMSCircle"_L1 )
190 else if ( style == "esriSMSCross"_L1 )
192 else if ( style == "esriSMSDiamond"_L1 )
194 else if ( style == "esriSMSSquare"_L1 )
196 else if ( style == "esriSMSX"_L1 )
198 else if ( style == "esriSMSTriangle"_L1 )
200 else
202}
203
204std::unique_ptr<QgsMarkerSymbol> QgsSymbolConverterEsriRest::parseEsriMarkerSymbolJson( const QVariantMap &symbolData )
205{
206 QColor fillColor = convertColor( symbolData.value( u"color"_s ) );
207 bool ok = false;
208 const double sizeInPoints = symbolData.value( u"size"_s ).toDouble( &ok );
209 if ( !ok )
210 return nullptr;
211 const double angleCCW = symbolData.value( u"angle"_s ).toDouble( &ok );
212 double angleCW = 0;
213 if ( ok )
214 angleCW = -angleCCW;
215
216 const Qgis::MarkerShape shape = parseEsriMarkerShape( symbolData.value( u"style"_s ).toString() );
217
218 const double xOffset = symbolData.value( u"xoffset"_s ).toDouble();
219 const double yOffset = symbolData.value( u"yoffset"_s ).toDouble();
220
221 const QVariantMap outlineData = symbolData.value( u"outline"_s ).toMap();
222 const QColor lineColor = convertColor( outlineData.value( u"color"_s ) );
223 const Qt::PenStyle penStyle = convertLineStyle( outlineData.value( u"style"_s ).toString() );
224 double penWidthInPoints = outlineData.value( u"width"_s ).toDouble( &ok );
225
226 QgsSymbolLayerList layers;
227 auto markerLayer = std::make_unique< QgsSimpleMarkerSymbolLayer >( shape, sizeInPoints, angleCW, Qgis::ScaleMethod::ScaleArea, fillColor, lineColor );
228 markerLayer->setSizeUnit( Qgis::RenderUnit::Points );
229 markerLayer->setStrokeWidthUnit( Qgis::RenderUnit::Points );
230 markerLayer->setStrokeStyle( penStyle );
231 markerLayer->setStrokeWidth( penWidthInPoints );
232 markerLayer->setOffset( QPointF( xOffset, yOffset ) );
233 markerLayer->setOffsetUnit( Qgis::RenderUnit::Points );
234 layers.append( markerLayer.release() );
235
236 auto symbol = std::make_unique< QgsMarkerSymbol >( layers );
237 return symbol;
238}
239
240std::unique_ptr<QgsMarkerSymbol> QgsSymbolConverterEsriRest::parseEsriPictureMarkerSymbolJson( const QVariantMap &symbolData )
241{
242 bool ok = false;
243 const double widthInPixels = symbolData.value( u"width"_s ).toInt( &ok );
244 if ( !ok )
245 return nullptr;
246 const double heightInPixels = symbolData.value( u"height"_s ).toInt( &ok );
247 if ( !ok )
248 return nullptr;
249
250 const double angleCCW = symbolData.value( u"angle"_s ).toDouble( &ok );
251 double angleCW = 0;
252 if ( ok )
253 angleCW = -angleCCW;
254
255 const double xOffset = symbolData.value( u"xoffset"_s ).toDouble();
256 const double yOffset = symbolData.value( u"yoffset"_s ).toDouble();
257
258 QString symbolPath( symbolData.value( u"imageData"_s ).toString() );
259 symbolPath.prepend( "base64:"_L1 );
260
261 QgsSymbolLayerList layers;
262 auto markerLayer = std::make_unique< QgsRasterMarkerSymbolLayer >( symbolPath, widthInPixels, angleCW, Qgis::ScaleMethod::ScaleArea );
263 markerLayer->setSizeUnit( Qgis::RenderUnit::Points );
264
265 // only change the default aspect ratio if the server height setting requires this
266 if ( !qgsDoubleNear( static_cast< double >( heightInPixels ) / widthInPixels, markerLayer->defaultAspectRatio() ) )
267 markerLayer->setFixedAspectRatio( static_cast< double >( heightInPixels ) / widthInPixels );
268
269 markerLayer->setOffset( QPointF( xOffset, yOffset ) );
270 markerLayer->setOffsetUnit( Qgis::RenderUnit::Points );
271 layers.append( markerLayer.release() );
272
273 auto symbol = std::make_unique< QgsMarkerSymbol >( layers );
274 return symbol;
275}
276
277std::unique_ptr<QgsMarkerSymbol> QgsSymbolConverterEsriRest::parseEsriTextMarkerSymbolJson( const QVariantMap &symbolData )
278{
279 QgsSymbolLayerList layers;
280
281 const QString fontFamily = symbolData.value( u"font"_s ).toMap().value( u"family"_s ).toString();
282 const QString chr = symbolData.value( u"text"_s ).toString();
283 const double pointSize = symbolData.value( u"font"_s ).toMap().value( u"size"_s ).toDouble();
284 const QColor color = convertColor( symbolData.value( u"color"_s ) );
285 const double esriAngle = symbolData.value( u"angle"_s ).toDouble();
286 const double angle = 90.0 - esriAngle;
287
288 auto markerLayer = std::make_unique< QgsFontMarkerSymbolLayer >( fontFamily, chr, pointSize, color, angle );
289
290 QColor strokeColor = convertColor( symbolData.value( u"borderLineColor"_s ) );
291 markerLayer->setStrokeColor( strokeColor );
292
293 double borderLineSize = symbolData.value( u"borderLineSize"_s ).toDouble();
294 markerLayer->setStrokeWidth( borderLineSize );
295
296 const QString fontStyle = symbolData.value( u"font"_s ).toMap().value( u"style"_s ).toString();
297 markerLayer->setFontStyle( fontStyle );
298
299 double xOffset = symbolData.value( u"xoffset"_s ).toDouble();
300 double yOffset = symbolData.value( u"yoffset"_s ).toDouble();
301
302 markerLayer->setOffset( QPointF( xOffset, yOffset ) );
303 markerLayer->setOffsetUnit( Qgis::RenderUnit::Points );
304
305 markerLayer->setSizeUnit( Qgis::RenderUnit::Points );
306 markerLayer->setStrokeWidthUnit( Qgis::RenderUnit::Points );
307
310
311 QString horizontalAnchorPoint = symbolData.value( u"horizontalAlignment"_s ).toString();
312 QString verticalAnchorPoint = symbolData.value( u"verticalAlignment"_s ).toString();
313
314 if ( horizontalAnchorPoint == QString( "center" ) )
315 {
317 }
318 else if ( horizontalAnchorPoint == QString( "left" ) )
319 {
321 }
322 else if ( horizontalAnchorPoint == QString( "right" ) )
323 {
325 }
326
327 if ( verticalAnchorPoint == QString( "center" ) )
328 {
330 }
331 else if ( verticalAnchorPoint == QString( "top" ) )
332 {
334 }
335 else if ( verticalAnchorPoint == QString( "bottom" ) )
336 {
338 }
339
340 markerLayer->setHorizontalAnchorPoint( hAlign );
341 markerLayer->setVerticalAnchorPoint( vAlign );
342
343 layers.append( markerLayer.release() );
344
345 auto symbol = std::make_unique< QgsMarkerSymbol >( layers );
346 return symbol;
347}
348
349QColor QgsSymbolConverterEsriRest::convertColor( const QVariant &colorData )
350{
351 const QVariantList colorParts = colorData.toList();
352 if ( colorParts.count() < 4 )
353 return QColor();
354
355 const int red = colorParts.at( 0 ).toInt();
356 const int green = colorParts.at( 1 ).toInt();
357 const int blue = colorParts.at( 2 ).toInt();
358 const int alpha = colorParts.at( 3 ).toInt();
359 return QColor( red, green, blue, alpha );
360}
361
362Qt::PenStyle QgsSymbolConverterEsriRest::convertLineStyle( const QString &style )
363{
364 if ( style == "esriSLSSolid"_L1 )
365 return Qt::SolidLine;
366 else if ( style == "esriSLSDash"_L1 )
367 return Qt::DashLine;
368 else if ( style == "esriSLSDashDot"_L1 )
369 return Qt::DashDotLine;
370 else if ( style == "esriSLSDashDotDot"_L1 )
371 return Qt::DashDotDotLine;
372 else if ( style == "esriSLSDot"_L1 )
373 return Qt::DotLine;
374 else if ( style == "esriSLSNull"_L1 )
375 return Qt::NoPen;
376 else
377 return Qt::SolidLine;
378}
379
380Qt::BrushStyle QgsSymbolConverterEsriRest::convertFillStyle( const QString &style )
381{
382 if ( style == "esriSFSBackwardDiagonal"_L1 )
383 return Qt::BDiagPattern;
384 else if ( style == "esriSFSCross"_L1 )
385 return Qt::CrossPattern;
386 else if ( style == "esriSFSDiagonalCross"_L1 )
387 return Qt::DiagCrossPattern;
388 else if ( style == "esriSFSForwardDiagonal"_L1 )
389 return Qt::FDiagPattern;
390 else if ( style == "esriSFSHorizontal"_L1 )
391 return Qt::HorPattern;
392 else if ( style == "esriSFSNull"_L1 )
393 return Qt::NoBrush;
394 else if ( style == "esriSFSSolid"_L1 )
395 return Qt::SolidPattern;
396 else if ( style == "esriSFSVertical"_L1 )
397 return Qt::VerPattern;
398 else
399 return Qt::SolidPattern;
400}
@ ScaleArea
Calculate scale by the area.
Definition qgis.h:651
MarkerShape
Marker shapes.
Definition qgis.h:3246
@ Circle
Circle.
Definition qgis.h:3255
@ Triangle
Triangle.
Definition qgis.h:3251
@ Cross2
Rotated cross (lines only), 'x' shape.
Definition qgis.h:3258
@ Diamond
Diamond.
Definition qgis.h:3248
@ Square
Square.
Definition qgis.h:3247
@ Cross
Cross (lines only).
Definition qgis.h:3256
VerticalAnchorPoint
Marker symbol vertical anchor points.
Definition qgis.h:861
@ Bottom
Align to bottom of symbol.
Definition qgis.h:864
@ Center
Align to vertical center of symbol.
Definition qgis.h:863
@ Top
Align to top of symbol.
Definition qgis.h:862
QFlags< SymbolConverterCapability > SymbolConverterCapabilities
Symbol converter capabilities.
Definition qgis.h:824
@ Points
Points (e.g., for font sizes).
Definition qgis.h:5444
@ ReadSymbol
Allows reading symbols from variants.
Definition qgis.h:813
HorizontalAnchorPoint
Marker symbol horizontal anchor points.
Definition qgis.h:847
@ Center
Align to horizontal center of symbol.
Definition qgis.h:849
@ Right
Align to right side of symbol.
Definition qgis.h:850
@ Left
Align to left side of symbol.
Definition qgis.h:848
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
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:7077
QList< QgsSymbolLayer * > QgsSymbolLayerList
Definition qgssymbol.h:30