QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
qgsarcgisrestutils.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsarcgisrestutils.cpp
3  ----------------------
4  begin : Nov 25, 2015
5  copyright : (C) 2015 by Sandro Mani
6  email : manisandro@gmail.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 #include "qgsarcgisrestutils.h"
17 #include "qgsfields.h"
18 #include "qgslogger.h"
19 #include "qgsrectangle.h"
20 #include "qgspallabeling.h"
21 #include "qgssymbol.h"
22 #include "qgssymbollayer.h"
23 #include "qgslinesymbollayer.h"
24 #include "qgsfillsymbollayer.h"
25 #include "qgsrenderer.h"
26 #include "qgsrulebasedlabeling.h"
29 #include "qgsvectorlayerlabeling.h"
30 #include "qgscircularstring.h"
31 #include "qgsmulticurve.h"
32 #include "qgspolygon.h"
33 #include "qgslinestring.h"
34 #include "qgscurve.h"
35 #include "qgsgeometryengine.h"
36 #include "qgsmultisurface.h"
37 #include "qgsmultipoint.h"
38 #include <QRegularExpression>
39 
40 
41 QVariant::Type QgsArcGisRestUtils::convertFieldType( const QString &esriFieldType )
42 {
43  if ( esriFieldType == QLatin1String( "esriFieldTypeInteger" ) )
44  return QVariant::LongLong;
45  if ( esriFieldType == QLatin1String( "esriFieldTypeSmallInteger" ) )
46  return QVariant::Int;
47  if ( esriFieldType == QLatin1String( "esriFieldTypeDouble" ) )
48  return QVariant::Double;
49  if ( esriFieldType == QLatin1String( "esriFieldTypeSingle" ) )
50  return QVariant::Double;
51  if ( esriFieldType == QLatin1String( "esriFieldTypeString" ) )
52  return QVariant::String;
53  if ( esriFieldType == QLatin1String( "esriFieldTypeDate" ) )
54  return QVariant::DateTime;
55  if ( esriFieldType == QLatin1String( "esriFieldTypeGeometry" ) )
56  return QVariant::Invalid; // Geometry column should not appear as field
57  if ( esriFieldType == QLatin1String( "esriFieldTypeOID" ) )
58  return QVariant::LongLong;
59  if ( esriFieldType == QLatin1String( "esriFieldTypeBlob" ) )
60  return QVariant::ByteArray;
61  if ( esriFieldType == QLatin1String( "esriFieldTypeGlobalID" ) )
62  return QVariant::String;
63  if ( esriFieldType == QLatin1String( "esriFieldTypeRaster" ) )
64  return QVariant::ByteArray;
65  if ( esriFieldType == QLatin1String( "esriFieldTypeGUID" ) )
66  return QVariant::String;
67  if ( esriFieldType == QLatin1String( "esriFieldTypeXML" ) )
68  return QVariant::String;
69  return QVariant::Invalid;
70 }
71 
73 {
74  // http://resources.arcgis.com/en/help/arcobjects-cpp/componenthelp/index.html#//000w0000001p000000
75  if ( esriGeometryType == QLatin1String( "esriGeometryNull" ) )
76  return QgsWkbTypes::Unknown;
77  else if ( esriGeometryType == QLatin1String( "esriGeometryPoint" ) )
78  return QgsWkbTypes::Point;
79  else if ( esriGeometryType == QLatin1String( "esriGeometryMultipoint" ) )
81  else if ( esriGeometryType == QLatin1String( "esriGeometryPolyline" ) )
83  else if ( esriGeometryType == QLatin1String( "esriGeometryPolygon" ) )
85  else if ( esriGeometryType == QLatin1String( "esriGeometryEnvelope" ) )
86  return QgsWkbTypes::Polygon;
87  // Unsupported (either by qgis, or format unspecified by the specification)
88  // esriGeometryCircularArc
89  // esriGeometryEllipticArc
90  // esriGeometryBezier3Curve
91  // esriGeometryPath
92  // esriGeometryRing
93  // esriGeometryLine
94  // esriGeometryAny
95  // esriGeometryMultiPatch
96  // esriGeometryTriangleStrip
97  // esriGeometryTriangleFan
98  // esriGeometryRay
99  // esriGeometrySphere
100  // esriGeometryTriangles
101  // esriGeometryBag
102  return QgsWkbTypes::Unknown;
103 }
104 
105 std::unique_ptr< QgsPoint > QgsArcGisRestUtils::convertPoint( const QVariantList &coordList, QgsWkbTypes::Type pointType )
106 {
107  int nCoords = coordList.size();
108  if ( nCoords < 2 )
109  return nullptr;
110  bool xok = false, yok = false;
111  double x = coordList[0].toDouble( &xok );
112  double y = coordList[1].toDouble( &yok );
113  if ( !xok || !yok )
114  return nullptr;
115  double z = nCoords >= 3 ? coordList[2].toDouble() : 0;
116  double m = nCoords >= 4 ? coordList[3].toDouble() : 0;
117  return qgis::make_unique< QgsPoint >( pointType, x, y, z, m );
118 }
119 
120 std::unique_ptr< QgsCircularString > QgsArcGisRestUtils::convertCircularString( const QVariantMap &curveData, QgsWkbTypes::Type pointType, const QgsPoint &startPoint )
121 {
122  const QVariantList coordsList = curveData[QStringLiteral( "c" )].toList();
123  if ( coordsList.isEmpty() )
124  return nullptr;
125  QVector<QgsPoint> points;
126  points.append( startPoint );
127  for ( const QVariant &coordData : coordsList )
128  {
129  std::unique_ptr< QgsPoint > point( convertPoint( coordData.toList(), pointType ) );
130  if ( !point )
131  {
132  return nullptr;
133  }
134  points.append( *point );
135  }
136  std::unique_ptr< QgsCircularString > curve = qgis::make_unique< QgsCircularString> ();
137  curve->setPoints( points );
138  return curve;
139 }
140 
141 std::unique_ptr< QgsCompoundCurve > QgsArcGisRestUtils::convertCompoundCurve( const QVariantList &curvesList, QgsWkbTypes::Type pointType )
142 {
143  // [[6,3],[5,3],{"b":[[3,2],[6,1],[2,4]]},[1,2],{"c": [[3,3],[1,4]]}]
144  std::unique_ptr< QgsCompoundCurve > compoundCurve = qgis::make_unique< QgsCompoundCurve >();
145  QgsLineString *lineString = new QgsLineString();
146  compoundCurve->addCurve( lineString );
147  for ( const QVariant &curveData : curvesList )
148  {
149  if ( curveData.type() == QVariant::List )
150  {
151  std::unique_ptr< QgsPoint > point( convertPoint( curveData.toList(), pointType ) );
152  if ( !point )
153  {
154  return nullptr;
155  }
156  lineString->addVertex( *point );
157  }
158  else if ( curveData.type() == QVariant::Map )
159  {
160  // The last point of the linestring is the start point of this circular string
161  std::unique_ptr< QgsCircularString > circularString( convertCircularString( curveData.toMap(), pointType, lineString->endPoint() ) );
162  if ( !circularString )
163  {
164  return nullptr;
165  }
166 
167  // If the previous curve had less than two points, remove it
168  if ( compoundCurve->curveAt( compoundCurve->nCurves() - 1 )->nCoordinates() < 2 )
169  compoundCurve->removeCurve( compoundCurve->nCurves() - 1 );
170 
171  const QgsPoint endPointCircularString = circularString->endPoint();
172  compoundCurve->addCurve( circularString.release() );
173 
174  // Prepare a new line string
175  lineString = new QgsLineString;
176  compoundCurve->addCurve( lineString );
177  lineString->addVertex( endPointCircularString );
178  }
179  }
180  return compoundCurve;
181 }
182 
183 std::unique_ptr< QgsPoint > QgsArcGisRestUtils::convertGeometryPoint( const QVariantMap &geometryData, QgsWkbTypes::Type pointType )
184 {
185  // {"x" : <x>, "y" : <y>, "z" : <z>, "m" : <m>}
186  bool xok = false, yok = false;
187  double x = geometryData[QStringLiteral( "x" )].toDouble( &xok );
188  double y = geometryData[QStringLiteral( "y" )].toDouble( &yok );
189  if ( !xok || !yok )
190  return nullptr;
191  double z = geometryData[QStringLiteral( "z" )].toDouble();
192  double m = geometryData[QStringLiteral( "m" )].toDouble();
193  return qgis::make_unique< QgsPoint >( pointType, x, y, z, m );
194 }
195 
196 std::unique_ptr< QgsMultiPoint > QgsArcGisRestUtils::convertMultiPoint( const QVariantMap &geometryData, QgsWkbTypes::Type pointType )
197 {
198  // {"points" : [[ <x1>, <y1>, <z1>, <m1> ] , [ <x2>, <y2>, <z2>, <m2> ], ... ]}
199  const QVariantList coordsList = geometryData[QStringLiteral( "points" )].toList();
200 
201  std::unique_ptr< QgsMultiPoint > multiPoint = qgis::make_unique< QgsMultiPoint >();
202  multiPoint->reserve( coordsList.size() );
203  for ( const QVariant &coordData : coordsList )
204  {
205  const QVariantList coordList = coordData.toList();
206  std::unique_ptr< QgsPoint > p = convertPoint( coordList, pointType );
207  if ( !p )
208  {
209  continue;
210  }
211  multiPoint->addGeometry( p.release() );
212  }
213 
214  // second chance -- sometimes layers are reported as multipoint but features have single
215  // point geometries. Silently handle this and upgrade to multipoint.
216  std::unique_ptr< QgsPoint > p = convertGeometryPoint( geometryData, pointType );
217  if ( p )
218  multiPoint->addGeometry( p.release() );
219 
220  if ( multiPoint->numGeometries() == 0 )
221  {
222  // didn't find any points, so reset geometry to null
223  multiPoint.reset();
224  }
225  return multiPoint;
226 }
227 
228 std::unique_ptr< QgsMultiCurve > QgsArcGisRestUtils::convertGeometryPolyline( const QVariantMap &geometryData, QgsWkbTypes::Type pointType )
229 {
230  // {"curvePaths": [[[0,0], {"c": [[3,3],[1,4]]} ]]}
231  QVariantList pathsList;
232  if ( geometryData[QStringLiteral( "paths" )].isValid() )
233  pathsList = geometryData[QStringLiteral( "paths" )].toList();
234  else if ( geometryData[QStringLiteral( "curvePaths" )].isValid() )
235  pathsList = geometryData[QStringLiteral( "curvePaths" )].toList();
236  if ( pathsList.isEmpty() )
237  return nullptr;
238  std::unique_ptr< QgsMultiCurve > multiCurve = qgis::make_unique< QgsMultiCurve >();
239  multiCurve->reserve( pathsList.size() );
240  for ( const QVariant &pathData : qgis::as_const( pathsList ) )
241  {
242  std::unique_ptr< QgsCompoundCurve > curve = convertCompoundCurve( pathData.toList(), pointType );
243  if ( !curve )
244  {
245  return nullptr;
246  }
247  multiCurve->addGeometry( curve.release() );
248  }
249  return multiCurve;
250 }
251 
252 std::unique_ptr< QgsMultiSurface > QgsArcGisRestUtils::convertGeometryPolygon( const QVariantMap &geometryData, QgsWkbTypes::Type pointType )
253 {
254  // {"curveRings": [[[0,0], {"c": [[3,3],[1,4]]} ]]}
255  QVariantList ringsList;
256  if ( geometryData[QStringLiteral( "rings" )].isValid() )
257  ringsList = geometryData[QStringLiteral( "rings" )].toList();
258  else if ( geometryData[QStringLiteral( "ringPaths" )].isValid() )
259  ringsList = geometryData[QStringLiteral( "ringPaths" )].toList();
260  if ( ringsList.isEmpty() )
261  return nullptr;
262 
263  QList< QgsCompoundCurve * > curves;
264  for ( int i = 0, n = ringsList.size(); i < n; ++i )
265  {
266  std::unique_ptr< QgsCompoundCurve > curve = convertCompoundCurve( ringsList[i].toList(), pointType );
267  if ( !curve )
268  {
269  continue;
270  }
271  curves.append( curve.release() );
272  }
273  if ( curves.count() == 0 )
274  return nullptr;
275 
276  std::sort( curves.begin(), curves.end(), []( const QgsCompoundCurve * a, const QgsCompoundCurve * b )->bool{ double a_area = 0.0; double b_area = 0.0; a->sumUpArea( a_area ); b->sumUpArea( b_area ); return std::abs( a_area ) > std::abs( b_area ); } );
277  std::unique_ptr< QgsMultiSurface > result = qgis::make_unique< QgsMultiSurface >();
278  result->reserve( curves.size() );
279  while ( !curves.isEmpty() )
280  {
281  QgsCompoundCurve *exterior = curves.takeFirst();
282  QgsCurvePolygon *newPolygon = new QgsCurvePolygon();
283  newPolygon->setExteriorRing( exterior );
284  std::unique_ptr<QgsGeometryEngine> engine( QgsGeometry::createGeometryEngine( newPolygon ) );
285  engine->prepareGeometry();
286 
287  QMutableListIterator< QgsCompoundCurve * > it( curves );
288  while ( it.hasNext() )
289  {
290  QgsCompoundCurve *curve = it.next();
291  QgsRectangle boundingBox = newPolygon->boundingBox();
292  if ( boundingBox.intersects( curve->boundingBox() ) )
293  {
294  QgsPoint point = curve->startPoint();
295  if ( engine->contains( &point ) )
296  {
297  newPolygon->addInteriorRing( curve );
298  it.remove();
299  engine.reset( QgsGeometry::createGeometryEngine( newPolygon ) );
300  engine->prepareGeometry();
301  }
302  }
303  }
304  result->addGeometry( newPolygon );
305  }
306  if ( result->numGeometries() == 0 )
307  return nullptr;
308 
309  return result;
310 }
311 
312 std::unique_ptr< QgsPolygon > QgsArcGisRestUtils::convertEnvelope( const QVariantMap &geometryData )
313 {
314  // {"xmin" : -109.55, "ymin" : 25.76, "xmax" : -86.39, "ymax" : 49.94}
315  bool xminOk = false, yminOk = false, xmaxOk = false, ymaxOk = false;
316  double xmin = geometryData[QStringLiteral( "xmin" )].toDouble( &xminOk );
317  double ymin = geometryData[QStringLiteral( "ymin" )].toDouble( &yminOk );
318  double xmax = geometryData[QStringLiteral( "xmax" )].toDouble( &xmaxOk );
319  double ymax = geometryData[QStringLiteral( "ymax" )].toDouble( &ymaxOk );
320  if ( !xminOk || !yminOk || !xmaxOk || !ymaxOk )
321  return nullptr;
322  std::unique_ptr< QgsLineString > ext = qgis::make_unique< QgsLineString> ();
323  ext->addVertex( QgsPoint( xmin, ymin ) );
324  ext->addVertex( QgsPoint( xmax, ymin ) );
325  ext->addVertex( QgsPoint( xmax, ymax ) );
326  ext->addVertex( QgsPoint( xmin, ymax ) );
327  ext->addVertex( QgsPoint( xmin, ymin ) );
328  std::unique_ptr< QgsPolygon > poly = qgis::make_unique< QgsPolygon >();
329  poly->setExteriorRing( ext.release() );
330  return poly;
331 }
332 
333 QgsAbstractGeometry *QgsArcGisRestUtils::convertGeometry( const QVariantMap &geometryData, const QString &esriGeometryType, bool readM, bool readZ, QgsCoordinateReferenceSystem *crs )
334 {
335  QgsWkbTypes::Type pointType = QgsWkbTypes::zmType( QgsWkbTypes::Point, readZ, readM );
336  if ( crs )
337  {
338  *crs = convertSpatialReference( geometryData[QStringLiteral( "spatialReference" )].toMap() );
339  }
340 
341  // http://resources.arcgis.com/en/help/arcgis-rest-api/index.html#/Geometry_Objects/02r3000000n1000000/
342  if ( esriGeometryType == QLatin1String( "esriGeometryNull" ) )
343  return nullptr;
344  else if ( esriGeometryType == QLatin1String( "esriGeometryPoint" ) )
345  return convertGeometryPoint( geometryData, pointType ).release();
346  else if ( esriGeometryType == QLatin1String( "esriGeometryMultipoint" ) )
347  return convertMultiPoint( geometryData, pointType ).release();
348  else if ( esriGeometryType == QLatin1String( "esriGeometryPolyline" ) )
349  return convertGeometryPolyline( geometryData, pointType ).release();
350  else if ( esriGeometryType == QLatin1String( "esriGeometryPolygon" ) )
351  return convertGeometryPolygon( geometryData, pointType ).release();
352  else if ( esriGeometryType == QLatin1String( "esriGeometryEnvelope" ) )
353  return convertEnvelope( geometryData ).release();
354  // Unsupported (either by qgis, or format unspecified by the specification)
355  // esriGeometryCircularArc
356  // esriGeometryEllipticArc
357  // esriGeometryBezier3Curve
358  // esriGeometryPath
359  // esriGeometryRing
360  // esriGeometryLine
361  // esriGeometryAny
362  // esriGeometryMultiPatch
363  // esriGeometryTriangleStrip
364  // esriGeometryTriangleFan
365  // esriGeometryRay
366  // esriGeometrySphere
367  // esriGeometryTriangles
368  // esriGeometryBag
369  return nullptr;
370 }
371 
373 {
374  QString spatialReference = spatialReferenceMap[QStringLiteral( "latestWkid" )].toString();
375  if ( spatialReference.isEmpty() )
376  spatialReference = spatialReferenceMap[QStringLiteral( "wkid" )].toString();
377  if ( spatialReference.isEmpty() )
378  spatialReference = spatialReferenceMap[QStringLiteral( "wkt" )].toString();
379  else
380  spatialReference = QStringLiteral( "EPSG:%1" ).arg( spatialReference );
382  crs.createFromString( spatialReference );
383  if ( !crs.isValid() )
384  {
385  // If not spatial reference, just use WGS84
386  crs.createFromString( QStringLiteral( "EPSG:4326" ) );
387  }
388  return crs;
389 }
390 
391 QgsSymbol *QgsArcGisRestUtils::convertSymbol( const QVariantMap &symbolData )
392 {
393  const QString type = symbolData.value( QStringLiteral( "type" ) ).toString();
394  if ( type == QLatin1String( "esriSMS" ) )
395  {
396  // marker symbol
397  return parseEsriMarkerSymbolJson( symbolData ).release();
398  }
399  else if ( type == QLatin1String( "esriSLS" ) )
400  {
401  // line symbol
402  return parseEsriLineSymbolJson( symbolData ).release();
403  }
404  else if ( type == QLatin1String( "esriSFS" ) )
405  {
406  // fill symbol
407  return parseEsriFillSymbolJson( symbolData ).release();
408  }
409  else if ( type == QLatin1String( "esriPFS" ) )
410  {
411  return parseEsriPictureFillSymbolJson( symbolData ).release();
412  }
413  else if ( type == QLatin1String( "esriPMS" ) )
414  {
415  // picture marker
416  return parseEsriPictureMarkerSymbolJson( symbolData ).release();
417  }
418  else if ( type == QLatin1String( "esriTS" ) )
419  {
420  // text symbol - not supported
421  return nullptr;
422  }
423  return nullptr;
424 }
425 
426 std::unique_ptr<QgsLineSymbol> QgsArcGisRestUtils::parseEsriLineSymbolJson( const QVariantMap &symbolData )
427 {
428  QColor lineColor = convertColor( symbolData.value( QStringLiteral( "color" ) ) );
429  if ( !lineColor.isValid() )
430  return nullptr;
431 
432  bool ok = false;
433  double widthInPoints = symbolData.value( QStringLiteral( "width" ) ).toDouble( &ok );
434  if ( !ok )
435  return nullptr;
436 
437  QgsSymbolLayerList layers;
438  Qt::PenStyle penStyle = convertLineStyle( symbolData.value( QStringLiteral( "style" ) ).toString() );
439  std::unique_ptr< QgsSimpleLineSymbolLayer > lineLayer = qgis::make_unique< QgsSimpleLineSymbolLayer >( lineColor, widthInPoints, penStyle );
440  lineLayer->setWidthUnit( QgsUnitTypes::RenderPoints );
441  layers.append( lineLayer.release() );
442 
443  std::unique_ptr< QgsLineSymbol > symbol = qgis::make_unique< QgsLineSymbol >( layers );
444  return symbol;
445 }
446 
447 std::unique_ptr<QgsFillSymbol> QgsArcGisRestUtils::parseEsriFillSymbolJson( const QVariantMap &symbolData )
448 {
449  QColor fillColor = convertColor( symbolData.value( QStringLiteral( "color" ) ) );
450  Qt::BrushStyle brushStyle = convertFillStyle( symbolData.value( QStringLiteral( "style" ) ).toString() );
451 
452  const QVariantMap outlineData = symbolData.value( QStringLiteral( "outline" ) ).toMap();
453  QColor lineColor = convertColor( outlineData.value( QStringLiteral( "color" ) ) );
454  Qt::PenStyle penStyle = convertLineStyle( outlineData.value( QStringLiteral( "style" ) ).toString() );
455  bool ok = false;
456  double penWidthInPoints = outlineData.value( QStringLiteral( "width" ) ).toDouble( &ok );
457 
458  QgsSymbolLayerList layers;
459  std::unique_ptr< QgsSimpleFillSymbolLayer > fillLayer = qgis::make_unique< QgsSimpleFillSymbolLayer >( fillColor, brushStyle, lineColor, penStyle, penWidthInPoints );
460  fillLayer->setStrokeWidthUnit( QgsUnitTypes::RenderPoints );
461  layers.append( fillLayer.release() );
462 
463  std::unique_ptr< QgsFillSymbol > symbol = qgis::make_unique< QgsFillSymbol >( layers );
464  return symbol;
465 }
466 
467 std::unique_ptr<QgsFillSymbol> QgsArcGisRestUtils::parseEsriPictureFillSymbolJson( const QVariantMap &symbolData )
468 {
469  bool ok = false;
470 
471  double widthInPixels = symbolData.value( QStringLiteral( "width" ) ).toInt( &ok );
472  if ( !ok )
473  return nullptr;
474 
475  const double xScale = symbolData.value( QStringLiteral( "xscale" ) ).toDouble( &ok );
476  if ( !qgsDoubleNear( xScale, 0.0 ) )
477  widthInPixels *= xScale;
478 
479  const double angleCCW = symbolData.value( QStringLiteral( "angle" ) ).toDouble( &ok );
480  double angleCW = 0;
481  if ( ok )
482  angleCW = -angleCCW;
483 
484  const double xOffset = symbolData.value( QStringLiteral( "xoffset" ) ).toDouble();
485  const double yOffset = symbolData.value( QStringLiteral( "yoffset" ) ).toDouble();
486 
487  QString symbolPath( symbolData.value( QStringLiteral( "imageData" ) ).toString() );
488  symbolPath.prepend( QLatin1String( "base64:" ) );
489 
490  QgsSymbolLayerList layers;
491  std::unique_ptr< QgsRasterFillSymbolLayer > fillLayer = qgis::make_unique< QgsRasterFillSymbolLayer >( symbolPath );
492  fillLayer->setWidth( widthInPixels );
493  fillLayer->setAngle( angleCW );
494  fillLayer->setWidthUnit( QgsUnitTypes::RenderPoints );
495  fillLayer->setOffset( QPointF( xOffset, yOffset ) );
496  fillLayer->setOffsetUnit( QgsUnitTypes::RenderPoints );
497  layers.append( fillLayer.release() );
498 
499  const QVariantMap outlineData = symbolData.value( QStringLiteral( "outline" ) ).toMap();
500  QColor lineColor = convertColor( outlineData.value( QStringLiteral( "color" ) ) );
501  Qt::PenStyle penStyle = convertLineStyle( outlineData.value( QStringLiteral( "style" ) ).toString() );
502  double penWidthInPoints = outlineData.value( QStringLiteral( "width" ) ).toDouble( &ok );
503 
504  std::unique_ptr< QgsSimpleLineSymbolLayer > lineLayer = qgis::make_unique< QgsSimpleLineSymbolLayer >( lineColor, penWidthInPoints, penStyle );
505  lineLayer->setWidthUnit( QgsUnitTypes::RenderPoints );
506  layers.append( lineLayer.release() );
507 
508  std::unique_ptr< QgsFillSymbol > symbol = qgis::make_unique< QgsFillSymbol >( layers );
509  return symbol;
510 }
511 
512 QgsSimpleMarkerSymbolLayerBase::Shape QgsArcGisRestUtils::parseEsriMarkerShape( const QString &style )
513 {
514  if ( style == QLatin1String( "esriSMSCircle" ) )
516  else if ( style == QLatin1String( "esriSMSCross" ) )
518  else if ( style == QLatin1String( "esriSMSDiamond" ) )
520  else if ( style == QLatin1String( "esriSMSSquare" ) )
522  else if ( style == QLatin1String( "esriSMSX" ) )
524  else if ( style == QLatin1String( "esriSMSTriangle" ) )
526  else
528 }
529 
530 std::unique_ptr<QgsMarkerSymbol> QgsArcGisRestUtils::parseEsriMarkerSymbolJson( const QVariantMap &symbolData )
531 {
532  QColor fillColor = convertColor( symbolData.value( QStringLiteral( "color" ) ) );
533  bool ok = false;
534  const double sizeInPoints = symbolData.value( QStringLiteral( "size" ) ).toDouble( &ok );
535  if ( !ok )
536  return nullptr;
537  const double angleCCW = symbolData.value( QStringLiteral( "angle" ) ).toDouble( &ok );
538  double angleCW = 0;
539  if ( ok )
540  angleCW = -angleCCW;
541 
542  QgsSimpleMarkerSymbolLayerBase::Shape shape = parseEsriMarkerShape( symbolData.value( QStringLiteral( "style" ) ).toString() );
543 
544  const double xOffset = symbolData.value( QStringLiteral( "xoffset" ) ).toDouble();
545  const double yOffset = symbolData.value( QStringLiteral( "yoffset" ) ).toDouble();
546 
547  const QVariantMap outlineData = symbolData.value( QStringLiteral( "outline" ) ).toMap();
548  QColor lineColor = convertColor( outlineData.value( QStringLiteral( "color" ) ) );
549  Qt::PenStyle penStyle = convertLineStyle( outlineData.value( QStringLiteral( "style" ) ).toString() );
550  double penWidthInPoints = outlineData.value( QStringLiteral( "width" ) ).toDouble( &ok );
551 
552  QgsSymbolLayerList layers;
553  std::unique_ptr< QgsSimpleMarkerSymbolLayer > markerLayer = qgis::make_unique< QgsSimpleMarkerSymbolLayer >( shape, sizeInPoints, angleCW, QgsSymbol::ScaleArea, fillColor, lineColor );
554  markerLayer->setSizeUnit( QgsUnitTypes::RenderPoints );
555  markerLayer->setStrokeWidthUnit( QgsUnitTypes::RenderPoints );
556  markerLayer->setStrokeStyle( penStyle );
557  markerLayer->setStrokeWidth( penWidthInPoints );
558  markerLayer->setOffset( QPointF( xOffset, yOffset ) );
559  markerLayer->setOffsetUnit( QgsUnitTypes::RenderPoints );
560  layers.append( markerLayer.release() );
561 
562  std::unique_ptr< QgsMarkerSymbol > symbol = qgis::make_unique< QgsMarkerSymbol >( layers );
563  return symbol;
564 }
565 
566 std::unique_ptr<QgsMarkerSymbol> QgsArcGisRestUtils::parseEsriPictureMarkerSymbolJson( const QVariantMap &symbolData )
567 {
568  bool ok = false;
569  const double widthInPixels = symbolData.value( QStringLiteral( "width" ) ).toInt( &ok );
570  if ( !ok )
571  return nullptr;
572  const double heightInPixels = symbolData.value( QStringLiteral( "height" ) ).toInt( &ok );
573  if ( !ok )
574  return nullptr;
575 
576  const double angleCCW = symbolData.value( QStringLiteral( "angle" ) ).toDouble( &ok );
577  double angleCW = 0;
578  if ( ok )
579  angleCW = -angleCCW;
580 
581  const double xOffset = symbolData.value( QStringLiteral( "xoffset" ) ).toDouble();
582  const double yOffset = symbolData.value( QStringLiteral( "yoffset" ) ).toDouble();
583 
584  //const QString contentType = symbolData.value( QStringLiteral( "contentType" ) ).toString();
585 
586  QString symbolPath( symbolData.value( QStringLiteral( "imageData" ) ).toString() );
587  symbolPath.prepend( QLatin1String( "base64:" ) );
588 
589  QgsSymbolLayerList layers;
590  std::unique_ptr< QgsRasterMarkerSymbolLayer > markerLayer = qgis::make_unique< QgsRasterMarkerSymbolLayer >( symbolPath, widthInPixels, angleCW, QgsSymbol::ScaleArea );
591  markerLayer->setSizeUnit( QgsUnitTypes::RenderPoints );
592 
593  // only change the default aspect ratio if the server height setting requires this
594  if ( !qgsDoubleNear( static_cast< double >( heightInPixels ) / widthInPixels, markerLayer->defaultAspectRatio() ) )
595  markerLayer->setFixedAspectRatio( static_cast< double >( heightInPixels ) / widthInPixels );
596 
597  markerLayer->setOffset( QPointF( xOffset, yOffset ) );
598  markerLayer->setOffsetUnit( QgsUnitTypes::RenderPoints );
599  layers.append( markerLayer.release() );
600 
601  std::unique_ptr< QgsMarkerSymbol > symbol = qgis::make_unique< QgsMarkerSymbol >( layers );
602  return symbol;
603 }
604 
606 {
607  if ( labelingData.empty() )
608  return nullptr;
609 
610  QgsRuleBasedLabeling::Rule *root = new QgsRuleBasedLabeling::Rule( new QgsPalLayerSettings(), 0, 0, QString(), QString(), false );
611  root->setActive( true );
612 
613  int i = 1;
614  for ( const QVariant &lbl : labelingData )
615  {
616  const QVariantMap labeling = lbl.toMap();
617 
618  QgsPalLayerSettings *settings = new QgsPalLayerSettings();
619  QgsTextFormat format;
620 
621  const QString placement = labeling.value( QStringLiteral( "labelPlacement" ) ).toString();
622  if ( placement == QLatin1String( "esriServerPointLabelPlacementAboveCenter" ) )
623  {
626  }
627  else if ( placement == QLatin1String( "esriServerPointLabelPlacementBelowCenter" ) )
628  {
631  }
632  else if ( placement == QLatin1String( "esriServerPointLabelPlacementCenterCenter" ) )
633  {
636  }
637  else if ( placement == QLatin1String( "esriServerPointLabelPlacementAboveLeft" ) )
638  {
641  }
642  else if ( placement == QLatin1String( "esriServerPointLabelPlacementBelowLeft" ) )
643  {
646  }
647  else if ( placement == QLatin1String( "esriServerPointLabelPlacementCenterLeft" ) )
648  {
651  }
652  else if ( placement == QLatin1String( "esriServerPointLabelPlacementAboveRight" ) )
653  {
656  }
657  else if ( placement == QLatin1String( "esriServerPointLabelPlacementBelowRight" ) )
658  {
661  }
662  else if ( placement == QLatin1String( "esriServerPointLabelPlacementCenterRight" ) )
663  {
666  }
667  else if ( placement == QLatin1String( "esriServerLinePlacementAboveAfter" ) ||
668  placement == QLatin1String( "esriServerLinePlacementAboveStart" ) ||
669  placement == QLatin1String( "esriServerLinePlacementAboveAlong" ) )
670  {
672  settings->lineSettings().setPlacementFlags( QgsLabeling::LinePlacementFlag::AboveLine | QgsLabeling::LinePlacementFlag::MapOrientation );
673  }
674  else if ( placement == QLatin1String( "esriServerLinePlacementBelowAfter" ) ||
675  placement == QLatin1String( "esriServerLinePlacementBelowStart" ) ||
676  placement == QLatin1String( "esriServerLinePlacementBelowAlong" ) )
677  {
679  settings->lineSettings().setPlacementFlags( QgsLabeling::LinePlacementFlag::BelowLine | QgsLabeling::LinePlacementFlag::MapOrientation );
680  }
681  else if ( placement == QLatin1String( "esriServerLinePlacementCenterAfter" ) ||
682  placement == QLatin1String( "esriServerLinePlacementCenterStart" ) ||
683  placement == QLatin1String( "esriServerLinePlacementCenterAlong" ) )
684  {
686  settings->lineSettings().setPlacementFlags( QgsLabeling::LinePlacementFlag::OnLine | QgsLabeling::LinePlacementFlag::MapOrientation );
687  }
688  else if ( placement == QLatin1String( "esriServerPolygonPlacementAlwaysHorizontal" ) )
689  {
691  }
692 
693  const double minScale = labeling.value( QStringLiteral( "minScale" ) ).toDouble();
694  const double maxScale = labeling.value( QStringLiteral( "maxScale" ) ).toDouble();
695 
696  QVariantMap symbol = labeling.value( QStringLiteral( "symbol" ) ).toMap();
697  format.setColor( convertColor( symbol.value( QStringLiteral( "color" ) ) ) );
698  const double haloSize = symbol.value( QStringLiteral( "haloSize" ) ).toDouble();
699  if ( !qgsDoubleNear( haloSize, 0.0 ) )
700  {
701  QgsTextBufferSettings buffer;
702  buffer.setEnabled( true );
703  buffer.setSize( haloSize );
705  buffer.setColor( convertColor( symbol.value( QStringLiteral( "haloColor" ) ) ) );
706  format.setBuffer( buffer );
707  }
708 
709  const QString fontFamily = symbol.value( QStringLiteral( "font" ) ).toMap().value( QStringLiteral( "family" ) ).toString();
710  const QString fontStyle = symbol.value( QStringLiteral( "font" ) ).toMap().value( QStringLiteral( "style" ) ).toString();
711  const QString fontWeight = symbol.value( QStringLiteral( "font" ) ).toMap().value( QStringLiteral( "weight" ) ).toString();
712  const int fontSize = symbol.value( QStringLiteral( "font" ) ).toMap().value( QStringLiteral( "size" ) ).toInt();
713  QFont font( fontFamily, fontSize );
714  font.setStyleName( fontStyle );
715  font.setWeight( fontWeight == QLatin1String( "bold" ) ? QFont::Bold : QFont::Normal );
716 
717  format.setFont( font );
718  format.setSize( fontSize );
720 
721  settings->setFormat( format );
722 
723  QString where = labeling.value( QStringLiteral( "where" ) ).toString();
724  QgsExpression exp( where );
725  // If the where clause isn't parsed as valid, don't use its
726  if ( !exp.isValid() )
727  where.clear();
728 
729  settings->fieldName = convertLabelingExpression( labeling.value( QStringLiteral( "labelExpression" ) ).toString() );
730  settings->isExpression = true;
731 
732  QgsRuleBasedLabeling::Rule *child = new QgsRuleBasedLabeling::Rule( settings, maxScale, minScale, where, QObject::tr( "ASF label %1" ).arg( i++ ), false );
733  child->setActive( true );
734  root->appendChild( child );
735  }
736 
737  return new QgsRuleBasedLabeling( root );
738 }
739 
740 QgsFeatureRenderer *QgsArcGisRestUtils::convertRenderer( const QVariantMap &rendererData )
741 {
742  const QString type = rendererData.value( QStringLiteral( "type" ) ).toString();
743  if ( type == QLatin1String( "simple" ) )
744  {
745  const QVariantMap symbolProps = rendererData.value( QStringLiteral( "symbol" ) ).toMap();
746  std::unique_ptr< QgsSymbol > symbol( convertSymbol( symbolProps ) );
747  if ( symbol )
748  return new QgsSingleSymbolRenderer( symbol.release() );
749  else
750  return nullptr;
751  }
752  else if ( type == QLatin1String( "uniqueValue" ) )
753  {
754  const QString field1 = rendererData.value( QStringLiteral( "field1" ) ).toString();
755  const QString field2 = rendererData.value( QStringLiteral( "field2" ) ).toString();
756  const QString field3 = rendererData.value( QStringLiteral( "field3" ) ).toString();
757  QString attribute;
758  if ( !field2.isEmpty() || !field3.isEmpty() )
759  {
760  const QString delimiter = rendererData.value( QStringLiteral( "fieldDelimiter" ) ).toString();
761  if ( !field3.isEmpty() )
762  {
763  attribute = QStringLiteral( "concat(\"%1\",'%2',\"%3\",'%4',\"%5\")" ).arg( field1, delimiter, field2, delimiter, field3 );
764  }
765  else
766  {
767  attribute = QStringLiteral( "concat(\"%1\",'%2',\"%3\")" ).arg( field1, delimiter, field2 );
768  }
769  }
770  else
771  {
772  attribute = field1;
773  }
774 
775  const QVariantList categories = rendererData.value( QStringLiteral( "uniqueValueInfos" ) ).toList();
776  QgsCategoryList categoryList;
777  for ( const QVariant &category : categories )
778  {
779  const QVariantMap categoryData = category.toMap();
780  const QString value = categoryData.value( QStringLiteral( "value" ) ).toString();
781  const QString label = categoryData.value( QStringLiteral( "label" ) ).toString();
782  std::unique_ptr< QgsSymbol > symbol( QgsArcGisRestUtils::convertSymbol( categoryData.value( QStringLiteral( "symbol" ) ).toMap() ) );
783  if ( symbol )
784  {
785  categoryList.append( QgsRendererCategory( value, symbol.release(), label ) );
786  }
787  }
788 
789  std::unique_ptr< QgsSymbol > defaultSymbol( convertSymbol( rendererData.value( QStringLiteral( "defaultSymbol" ) ).toMap() ) );
790  if ( defaultSymbol )
791  {
792  categoryList.append( QgsRendererCategory( QVariant(), defaultSymbol.release(), rendererData.value( QStringLiteral( "defaultLabel" ) ).toString() ) );
793  }
794 
795  if ( categoryList.empty() )
796  return nullptr;
797 
798  return new QgsCategorizedSymbolRenderer( attribute, categoryList );
799  }
800  else if ( type == QLatin1String( "classBreaks" ) )
801  {
802  // currently unsupported
803  return nullptr;
804  }
805  else if ( type == QLatin1String( "heatmap" ) )
806  {
807  // currently unsupported
808  return nullptr;
809  }
810  else if ( type == QLatin1String( "vectorField" ) )
811  {
812  // currently unsupported
813  return nullptr;
814  }
815  return nullptr;
816 }
817 
818 QString QgsArcGisRestUtils::convertLabelingExpression( const QString &string )
819 {
820  QString expression = string;
821 
822  // Replace a few ArcGIS token to QGIS equivalents
823  expression = expression.replace( QRegularExpression( "(?=([^\"\\\\]*(\\\\.|\"([^\"\\\\]*\\\\.)*[^\"\\\\]*\"))*[^\"]*$)(\\s|^)CONCAT(\\s|$)" ), QStringLiteral( "\\4||\\5" ) );
824  expression = expression.replace( QRegularExpression( "(?=([^\"\\\\]*(\\\\.|\"([^\"\\\\]*\\\\.)*[^\"\\\\]*\"))*[^\"]*$)(\\s|^)NEWLINE(\\s|$)" ), QStringLiteral( "\\4'\\n'\\5" ) );
825 
826  // ArcGIS's double quotes are single quotes in QGIS
827  expression = expression.replace( QRegularExpression( "\"(.*?(?<!\\\\))\"" ), QStringLiteral( "'\\1'" ) );
828  expression = expression.replace( QRegularExpression( "\\\\\"" ), QStringLiteral( "\"" ) );
829 
830  // ArcGIS's square brakets are double quotes in QGIS
831  expression = expression.replace( QRegularExpression( "\\[([^]]*)\\]" ), QStringLiteral( "\"\\1\"" ) );
832 
833  return expression;
834 }
835 
836 QColor QgsArcGisRestUtils::convertColor( const QVariant &colorData )
837 {
838  const QVariantList colorParts = colorData.toList();
839  if ( colorParts.count() < 4 )
840  return QColor();
841 
842  int red = colorParts.at( 0 ).toInt();
843  int green = colorParts.at( 1 ).toInt();
844  int blue = colorParts.at( 2 ).toInt();
845  int alpha = colorParts.at( 3 ).toInt();
846  return QColor( red, green, blue, alpha );
847 }
848 
849 Qt::PenStyle QgsArcGisRestUtils::convertLineStyle( const QString &style )
850 {
851  if ( style == QLatin1String( "esriSLSSolid" ) )
852  return Qt::SolidLine;
853  else if ( style == QLatin1String( "esriSLSDash" ) )
854  return Qt::DashLine;
855  else if ( style == QLatin1String( "esriSLSDashDot" ) )
856  return Qt::DashDotLine;
857  else if ( style == QLatin1String( "esriSLSDashDotDot" ) )
858  return Qt::DashDotDotLine;
859  else if ( style == QLatin1String( "esriSLSDot" ) )
860  return Qt::DotLine;
861  else if ( style == QLatin1String( "esriSLSNull" ) )
862  return Qt::NoPen;
863  else
864  return Qt::SolidLine;
865 }
866 
867 Qt::BrushStyle QgsArcGisRestUtils::convertFillStyle( const QString &style )
868 {
869  if ( style == QLatin1String( "esriSFSBackwardDiagonal" ) )
870  return Qt::BDiagPattern;
871  else if ( style == QLatin1String( "esriSFSCross" ) )
872  return Qt::CrossPattern;
873  else if ( style == QLatin1String( "esriSFSDiagonalCross" ) )
874  return Qt::DiagCrossPattern;
875  else if ( style == QLatin1String( "esriSFSForwardDiagonal" ) )
876  return Qt::FDiagPattern;
877  else if ( style == QLatin1String( "esriSFSHorizontal" ) )
878  return Qt::HorPattern;
879  else if ( style == QLatin1String( "esriSFSNull" ) )
880  return Qt::NoBrush;
881  else if ( style == QLatin1String( "esriSFSSolid" ) )
882  return Qt::SolidPattern;
883  else if ( style == QLatin1String( "esriSFSVertical" ) )
884  return Qt::VerPattern;
885  else
886  return Qt::SolidPattern;
887 }
888 
889 QDateTime QgsArcGisRestUtils::convertDateTime( const QVariant &value )
890 {
891  if ( value.isNull() )
892  return QDateTime();
893  bool ok = false;
894  QDateTime dt = QDateTime::fromMSecsSinceEpoch( value.toLongLong( &ok ) );
895  if ( !ok )
896  {
897  QgsDebugMsg( QStringLiteral( "Invalid value %1 for datetime" ).arg( value.toString() ) );
898  return QDateTime();
899  }
900  else
901  return dt;
902 }
Abstract base class for all geometries.
Abstract base class - its implementations define different approaches to the labeling of a vector lay...
static QgsCoordinateReferenceSystem convertSpatialReference(const QVariantMap &spatialReferenceMap)
Converts a spatial reference JSON definition to a QgsCoordinateReferenceSystem value.
static QDateTime convertDateTime(const QVariant &value)
Converts a date time value to a QDateTime.
static QString convertLabelingExpression(const QString &string)
Converts an ESRI labeling expression to a QGIS expression string.
static QgsSymbol * convertSymbol(const QVariantMap &definition)
Converts a symbol JSON definition to a QgsSymbol.
static QgsAbstractGeometry * convertGeometry(const QVariantMap &geometry, const QString &esriGeometryType, bool hasM, bool hasZ, QgsCoordinateReferenceSystem *crs=nullptr)
Converts an ESRI REST geometry JSON definition to a QgsAbstractGeometry.
static Qt::PenStyle convertLineStyle(const QString &style)
Converts an ESRI line style to a Qt pen style.
static QgsFeatureRenderer * convertRenderer(const QVariantMap &rendererData)
Converts renderer JSON data to an equivalent QgsFeatureRenderer.
static Qt::BrushStyle convertFillStyle(const QString &style)
Converts an ESRI fill style to a Qt brush style.
static QVariant::Type convertFieldType(const QString &type)
Converts an ESRI REST field type to a QVariant type.
static QgsWkbTypes::Type convertGeometryType(const QString &type)
Converts an ESRI REST geometry type to a WKB type.
static QgsAbstractVectorLayerLabeling * convertLabeling(const QVariantList &data)
Converts labeling JSON data to an equivalent QGIS vector labeling.
static QColor convertColor(const QVariant &data)
Converts ESRI JSON color data to a QColor object.
Compound curve geometry type.
QgsPoint startPoint() const override SIP_HOLDGIL
Returns the starting point of the curve.
This class represents a coordinate reference system (CRS).
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
bool createFromString(const QString &definition)
Set up this CRS from a string definition.
Curve polygon geometry type.
virtual void setExteriorRing(QgsCurve *ring)
Sets the exterior ring of the polygon.
virtual void addInteriorRing(QgsCurve *ring)
Adds an interior ring to the geometry (takes ownership)
QgsRectangle boundingBox() const override
Returns the minimal bounding box for the geometry.
Definition: qgscurve.cpp:202
Class for parsing and evaluation of expressions (formerly called "search strings").
bool isValid() const
Checks if this expression is valid.
static QgsGeometryEngine * createGeometryEngine(const QgsAbstractGeometry *geometry)
Creates and returns a new geometry engine.
void setPlacementFlags(QgsLabeling::LinePlacementFlags flags)
Returns the line placement flags, which dictate how line labels can be placed above or below the line...
Line string geometry type, with support for z-dimension and m-values.
Definition: qgslinestring.h:44
QgsPoint endPoint() const override SIP_HOLDGIL
Returns the end point of the curve.
void addVertex(const QgsPoint &pt)
Adds a new vertex to the end of the line string.
Contains settings for how a map layer will be labeled.
void setFormat(const QgsTextFormat &format)
Sets the label text formatting settings, e.g., font settings, buffer settings, etc.
QuadrantPosition quadOffset
Sets the quadrant in which to offset labels from feature.
@ Horizontal
Arranges horizontal candidates scattered throughout a polygon feature. Applies to polygon layers only...
@ OverPoint
Arranges candidates over a point (or centroid of a polygon), or at a preset offset from the point....
@ Line
Arranges candidates parallel to a generalised line representing the feature or parallel to a polygon'...
const QgsLabelLineSettings & lineSettings() const
Returns the label line settings, which contain settings related to how the label engine places and fo...
bool isExpression
true if this label is made from a expression string, e.g., FieldName || 'mm'
QString fieldName
Name of field (or an expression) to use for label text.
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:38
A rectangle specified with double values.
Definition: qgsrectangle.h:42
bool intersects(const QgsRectangle &rect) const
Returns true when rectangle intersects with other rectangle.
Definition: qgsrectangle.h:328
Represents an individual category (class) from a QgsCategorizedSymbolRenderer.
A child rule for QgsRuleBasedLabeling.
void setActive(bool state)
Sets if this rule is active.
void appendChild(QgsRuleBasedLabeling::Rule *rule)
add child rule, take ownership, sets this as parent
Rule based labeling for a vector layer.
@ Cross2
Rotated cross (lines only), "x" shape.
QgsRectangle boundingBox() const override
Returns the minimal bounding box for the geometry.
Definition: qgssurface.h:43
Abstract base class for all rendered symbols.
Definition: qgssymbol.h:65
@ ScaleArea
Calculate scale by the area.
Definition: qgssymbol.h:99
Container for settings relating to a text buffer.
void setColor(const QColor &color)
Sets the color for the buffer.
void setEnabled(bool enabled)
Sets whether the text buffer will be drawn.
void setSizeUnit(QgsUnitTypes::RenderUnit unit)
Sets the units used for the buffer size.
void setSize(double size)
Sets the size of the buffer.
Container for all settings relating to text rendering.
Definition: qgstextformat.h:41
void setColor(const QColor &color)
Sets the color that text will be rendered in.
void setSize(double size)
Sets the size for rendered text.
void setFont(const QFont &font)
Sets the font used for rendering text.
void setBuffer(const QgsTextBufferSettings &bufferSettings)
Sets the text's buffer settings.
void setSizeUnit(QgsUnitTypes::RenderUnit unit)
Sets the units for the size of rendered text.
@ RenderPoints
Points (e.g., for font sizes)
Definition: qgsunittypes.h:172
Type
The WKB type describes the number of dimensions a geometry has.
Definition: qgswkbtypes.h:70
static Type zmType(Type type, bool hasZ, bool hasM) SIP_HOLDGIL
Returns the modified input geometry type according to hasZ / hasM.
Definition: qgswkbtypes.h:801
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:316
QList< QgsRendererCategory > QgsCategoryList
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QList< QgsSymbolLayer * > QgsSymbolLayerList
Definition: qgssymbol.h:54
const QgsCoordinateReferenceSystem & crs