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