QGIS API Documentation 3.99.0-Master (c22de0620c0)
Loading...
Searching...
No Matches
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
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
19#include "qgscircularstring.h"
26#include "qgscolorrampimpl.h"
27#include "qgscurve.h"
28#include "qgsfields.h"
29#include "qgsfillsymbol.h"
30#include "qgsfillsymbollayer.h"
31#include "qgsgeometryengine.h"
33#include "qgslinestring.h"
34#include "qgslinesymbol.h"
35#include "qgslinesymbollayer.h"
36#include "qgslogger.h"
37#include "qgsmarkersymbol.h"
39#include "qgsmulticurve.h"
40#include "qgsmultilinestring.h"
41#include "qgsmultipoint.h"
42#include "qgsmultipolygon.h"
43#include "qgsmultisurface.h"
44#include "qgspallabeling.h"
45#include "qgspolygon.h"
47#include "qgsrectangle.h"
48#include "qgsrenderer.h"
51#include "qgssymbol.h"
52#include "qgssymbollayer.h"
53#include "qgsvariantutils.h"
55
56#include <QRegularExpression>
57#include <QString>
58#include <QUrl>
59
60#include "moc_qgsarcgisrestutils.cpp"
61
62using namespace Qt::StringLiterals;
63
64QMetaType::Type QgsArcGisRestUtils::convertFieldType( const QString &esriFieldType )
65{
66 if ( esriFieldType == "esriFieldTypeInteger"_L1 )
67 return QMetaType::Type::LongLong;
68 if ( esriFieldType == "esriFieldTypeSmallInteger"_L1 )
69 return QMetaType::Type::Int;
70 if ( esriFieldType == "esriFieldTypeDouble"_L1 )
71 return QMetaType::Type::Double;
72 if ( esriFieldType == "esriFieldTypeSingle"_L1 )
73 return QMetaType::Type::Double;
74 if ( esriFieldType == "esriFieldTypeString"_L1 )
75 return QMetaType::Type::QString;
76 if ( esriFieldType == "esriFieldTypeDate"_L1 )
77 return QMetaType::Type::QDateTime;
78 if ( esriFieldType == "esriFieldTypeGeometry"_L1 )
79 return QMetaType::Type::UnknownType; // Geometry column should not appear as field
80 if ( esriFieldType == "esriFieldTypeOID"_L1 )
81 return QMetaType::Type::LongLong;
82 if ( esriFieldType == "esriFieldTypeBlob"_L1 )
83 return QMetaType::Type::QByteArray;
84 if ( esriFieldType == "esriFieldTypeGlobalID"_L1 )
85 return QMetaType::Type::QString;
86 if ( esriFieldType == "esriFieldTypeRaster"_L1 )
87 return QMetaType::Type::QByteArray;
88 if ( esriFieldType == "esriFieldTypeGUID"_L1 )
89 return QMetaType::Type::QString;
90 if ( esriFieldType == "esriFieldTypeXML"_L1 )
91 return QMetaType::Type::QString;
92 return QMetaType::Type::UnknownType;
93}
94
96{
97 // http://resources.arcgis.com/en/help/arcobjects-cpp/componenthelp/index.html#//000w0000001p000000
98 if ( esriGeometryType == "esriGeometryNull"_L1 )
100 else if ( esriGeometryType == "esriGeometryPoint"_L1 )
102 else if ( esriGeometryType == "esriGeometryMultipoint"_L1 )
104 else if ( esriGeometryType == "esriGeometryPolyline"_L1 )
106 else if ( esriGeometryType == "esriGeometryPolygon"_L1 )
108 else if ( esriGeometryType == "esriGeometryEnvelope"_L1 )
110 // Unsupported (either by qgis, or format unspecified by the specification)
111 // esriGeometryCircularArc
112 // esriGeometryEllipticArc
113 // esriGeometryBezier3Curve
114 // esriGeometryPath
115 // esriGeometryRing
116 // esriGeometryLine
117 // esriGeometryAny
118 // esriGeometryMultiPatch
119 // esriGeometryTriangleStrip
120 // esriGeometryTriangleFan
121 // esriGeometryRay
122 // esriGeometrySphere
123 // esriGeometryTriangles
124 // esriGeometryBag
126}
127
128std::unique_ptr< QgsPoint > QgsArcGisRestUtils::convertPoint( const QVariantList &coordList, Qgis::WkbType pointType )
129{
130 const int nCoords = static_cast< int >( coordList.size() );
131 if ( nCoords < 2 )
132 return nullptr;
133 bool xok = false, yok = false;
134 const double x = coordList[0].toDouble( &xok );
135 const double y = coordList[1].toDouble( &yok );
136 if ( !xok || !yok )
137 return nullptr;
138 const bool hasZ = QgsWkbTypes::hasZ( pointType );
139 const double z = hasZ && nCoords >= 3 ? coordList[2].toDouble() : std::numeric_limits< double >::quiet_NaN();
140
141 // if point has just M but not Z, then the point dimension list will only have X, Y, M, otherwise it will have X, Y, Z, M
142 const double m = QgsWkbTypes::hasM( pointType ) && ( ( hasZ && nCoords >= 4 ) || ( !hasZ && nCoords >= 3 ) ) ? coordList[ hasZ ? 3 : 2].toDouble() : std::numeric_limits< double >::quiet_NaN();
143 return std::make_unique< QgsPoint >( pointType, x, y, z, m );
144}
145
146std::unique_ptr< QgsCircularString > QgsArcGisRestUtils::convertCircularString( const QVariantMap &curveData, Qgis::WkbType pointType, const QgsPoint &startPoint )
147{
148 const QVariantList coordsList = curveData[u"c"_s].toList();
149 if ( coordsList.isEmpty() )
150 return nullptr;
151 const int coordsListSize = static_cast< int >( coordsList.size() );
152
153 QVector<QgsPoint> points;
154 points.reserve( coordsListSize + 1 );
155 points.append( startPoint );
156
157 for ( int i = 0; i < coordsListSize - 1; )
158 {
159 // first point is end point, second is point on curve
160 // i.e. the opposite to what QGIS requires!
161 std::unique_ptr< QgsPoint > endPoint( convertPoint( coordsList.at( i ).toList(), pointType ) );
162 if ( !endPoint )
163 return nullptr;
164 i++;
165 std::unique_ptr< QgsPoint > interiorPoint( convertPoint( coordsList.at( i ).toList(), pointType ) );
166 if ( !interiorPoint )
167 return nullptr;
168 i++;
169 points << *interiorPoint;
170 points << *endPoint;
171 }
172 auto curve = std::make_unique< QgsCircularString> ();
173 curve->setPoints( points );
174 return curve;
175}
176
177std::unique_ptr< QgsCurve > QgsArcGisRestUtils::convertCompoundCurve( const QVariantList &curvesList, Qgis::WkbType pointType )
178{
179 // [[6,3],[5,3],{"b":[[3,2],[6,1],[2,4]]},[1,2],{"c": [[3,3],[1,4]]}]
180 auto compoundCurve = std::make_unique< QgsCompoundCurve >();
181
182 QVector< double > lineX;
183 QVector< double > lineY;
184 QVector< double > lineZ;
185 QVector< double > lineM;
186 const int maxCurveListSize = static_cast< int >( curvesList.size() );
187 lineX.resize( maxCurveListSize );
188 lineY.resize( maxCurveListSize );
189
190 const bool hasZ = QgsWkbTypes::hasZ( pointType );
191 if ( hasZ )
192 lineZ.resize( maxCurveListSize );
193 const bool hasM = QgsWkbTypes::hasM( pointType );
194 if ( hasM )
195 lineM.resize( maxCurveListSize );
196
197 double *outLineX = lineX.data();
198 double *outLineY = lineY.data();
199 double *outLineZ = lineZ.data();
200 double *outLineM = lineM.data();
201 int actualLineSize = 0;
202
203 bool xok = false;
204 bool yok = false;
205
206 int curveListIndex = 0;
207 for ( const QVariant &curveData : curvesList )
208 {
209 if ( curveData.userType() == QMetaType::Type::QVariantList )
210 {
211 const QVariantList coordList = curveData.toList();
212 const int nCoords = static_cast< int >( coordList.size() );
213 if ( nCoords < 2 )
214 return nullptr;
215
216 const double x = coordList[0].toDouble( &xok );
217 const double y = coordList[1].toDouble( &yok );
218 if ( !xok || !yok )
219 return nullptr;
220
221 actualLineSize++;
222 *outLineX++ = x;
223 *outLineY++ = y;
224 if ( hasZ )
225 {
226 *outLineZ++ = nCoords >= 3 ? coordList[2].toDouble() : std::numeric_limits< double >::quiet_NaN();
227 }
228
229 if ( hasM )
230 {
231 // if point has just M but not Z, then the point dimension list will only have X, Y, M, otherwise it will have X, Y, Z, M
232 *outLineM++ = ( ( hasZ && nCoords >= 4 ) || ( !hasZ && nCoords >= 3 ) ) ? coordList[ hasZ ? 3 : 2].toDouble() : std::numeric_limits< double >::quiet_NaN();
233 }
234 }
235 else if ( curveData.userType() == QMetaType::Type::QVariantMap )
236 {
237 // The last point of the linestring is the start point of this circular string
238 QgsPoint lastLineStringPoint;
239 if ( actualLineSize > 0 )
240 {
241 lastLineStringPoint = QgsPoint( lineX.at( actualLineSize - 1 ),
242 lineY.at( actualLineSize - 1 ),
243 hasZ ? lineZ.at( actualLineSize - 1 ) : std::numeric_limits< double >::quiet_NaN(),
244 hasM ? lineM.at( actualLineSize - 1 ) : std::numeric_limits< double >::quiet_NaN() );
245 }
246 std::unique_ptr< QgsCircularString > circularString( convertCircularString( curveData.toMap(), pointType, lastLineStringPoint ) );
247 if ( !circularString )
248 {
249 return nullptr;
250 }
251
252 if ( actualLineSize > 0 )
253 {
254 lineX.resize( actualLineSize );
255 lineY.resize( actualLineSize );
256 if ( hasZ )
257 lineZ.resize( actualLineSize );
258 if ( hasM )
259 lineM.resize( actualLineSize );
260
261 compoundCurve->addCurve( new QgsLineString( lineX, lineY, lineZ, lineM ) );
262 lineX.resize( maxCurveListSize - curveListIndex );
263 lineY.resize( maxCurveListSize - curveListIndex );
264 if ( hasZ )
265 lineZ.resize( maxCurveListSize - curveListIndex );
266 if ( hasM )
267 lineM.resize( maxCurveListSize - curveListIndex );
268 outLineX = lineX.data();
269 outLineY = lineY.data();
270 outLineZ = lineZ.data();
271 outLineM = lineM.data();
272 }
273
274 // If the previous curve had less than two points, remove it
275 if ( compoundCurve->curveAt( compoundCurve->nCurves() - 1 )->nCoordinates() < 2 )
276 compoundCurve->removeCurve( compoundCurve->nCurves() - 1 );
277
278 const QgsPoint endPointCircularString = circularString->endPoint();
279 compoundCurve->addCurve( circularString.release() );
280
281 // Prepare a new line string
282 actualLineSize = 1;
283 *outLineX++ = endPointCircularString.x();
284 *outLineY++ = endPointCircularString.y();
285 if ( hasZ )
286 *outLineZ++ = endPointCircularString.z();
287 if ( hasM )
288 *outLineM++ = endPointCircularString.m();
289 }
290 curveListIndex++;
291 }
292
293 if ( actualLineSize == 1 && compoundCurve->nCurves() > 0 )
294 {
295 const QgsCurve *finalCurve = compoundCurve->curveAt( compoundCurve->nCurves() - 1 );
296 const QgsPoint finalCurveEndPoint = finalCurve->endPoint();
297 if ( qgsDoubleNear( finalCurveEndPoint.x(), lineX.at( 0 ) )
298 && qgsDoubleNear( finalCurveEndPoint.y(), lineY.at( 0 ) )
299 && ( !hasZ || qgsDoubleNear( finalCurveEndPoint.z(), lineZ.at( 0 ) ) )
300 && ( !hasM || qgsDoubleNear( finalCurveEndPoint.m(), lineM.at( 0 ) ) ) )
301 {
302 actualLineSize = 0; // redundant final curve containing a duplicate vertex
303 }
304 }
305
306 if ( actualLineSize > 0 )
307 {
308 lineX.resize( actualLineSize );
309 lineY.resize( actualLineSize );
310 if ( hasZ )
311 lineZ.resize( actualLineSize );
312 if ( hasM )
313 lineM.resize( actualLineSize );
314 compoundCurve->addCurve( new QgsLineString( lineX, lineY, lineZ, lineM ) );
315 }
316
317 return compoundCurve;
318}
319
320std::unique_ptr<QgsLineString> QgsArcGisRestUtils::convertLineString( const QVariantList &curvesList, Qgis::WkbType pointType )
321{
322 auto linestring = std::make_unique< QgsLineString >();
323
324 QVector< double > lineX;
325 QVector< double > lineY;
326 QVector< double > lineZ;
327 QVector< double > lineM;
328 const int maxCurveListSize = static_cast< int >( curvesList.size() );
329 lineX.resize( maxCurveListSize );
330 lineY.resize( maxCurveListSize );
331
332 const bool hasZ = QgsWkbTypes::hasZ( pointType );
333 if ( hasZ )
334 lineZ.resize( maxCurveListSize );
335 const bool hasM = QgsWkbTypes::hasM( pointType );
336 if ( hasM )
337 lineM.resize( maxCurveListSize );
338
339 double *outLineX = lineX.data();
340 double *outLineY = lineY.data();
341 double *outLineZ = lineZ.data();
342 double *outLineM = lineM.data();
343
344 bool xok = false;
345 bool yok = false;
346
347 int actualLineSize = 0;
348 for ( const QVariant &curveData : curvesList )
349 {
350 if ( curveData.userType() == QMetaType::Type::QVariantList )
351 {
352 const QVariantList coordList = curveData.toList();
353 const int nCoords = static_cast< int >( coordList.size() );
354 if ( nCoords < 2 )
355 return nullptr;
356
357 const double x = coordList[0].toDouble( &xok );
358 const double y = coordList[1].toDouble( &yok );
359 if ( !xok || !yok )
360 return nullptr;
361
362 actualLineSize++;
363 *outLineX++ = x;
364 *outLineY++ = y;
365 if ( hasZ )
366 {
367 *outLineZ++ = nCoords >= 3 ? coordList[2].toDouble() : std::numeric_limits< double >::quiet_NaN();
368 }
369
370 if ( hasM )
371 {
372 // if point has just M but not Z, then the point dimension list will only have X, Y, M, otherwise it will have X, Y, Z, M
373 *outLineM++ = ( ( hasZ && nCoords >= 4 ) || ( !hasZ && nCoords >= 3 ) ) ? coordList[ hasZ ? 3 : 2].toDouble() : std::numeric_limits< double >::quiet_NaN();
374 }
375 }
376 else
377 {
378 QgsDebugError( u"Found unexpected value when parsing ESRI json line string. Expected list, got %1"_s.arg( curveData.metaType().name() ) );
379 return nullptr;
380 }
381 }
382
383 if ( actualLineSize == 0 )
384 return nullptr;
385
386 lineX.resize( actualLineSize );
387 lineY.resize( actualLineSize );
388 if ( hasZ )
389 lineZ.resize( actualLineSize );
390 if ( hasM )
391 lineM.resize( actualLineSize );
392 return std::make_unique< QgsLineString>( lineX, lineY, lineZ, lineM );
393}
394
395std::unique_ptr< QgsPoint > QgsArcGisRestUtils::convertGeometryPoint( const QVariantMap &geometryData, Qgis::WkbType pointType )
396{
397 // {"x" : <x>, "y" : <y>, "z" : <z>, "m" : <m>}
398 bool xok = false, yok = false;
399 double x = geometryData[u"x"_s].toDouble( &xok );
400 double y = geometryData[u"y"_s].toDouble( &yok );
401 if ( !xok || !yok )
402 return nullptr;
403 double z = geometryData[u"z"_s].toDouble();
404 double m = geometryData[u"m"_s].toDouble();
405 return std::make_unique< QgsPoint >( pointType, x, y, z, m );
406}
407
408std::unique_ptr< QgsMultiPoint > QgsArcGisRestUtils::convertMultiPoint( const QVariantMap &geometryData, Qgis::WkbType pointType )
409{
410 // {"points" : [[ <x1>, <y1>, <z1>, <m1> ] , [ <x2>, <y2>, <z2>, <m2> ], ... ]}
411 const QVariantList coordsList = geometryData[u"points"_s].toList();
412
413 auto multiPoint = std::make_unique< QgsMultiPoint >();
414 multiPoint->reserve( static_cast< int >( coordsList.size() ) );
415 for ( const QVariant &coordData : coordsList )
416 {
417 const QVariantList coordList = coordData.toList();
418 std::unique_ptr< QgsPoint > p = convertPoint( coordList, pointType );
419 if ( !p )
420 {
421 continue;
422 }
423 multiPoint->addGeometry( p.release() );
424 }
425
426 // second chance -- sometimes layers are reported as multipoint but features have single
427 // point geometries. Silently handle this and upgrade to multipoint.
428 std::unique_ptr< QgsPoint > p = convertGeometryPoint( geometryData, pointType );
429 if ( p )
430 multiPoint->addGeometry( p.release() );
431
432 if ( multiPoint->numGeometries() == 0 )
433 {
434 // didn't find any points, so reset geometry to null
435 multiPoint.reset();
436 }
437 return multiPoint;
438}
439
440std::unique_ptr< QgsMultiCurve > QgsArcGisRestUtils::convertGeometryPolyline( const QVariantMap &geometryData, Qgis::WkbType pointType, bool allowCurves )
441{
442 // {"curvePaths": [[[0,0], {"c": [[3,3],[1,4]]} ]]}
443 QVariantList pathsList;
444 if ( geometryData[u"paths"_s].isValid() )
445 pathsList = geometryData[u"paths"_s].toList();
446 else if ( geometryData[u"curvePaths"_s].isValid() )
447 pathsList = geometryData[u"curvePaths"_s].toList();
448 if ( pathsList.isEmpty() )
449 return nullptr;
450 std::unique_ptr< QgsMultiCurve > multiCurve = allowCurves ? std::make_unique< QgsMultiCurve >() : std::make_unique< QgsMultiLineString >();
451 multiCurve->reserve( static_cast< int >( pathsList.size() ) );
452 for ( const QVariant &pathData : std::as_const( pathsList ) )
453 {
454 std::unique_ptr< QgsCurve > curve = allowCurves ? convertCompoundCurve( pathData.toList(), pointType ) : convertLineString( pathData.toList(), pointType );
455 if ( !curve )
456 {
457 return nullptr;
458 }
459 multiCurve->addGeometry( curve.release() );
460 }
461 return multiCurve;
462}
463
464std::unique_ptr< QgsMultiSurface > QgsArcGisRestUtils::convertGeometryPolygon( const QVariantMap &geometryData, Qgis::WkbType pointType, bool allowCurves )
465{
466 // {"curveRings": [[[0,0], {"c": [[3,3],[1,4]]} ]]}
467 QVariantList ringsList;
468 if ( geometryData[u"rings"_s].isValid() )
469 ringsList = geometryData[u"rings"_s].toList();
470 else if ( geometryData[u"ringPaths"_s].isValid() )
471 ringsList = geometryData[u"ringPaths"_s].toList();
472 if ( ringsList.isEmpty() )
473 return nullptr;
474
475 QList< QgsCurve * > curves;
476 for ( int i = 0, n = static_cast< int >( ringsList.size() ); i < n; ++i )
477 {
478 std::unique_ptr< QgsCurve > curve = allowCurves ? convertCompoundCurve( ringsList[i].toList(), pointType ) : convertLineString( ringsList[i].toList(), pointType );
479 if ( !curve )
480 {
481 continue;
482 }
483 curves.append( curve.release() );
484 }
485 if ( curves.count() == 0 )
486 return nullptr;
487
488 std::unique_ptr< QgsMultiSurface > result = allowCurves ? std::make_unique< QgsMultiSurface >() : std::make_unique< QgsMultiPolygon >();
489 if ( curves.count() == 1 )
490 {
491 // shortcut for exterior ring only
492 std::unique_ptr< QgsCurvePolygon > newPolygon = allowCurves ? std::make_unique< QgsCurvePolygon >() : std::make_unique< QgsPolygon >();
493 newPolygon->setExteriorRing( curves.takeAt( 0 ) );
494 result->addGeometry( newPolygon.release() );
495 return result;
496 }
497
498 std::sort( curves.begin(), curves.end(), []( const QgsCurve * a, const QgsCurve * 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 ); } );
499 result->reserve( static_cast< int >( curves.size() ) );
500 while ( !curves.isEmpty() )
501 {
502 QgsCurve *exterior = curves.takeFirst();
503 QgsCurvePolygon *newPolygon = allowCurves ? new QgsCurvePolygon() : new QgsPolygon();
504 newPolygon->setExteriorRing( exterior );
505 std::unique_ptr<QgsGeometryEngine> engine( QgsGeometry::createGeometryEngine( newPolygon ) );
506 engine->prepareGeometry();
507
508 QMutableListIterator< QgsCurve * > it( curves );
509 while ( it.hasNext() )
510 {
511 QgsCurve *curve = it.next();
512 QgsRectangle boundingBox = newPolygon->boundingBox();
513 if ( boundingBox.intersects( curve->boundingBox() ) )
514 {
515 QgsPoint point = curve->startPoint();
516 if ( engine->contains( &point ) )
517 {
518 newPolygon->addInteriorRing( curve );
519 it.remove();
520 engine.reset( QgsGeometry::createGeometryEngine( newPolygon ) );
521 engine->prepareGeometry();
522 }
523 }
524 }
525 result->addGeometry( newPolygon );
526 }
527 if ( result->numGeometries() == 0 )
528 return nullptr;
529
530 return result;
531}
532
533std::unique_ptr< QgsPolygon > QgsArcGisRestUtils::convertEnvelope( const QVariantMap &geometryData )
534{
535 // {"xmin" : -109.55, "ymin" : 25.76, "xmax" : -86.39, "ymax" : 49.94}
536 bool xminOk = false, yminOk = false, xmaxOk = false, ymaxOk = false;
537 double xmin = geometryData[u"xmin"_s].toDouble( &xminOk );
538 double ymin = geometryData[u"ymin"_s].toDouble( &yminOk );
539 double xmax = geometryData[u"xmax"_s].toDouble( &xmaxOk );
540 double ymax = geometryData[u"ymax"_s].toDouble( &ymaxOk );
541 if ( !xminOk || !yminOk || !xmaxOk || !ymaxOk )
542 return nullptr;
543 auto ext = std::make_unique< QgsLineString> ();
544 ext->addVertex( QgsPoint( xmin, ymin ) );
545 ext->addVertex( QgsPoint( xmax, ymin ) );
546 ext->addVertex( QgsPoint( xmax, ymax ) );
547 ext->addVertex( QgsPoint( xmin, ymax ) );
548 ext->addVertex( QgsPoint( xmin, ymin ) );
549 auto poly = std::make_unique< QgsPolygon >();
550 poly->setExteriorRing( ext.release() );
551 return poly;
552}
553
554std::unique_ptr< QgsAbstractGeometry > QgsArcGisRestUtils::convertGeometry( const QVariantMap &geometryData, const QString &esriGeometryType, bool readM, bool readZ, bool allowCurves, QgsCoordinateReferenceSystem *crs )
555{
556 Qgis::WkbType pointType = QgsWkbTypes::zmType( Qgis::WkbType::Point, readZ, readM );
557 if ( crs )
558 {
559 *crs = convertSpatialReference( geometryData[u"spatialReference"_s].toMap() );
560 }
561
562 // http://resources.arcgis.com/en/help/arcgis-rest-api/index.html#/Geometry_Objects/02r3000000n1000000/
563 if ( esriGeometryType == "esriGeometryNull"_L1 )
564 return nullptr;
565 else if ( esriGeometryType == "esriGeometryPoint"_L1 )
566 return convertGeometryPoint( geometryData, pointType );
567 else if ( esriGeometryType == "esriGeometryMultipoint"_L1 )
568 return convertMultiPoint( geometryData, pointType );
569 else if ( esriGeometryType == "esriGeometryPolyline"_L1 )
570 return convertGeometryPolyline( geometryData, pointType, allowCurves );
571 else if ( esriGeometryType == "esriGeometryPolygon"_L1 )
572 return convertGeometryPolygon( geometryData, pointType, allowCurves );
573 else if ( esriGeometryType == "esriGeometryEnvelope"_L1 )
574 return convertEnvelope( geometryData );
575 // Unsupported (either by qgis, or format unspecified by the specification)
576 // esriGeometryCircularArc
577 // esriGeometryEllipticArc
578 // esriGeometryBezier3Curve
579 // esriGeometryPath
580 // esriGeometryRing
581 // esriGeometryLine
582 // esriGeometryAny
583 // esriGeometryMultiPatch
584 // esriGeometryTriangleStrip
585 // esriGeometryTriangleFan
586 // esriGeometryRay
587 // esriGeometrySphere
588 // esriGeometryTriangles
589 // esriGeometryBag
590 return nullptr;
591}
592
594{
596
597 QString spatialReference = spatialReferenceMap[u"latestWkid"_s].toString();
598 if ( spatialReference.isEmpty() )
599 spatialReference = spatialReferenceMap[u"wkid"_s].toString();
600
601 // prefer using authority/id wherever we can
602 if ( !spatialReference.isEmpty() )
603 {
604 crs.createFromString( u"EPSG:%1"_s.arg( spatialReference ) );
605 if ( !crs.isValid() )
606 {
607 // Try as an ESRI auth
608 crs.createFromString( u"ESRI:%1"_s.arg( spatialReference ) );
609 }
610 }
611 else if ( !spatialReferenceMap[u"wkt"_s].toString().isEmpty() )
612 {
613 // otherwise fallback to WKT
614 crs.createFromWkt( spatialReferenceMap[u"wkt"_s].toString() );
615 }
616
617 if ( !crs.isValid() )
618 {
619 // If no spatial reference, just use WGS84
620 // TODO -- this needs further investigation! Most ESRI server services default to 3857, so that would likely be
621 // a safer fallback to use...
622 crs.createFromString( u"EPSG:4326"_s );
623 }
624 return crs;
625}
626
627std::unique_ptr< QgsSymbol > QgsArcGisRestUtils::convertSymbol( const QVariantMap &symbolData )
628{
629 const QString type = symbolData.value( u"type"_s ).toString();
630 if ( type == "esriSMS"_L1 )
631 {
632 // marker symbol
633 return parseEsriMarkerSymbolJson( symbolData );
634 }
635 else if ( type == "esriSLS"_L1 )
636 {
637 // line symbol
638 return parseEsriLineSymbolJson( symbolData );
639 }
640 else if ( type == "esriSFS"_L1 )
641 {
642 // fill symbol
643 return parseEsriFillSymbolJson( symbolData );
644 }
645 else if ( type == "esriPFS"_L1 )
646 {
647 return parseEsriPictureFillSymbolJson( symbolData );
648 }
649 else if ( type == "esriPMS"_L1 )
650 {
651 // picture marker
652 return parseEsriPictureMarkerSymbolJson( symbolData );
653 }
654 else if ( type == "esriTS"_L1 )
655 {
656 return parseEsriTextMarkerSymbolJson( symbolData );
657 }
658 return nullptr;
659}
660
661std::unique_ptr<QgsLineSymbol> QgsArcGisRestUtils::parseEsriLineSymbolJson( const QVariantMap &symbolData )
662{
663 QColor lineColor = convertColor( symbolData.value( u"color"_s ) );
664 if ( !lineColor.isValid() )
665 return nullptr;
666
667 bool ok = false;
668 double widthInPoints = symbolData.value( u"width"_s ).toDouble( &ok );
669 if ( !ok )
670 return nullptr;
671
672 QgsSymbolLayerList layers;
673 Qt::PenStyle penStyle = convertLineStyle( symbolData.value( u"style"_s ).toString() );
674 auto lineLayer = std::make_unique< QgsSimpleLineSymbolLayer >( lineColor, widthInPoints, penStyle );
675 lineLayer->setWidthUnit( Qgis::RenderUnit::Points );
676 layers.append( lineLayer.release() );
677
678 auto symbol = std::make_unique< QgsLineSymbol >( layers );
679 return symbol;
680}
681
682std::unique_ptr<QgsFillSymbol> QgsArcGisRestUtils::parseEsriFillSymbolJson( const QVariantMap &symbolData )
683{
684 QColor fillColor = convertColor( symbolData.value( u"color"_s ) );
685 Qt::BrushStyle brushStyle = convertFillStyle( symbolData.value( u"style"_s ).toString() );
686
687 const QVariantMap outlineData = symbolData.value( u"outline"_s ).toMap();
688 QColor lineColor = convertColor( outlineData.value( u"color"_s ) );
689 Qt::PenStyle penStyle = convertLineStyle( outlineData.value( u"style"_s ).toString() );
690 bool ok = false;
691 double penWidthInPoints = outlineData.value( u"width"_s ).toDouble( &ok );
692
693 QgsSymbolLayerList layers;
694 auto fillLayer = std::make_unique< QgsSimpleFillSymbolLayer >( fillColor, brushStyle, lineColor, penStyle, penWidthInPoints );
695 fillLayer->setStrokeWidthUnit( Qgis::RenderUnit::Points );
696 layers.append( fillLayer.release() );
697
698 auto symbol = std::make_unique< QgsFillSymbol >( layers );
699 return symbol;
700}
701
702std::unique_ptr<QgsFillSymbol> QgsArcGisRestUtils::parseEsriPictureFillSymbolJson( const QVariantMap &symbolData )
703{
704 bool ok = false;
705
706 double widthInPixels = symbolData.value( u"width"_s ).toInt( &ok );
707 if ( !ok )
708 return nullptr;
709
710 const double xScale = symbolData.value( u"xscale"_s ).toDouble( &ok );
711 if ( !qgsDoubleNear( xScale, 0.0 ) )
712 widthInPixels *= xScale;
713
714 const double angleCCW = symbolData.value( u"angle"_s ).toDouble( &ok );
715 double angleCW = 0;
716 if ( ok )
717 angleCW = -angleCCW;
718
719 const double xOffset = symbolData.value( u"xoffset"_s ).toDouble();
720 const double yOffset = symbolData.value( u"yoffset"_s ).toDouble();
721
722 QString symbolPath( symbolData.value( u"imageData"_s ).toString() );
723 symbolPath.prepend( "base64:"_L1 );
724
725 QgsSymbolLayerList layers;
726 auto fillLayer = std::make_unique< QgsRasterFillSymbolLayer >( symbolPath );
727 fillLayer->setWidth( widthInPixels );
728 fillLayer->setAngle( angleCW );
729 fillLayer->setSizeUnit( Qgis::RenderUnit::Points );
730 fillLayer->setOffset( QPointF( xOffset, yOffset ) );
731 fillLayer->setOffsetUnit( Qgis::RenderUnit::Points );
732 layers.append( fillLayer.release() );
733
734 const QVariantMap outlineData = symbolData.value( u"outline"_s ).toMap();
735 QColor lineColor = convertColor( outlineData.value( u"color"_s ) );
736 Qt::PenStyle penStyle = convertLineStyle( outlineData.value( u"style"_s ).toString() );
737 double penWidthInPoints = outlineData.value( u"width"_s ).toDouble( &ok );
738
739 auto lineLayer = std::make_unique< QgsSimpleLineSymbolLayer >( lineColor, penWidthInPoints, penStyle );
740 lineLayer->setWidthUnit( Qgis::RenderUnit::Points );
741 layers.append( lineLayer.release() );
742
743 auto symbol = std::make_unique< QgsFillSymbol >( layers );
744 return symbol;
745}
746
747Qgis::MarkerShape QgsArcGisRestUtils::parseEsriMarkerShape( const QString &style )
748{
749 if ( style == "esriSMSCircle"_L1 )
751 else if ( style == "esriSMSCross"_L1 )
753 else if ( style == "esriSMSDiamond"_L1 )
755 else if ( style == "esriSMSSquare"_L1 )
757 else if ( style == "esriSMSX"_L1 )
759 else if ( style == "esriSMSTriangle"_L1 )
761 else
763}
764
765std::unique_ptr<QgsMarkerSymbol> QgsArcGisRestUtils::parseEsriMarkerSymbolJson( const QVariantMap &symbolData )
766{
767 QColor fillColor = convertColor( symbolData.value( u"color"_s ) );
768 bool ok = false;
769 const double sizeInPoints = symbolData.value( u"size"_s ).toDouble( &ok );
770 if ( !ok )
771 return nullptr;
772 const double angleCCW = symbolData.value( u"angle"_s ).toDouble( &ok );
773 double angleCW = 0;
774 if ( ok )
775 angleCW = -angleCCW;
776
777 Qgis::MarkerShape shape = parseEsriMarkerShape( symbolData.value( u"style"_s ).toString() );
778
779 const double xOffset = symbolData.value( u"xoffset"_s ).toDouble();
780 const double yOffset = symbolData.value( u"yoffset"_s ).toDouble();
781
782 const QVariantMap outlineData = symbolData.value( u"outline"_s ).toMap();
783 QColor lineColor = convertColor( outlineData.value( u"color"_s ) );
784 Qt::PenStyle penStyle = convertLineStyle( outlineData.value( u"style"_s ).toString() );
785 double penWidthInPoints = outlineData.value( u"width"_s ).toDouble( &ok );
786
787 QgsSymbolLayerList layers;
788 auto markerLayer = std::make_unique< QgsSimpleMarkerSymbolLayer >( shape, sizeInPoints, angleCW, Qgis::ScaleMethod::ScaleArea, fillColor, lineColor );
789 markerLayer->setSizeUnit( Qgis::RenderUnit::Points );
790 markerLayer->setStrokeWidthUnit( Qgis::RenderUnit::Points );
791 markerLayer->setStrokeStyle( penStyle );
792 markerLayer->setStrokeWidth( penWidthInPoints );
793 markerLayer->setOffset( QPointF( xOffset, yOffset ) );
794 markerLayer->setOffsetUnit( Qgis::RenderUnit::Points );
795 layers.append( markerLayer.release() );
796
797 auto symbol = std::make_unique< QgsMarkerSymbol >( layers );
798 return symbol;
799}
800
801std::unique_ptr<QgsMarkerSymbol> QgsArcGisRestUtils::parseEsriPictureMarkerSymbolJson( const QVariantMap &symbolData )
802{
803 bool ok = false;
804 const double widthInPixels = symbolData.value( u"width"_s ).toInt( &ok );
805 if ( !ok )
806 return nullptr;
807 const double heightInPixels = symbolData.value( u"height"_s ).toInt( &ok );
808 if ( !ok )
809 return nullptr;
810
811 const double angleCCW = symbolData.value( u"angle"_s ).toDouble( &ok );
812 double angleCW = 0;
813 if ( ok )
814 angleCW = -angleCCW;
815
816 const double xOffset = symbolData.value( u"xoffset"_s ).toDouble();
817 const double yOffset = symbolData.value( u"yoffset"_s ).toDouble();
818
819 //const QString contentType = symbolData.value( u"contentType"_s ).toString();
820
821 QString symbolPath( symbolData.value( u"imageData"_s ).toString() );
822 symbolPath.prepend( "base64:"_L1 );
823
824 QgsSymbolLayerList layers;
825 auto markerLayer = std::make_unique< QgsRasterMarkerSymbolLayer >( symbolPath, widthInPixels, angleCW, Qgis::ScaleMethod::ScaleArea );
826 markerLayer->setSizeUnit( Qgis::RenderUnit::Points );
827
828 // only change the default aspect ratio if the server height setting requires this
829 if ( !qgsDoubleNear( static_cast< double >( heightInPixels ) / widthInPixels, markerLayer->defaultAspectRatio() ) )
830 markerLayer->setFixedAspectRatio( static_cast< double >( heightInPixels ) / widthInPixels );
831
832 markerLayer->setOffset( QPointF( xOffset, yOffset ) );
833 markerLayer->setOffsetUnit( Qgis::RenderUnit::Points );
834 layers.append( markerLayer.release() );
835
836 auto symbol = std::make_unique< QgsMarkerSymbol >( layers );
837 return symbol;
838}
839
840std::unique_ptr<QgsMarkerSymbol> QgsArcGisRestUtils::parseEsriTextMarkerSymbolJson( const QVariantMap &symbolData )
841{
842 QgsSymbolLayerList layers;
843
844 const QString fontFamily = symbolData.value( u"font"_s ).toMap().value( u"family"_s ).toString();
845
846 const QString chr = symbolData.value( u"text"_s ).toString();
847
848 const double pointSize = symbolData.value( u"font"_s ).toMap().value( u"size"_s ).toDouble();
849
850 const QColor color = convertColor( symbolData.value( u"color"_s ) );
851
852 const double esriAngle = symbolData.value( u"angle"_s ).toDouble();
853
854 const double angle = 90.0 - esriAngle;
855
856 auto markerLayer = std::make_unique< QgsFontMarkerSymbolLayer >( fontFamily, chr, pointSize, color, angle );
857
858 QColor strokeColor = convertColor( symbolData.value( u"borderLineColor"_s ) );
859 markerLayer->setStrokeColor( strokeColor );
860
861 double borderLineSize = symbolData.value( u"borderLineSize"_s ).toDouble();
862 markerLayer->setStrokeWidth( borderLineSize );
863
864 const QString fontStyle = symbolData.value( u"font"_s ).toMap().value( u"style"_s ).toString();
865 markerLayer->setFontStyle( fontStyle );
866
867 double xOffset = symbolData.value( u"xoffset"_s ).toDouble();
868 double yOffset = symbolData.value( u"yoffset"_s ).toDouble();
869
870 markerLayer->setOffset( QPointF( xOffset, yOffset ) );
871 markerLayer->setOffsetUnit( Qgis::RenderUnit::Points );
872
873 markerLayer->setSizeUnit( Qgis::RenderUnit::Points );
874 markerLayer->setStrokeWidthUnit( Qgis::RenderUnit::Points );
875
878
879 QString horizontalAnchorPoint = symbolData.value( u"horizontalAlignment"_s ).toString();
880 QString verticalAnchorPoint = symbolData.value( u"verticalAlignment"_s ).toString();
881
882 if ( horizontalAnchorPoint == QString( "center" ) )
883 {
885 }
886 else if ( horizontalAnchorPoint == QString( "left" ) )
887 {
889 }
890 else if ( horizontalAnchorPoint == QString( "right" ) )
891 {
893 }
894
895 if ( verticalAnchorPoint == QString( "center" ) )
896 {
898 }
899 else if ( verticalAnchorPoint == QString( "top" ) )
900 {
902 }
903 else if ( verticalAnchorPoint == QString( "bottom" ) )
904 {
906 }
907
908 markerLayer->setHorizontalAnchorPoint( hAlign );
909 markerLayer->setVerticalAnchorPoint( vAlign );
910
911 layers.append( markerLayer.release() );
912
913 auto symbol = std::make_unique< QgsMarkerSymbol >( layers );
914 return symbol;
915}
916
917std::unique_ptr<QgsAbstractVectorLayerLabeling > QgsArcGisRestUtils::convertLabeling( const QVariantList &labelingData )
918{
919 if ( labelingData.empty() )
920 return nullptr;
921
922 QgsRuleBasedLabeling::Rule *root = new QgsRuleBasedLabeling::Rule( new QgsPalLayerSettings(), 0, 0, QString(), QString(), false );
923 root->setActive( true );
924
925 int i = 1;
926 for ( const QVariant &lbl : labelingData )
927 {
928 const QVariantMap labeling = lbl.toMap();
929
931 QgsTextFormat format;
932
933 const QString placement = labeling.value( u"labelPlacement"_s ).toString();
934 if ( placement == "esriServerPointLabelPlacementAboveCenter"_L1 )
935 {
938 }
939 else if ( placement == "esriServerPointLabelPlacementBelowCenter"_L1 )
940 {
943 }
944 else if ( placement == "esriServerPointLabelPlacementCenterCenter"_L1 )
945 {
948 }
949 else if ( placement == "esriServerPointLabelPlacementAboveLeft"_L1 )
950 {
953 }
954 else if ( placement == "esriServerPointLabelPlacementBelowLeft"_L1 )
955 {
958 }
959 else if ( placement == "esriServerPointLabelPlacementCenterLeft"_L1 )
960 {
963 }
964 else if ( placement == "esriServerPointLabelPlacementAboveRight"_L1 )
965 {
968 }
969 else if ( placement == "esriServerPointLabelPlacementBelowRight"_L1 )
970 {
973 }
974 else if ( placement == "esriServerPointLabelPlacementCenterRight"_L1 )
975 {
978 }
979 else if ( placement == "esriServerLinePlacementAboveAfter"_L1 ||
980 placement == "esriServerLinePlacementAboveStart"_L1 ||
981 placement == "esriServerLinePlacementAboveAlong"_L1 )
982 {
985 }
986 else if ( placement == "esriServerLinePlacementBelowAfter"_L1 ||
987 placement == "esriServerLinePlacementBelowStart"_L1 ||
988 placement == "esriServerLinePlacementBelowAlong"_L1 )
989 {
992 }
993 else if ( placement == "esriServerLinePlacementCenterAfter"_L1 ||
994 placement == "esriServerLinePlacementCenterStart"_L1 ||
995 placement == "esriServerLinePlacementCenterAlong"_L1 )
996 {
999 }
1000 else if ( placement == "esriServerPolygonPlacementAlwaysHorizontal"_L1 )
1001 {
1003 }
1004
1005 const double minScale = labeling.value( u"minScale"_s ).toDouble();
1006 const double maxScale = labeling.value( u"maxScale"_s ).toDouble();
1007
1008 QVariantMap symbol = labeling.value( u"symbol"_s ).toMap();
1009 format.setColor( convertColor( symbol.value( u"color"_s ) ) );
1010 const double haloSize = symbol.value( u"haloSize"_s ).toDouble();
1011 if ( !qgsDoubleNear( haloSize, 0.0 ) )
1012 {
1013 QgsTextBufferSettings buffer;
1014 buffer.setEnabled( true );
1015 buffer.setSize( haloSize );
1017 buffer.setColor( convertColor( symbol.value( u"haloColor"_s ) ) );
1018 format.setBuffer( buffer );
1019 }
1020
1021 const QString fontFamily = symbol.value( u"font"_s ).toMap().value( u"family"_s ).toString();
1022 const QString fontStyle = symbol.value( u"font"_s ).toMap().value( u"style"_s ).toString();
1023 const QString fontWeight = symbol.value( u"font"_s ).toMap().value( u"weight"_s ).toString();
1024 const int fontSize = symbol.value( u"font"_s ).toMap().value( u"size"_s ).toInt();
1025 QFont font( fontFamily, fontSize );
1026 font.setStyleName( fontStyle );
1027 font.setWeight( fontWeight == "bold"_L1 ? QFont::Bold : QFont::Normal );
1028
1029 format.setFont( font );
1030 format.setSize( fontSize );
1032
1033 settings->setFormat( format );
1034
1035 QString where = labeling.value( u"where"_s ).toString();
1036 QgsExpression exp( where );
1037 // If the where clause isn't parsed as valid, don't use its
1038 if ( !exp.isValid() )
1039 where.clear();
1040
1041 settings->fieldName = convertLabelingExpression( labeling.value( u"labelExpression"_s ).toString() );
1042 settings->isExpression = true;
1043
1044 QgsRuleBasedLabeling::Rule *child = new QgsRuleBasedLabeling::Rule( settings, maxScale, minScale, where, QObject::tr( "ASF label %1" ).arg( i++ ), false );
1045 child->setActive( true );
1046 root->appendChild( child );
1047 }
1048
1049 return std::make_unique< QgsRuleBasedLabeling >( root );
1050}
1051
1052std::unique_ptr< QgsFeatureRenderer > QgsArcGisRestUtils::convertRenderer( const QVariantMap &rendererData )
1053{
1054 const QString type = rendererData.value( u"type"_s ).toString();
1055 if ( type == "simple"_L1 )
1056 {
1057 const QVariantMap symbolProps = rendererData.value( u"symbol"_s ).toMap();
1058 std::unique_ptr< QgsSymbol > symbol( convertSymbol( symbolProps ) );
1059 if ( symbol )
1060 return std::make_unique< QgsSingleSymbolRenderer >( symbol.release() );
1061 else
1062 return nullptr;
1063 }
1064 else if ( type == "uniqueValue"_L1 )
1065 {
1066 const QString field1 = rendererData.value( u"field1"_s ).toString();
1067 const QString field2 = rendererData.value( u"field2"_s ).toString();
1068 const QString field3 = rendererData.value( u"field3"_s ).toString();
1069 QString attribute;
1070 if ( !field2.isEmpty() || !field3.isEmpty() )
1071 {
1072 const QString delimiter = rendererData.value( u"fieldDelimiter"_s ).toString();
1073 if ( !field3.isEmpty() )
1074 {
1075 attribute = u"concat(\"%1\",'%2',\"%3\",'%4',\"%5\")"_s.arg( field1, delimiter, field2, delimiter, field3 );
1076 }
1077 else
1078 {
1079 attribute = u"concat(\"%1\",'%2',\"%3\")"_s.arg( field1, delimiter, field2 );
1080 }
1081 }
1082 else
1083 {
1084 attribute = field1;
1085 }
1086
1087 const QVariantList categories = rendererData.value( u"uniqueValueInfos"_s ).toList();
1088 QgsCategoryList categoryList;
1089 for ( const QVariant &category : categories )
1090 {
1091 const QVariantMap categoryData = category.toMap();
1092 const QString value = categoryData.value( u"value"_s ).toString();
1093 const QString label = categoryData.value( u"label"_s ).toString();
1094 std::unique_ptr< QgsSymbol > symbol( QgsArcGisRestUtils::convertSymbol( categoryData.value( u"symbol"_s ).toMap() ) );
1095 if ( symbol )
1096 {
1097 categoryList.append( QgsRendererCategory( value, symbol.release(), label ) );
1098 }
1099 }
1100
1101 std::unique_ptr< QgsSymbol > defaultSymbol( convertSymbol( rendererData.value( u"defaultSymbol"_s ).toMap() ) );
1102 if ( defaultSymbol )
1103 {
1104 categoryList.append( QgsRendererCategory( QVariant(), defaultSymbol.release(), rendererData.value( u"defaultLabel"_s ).toString() ) );
1105 }
1106
1107 if ( categoryList.empty() )
1108 return nullptr;
1109
1110 return std::make_unique< QgsCategorizedSymbolRenderer >( attribute, categoryList );
1111 }
1112 else if ( type == "classBreaks"_L1 )
1113 {
1114 const QString attrName = rendererData.value( u"field"_s ).toString();
1115
1116 const QVariantList classBreakInfos = rendererData.value( u"classBreakInfos"_s ).toList();
1117 const QVariantMap authoringInfo = rendererData.value( u"authoringInfo"_s ).toMap();
1118 QVariantMap symbolData;
1119
1120 QString esriMode = authoringInfo.value( u"classificationMethod"_s ).toString();
1121 if ( esriMode.isEmpty() )
1122 {
1123 esriMode = rendererData.value( u"classificationMethod"_s ).toString();
1124 }
1125
1126 if ( !classBreakInfos.isEmpty() )
1127 {
1128 symbolData = classBreakInfos.at( 0 ).toMap().value( u"symbol"_s ).toMap();
1129 }
1130 std::unique_ptr< QgsSymbol > symbol( QgsArcGisRestUtils::convertSymbol( symbolData ) );
1131 if ( !symbol )
1132 return nullptr;
1133
1134 const double transparency = rendererData.value( u"transparency"_s ).toDouble();
1135 const double opacity = ( 100.0 - transparency ) / 100.0;
1136 symbol->setOpacity( opacity );
1137
1138 const QVariantList visualVariablesData = rendererData.value( u"visualVariables"_s ).toList();
1139
1140 for ( const QVariant &visualVariable : visualVariablesData )
1141 {
1142 const QVariantMap visualVariableData = visualVariable.toMap();
1143 const QString variableType = visualVariableData.value( u"type"_s ).toString();
1144 if ( variableType == "sizeInfo"_L1 )
1145 {
1146 continue;
1147 }
1148 else if ( variableType == "colorInfo"_L1 )
1149 {
1150 const QVariantList stops = visualVariableData.value( u"stops"_s ).toList();
1151 if ( stops.size() < 2 )
1152 continue;
1153
1154 // layer has continuous coloring, so convert to a symbol using color ramp assistant
1155 bool ok = false;
1156 const double minValue = stops.front().toMap().value( u"value"_s ).toDouble( &ok );
1157 if ( !ok )
1158 continue;
1159 const QColor minColor = convertColor( stops.front().toMap().value( u"color"_s ) );
1160
1161 const double maxValue = stops.back().toMap().value( u"value"_s ).toDouble( &ok );
1162 if ( !ok )
1163 continue;
1164 const QColor maxColor = convertColor( stops.back().toMap().value( u"color"_s ) );
1165
1166 QgsGradientStopsList gradientStops;
1167 for ( int i = 1; i < stops.size() - 1; ++i )
1168 {
1169 const QVariantMap stopData = stops.at( i ).toMap();
1170 const double breakpoint = stopData.value( u"value"_s ).toDouble();
1171 const double scaledBreakpoint = ( breakpoint - minValue ) / ( maxValue - minValue );
1172 const QColor fillColor = convertColor( stopData.value( u"color"_s ) );
1173
1174 gradientStops.append( QgsGradientStop( scaledBreakpoint, fillColor ) );
1175 }
1176
1177 auto colorRamp = std::make_unique< QgsGradientColorRamp >(
1178 minColor, maxColor, false, gradientStops
1179 );
1180
1181 QgsProperty colorProperty = QgsProperty::fromField( attrName );
1182 colorProperty.setTransformer(
1183 new QgsColorRampTransformer( minValue, maxValue, colorRamp.release() )
1184 );
1185 for ( int layer = 0; layer < symbol->symbolLayerCount(); ++layer )
1186 {
1187 symbol->symbolLayer( layer )->setDataDefinedProperty( QgsSymbolLayer::Property::FillColor, colorProperty );
1188 }
1189
1190 return std::make_unique< QgsSingleSymbolRenderer >( symbol.release() );
1191 }
1192 else
1193 {
1194 QgsDebugError( u"ESRI visualVariable type %1 is not currently supported"_s.arg( variableType ) );
1195 }
1196 }
1197
1198 double lastValue = rendererData.value( u"minValue"_s ).toDouble();
1199
1200 auto graduatedRenderer = std::make_unique< QgsGraduatedSymbolRenderer >( attrName );
1201
1202 graduatedRenderer->setSourceSymbol( symbol.release() );
1203
1204 if ( esriMode == "esriClassifyDefinedInterval"_L1 )
1205 {
1207 graduatedRenderer->setClassificationMethod( method );
1208 }
1209 else if ( esriMode == "esriClassifyEqualInterval"_L1 )
1210 {
1212 graduatedRenderer->setClassificationMethod( method );
1213 }
1214 else if ( esriMode == "esriClassifyGeometricalInterval"_L1 )
1215 {
1217 graduatedRenderer->setClassificationMethod( method );
1218 }
1219 else if ( esriMode == "esriClassifyManual"_L1 )
1220 {
1222 graduatedRenderer->setClassificationMethod( method );
1223 }
1224 else if ( esriMode == "esriClassifyNaturalBreaks"_L1 )
1225 {
1227 graduatedRenderer->setClassificationMethod( method );
1228 }
1229 else if ( esriMode == "esriClassifyQuantile"_L1 )
1230 {
1232 graduatedRenderer->setClassificationMethod( method );
1233 }
1234 else if ( esriMode == "esriClassifyStandardDeviation"_L1 )
1235 {
1237 graduatedRenderer->setClassificationMethod( method );
1238 }
1239 else if ( !esriMode.isEmpty() )
1240 {
1241 QgsDebugError( u"ESRI classification mode %1 is not currently supported"_s.arg( esriMode ) );
1242 }
1243
1244 for ( const QVariant &classBreakInfo : classBreakInfos )
1245 {
1246 const QVariantMap symbolData = classBreakInfo.toMap().value( u"symbol"_s ).toMap();
1247 std::unique_ptr< QgsSymbol > symbol( QgsArcGisRestUtils::convertSymbol( symbolData ) );
1248 double classMaxValue = classBreakInfo.toMap().value( u"classMaxValue"_s ).toDouble();
1249 const QString label = classBreakInfo.toMap().value( u"label"_s ).toString();
1250
1251 QgsRendererRange range;
1252
1253 range.setLowerValue( lastValue );
1254 range.setUpperValue( classMaxValue );
1255 range.setLabel( label );
1256 range.setSymbol( symbol.release() );
1257
1258 lastValue = classMaxValue;
1259 graduatedRenderer->addClass( range );
1260 }
1261
1262 return graduatedRenderer;
1263 }
1264 else if ( type == "heatmap"_L1 )
1265 {
1266 // currently unsupported
1267 return nullptr;
1268 }
1269 else if ( type == "vectorField"_L1 )
1270 {
1271 // currently unsupported
1272 return nullptr;
1273 }
1274 return nullptr;
1275}
1276
1278{
1279 QString expression = string;
1280
1281 // Replace a few ArcGIS token to QGIS equivalents
1282 const thread_local QRegularExpression rx1 = QRegularExpression( u"(?=([^\"\\\\]*(\\\\.|\"([^\"\\\\]*\\\\.)*[^\"\\\\]*\"))*[^\"]*$)(\\s|^)CONCAT(\\s|$)"_s );
1283 expression = expression.replace( rx1, u"\\4||\\5"_s );
1284
1285 const thread_local QRegularExpression rx2 = QRegularExpression( u"(?=([^\"\\\\]*(\\\\.|\"([^\"\\\\]*\\\\.)*[^\"\\\\]*\"))*[^\"]*$)(\\s|^)NEWLINE(\\s|$)"_s );
1286 expression = expression.replace( rx2, u"\\4'\\n'\\5"_s );
1287
1288 // ArcGIS's double quotes are single quotes in QGIS
1289 const thread_local QRegularExpression rx3 = QRegularExpression( u"\"(.*?(?<!\\\\))\""_s );
1290 expression = expression.replace( rx3, u"'\\1'"_s );
1291 const thread_local QRegularExpression rx4 = QRegularExpression( u"\\\\\""_s );
1292 expression = expression.replace( rx4, u"\""_s );
1293
1294 // ArcGIS's square brakets are double quotes in QGIS
1295 const thread_local QRegularExpression rx5 = QRegularExpression( u"\\[([^]]*)\\]"_s );
1296 expression = expression.replace( rx5, u"\"\\1\""_s );
1297
1298 return expression;
1299}
1300
1301QColor QgsArcGisRestUtils::convertColor( const QVariant &colorData )
1302{
1303 const QVariantList colorParts = colorData.toList();
1304 if ( colorParts.count() < 4 )
1305 return QColor();
1306
1307 int red = colorParts.at( 0 ).toInt();
1308 int green = colorParts.at( 1 ).toInt();
1309 int blue = colorParts.at( 2 ).toInt();
1310 int alpha = colorParts.at( 3 ).toInt();
1311 return QColor( red, green, blue, alpha );
1312}
1313
1314Qt::PenStyle QgsArcGisRestUtils::convertLineStyle( const QString &style )
1315{
1316 if ( style == "esriSLSSolid"_L1 )
1317 return Qt::SolidLine;
1318 else if ( style == "esriSLSDash"_L1 )
1319 return Qt::DashLine;
1320 else if ( style == "esriSLSDashDot"_L1 )
1321 return Qt::DashDotLine;
1322 else if ( style == "esriSLSDashDotDot"_L1 )
1323 return Qt::DashDotDotLine;
1324 else if ( style == "esriSLSDot"_L1 )
1325 return Qt::DotLine;
1326 else if ( style == "esriSLSNull"_L1 )
1327 return Qt::NoPen;
1328 else
1329 return Qt::SolidLine;
1330}
1331
1332Qt::BrushStyle QgsArcGisRestUtils::convertFillStyle( const QString &style )
1333{
1334 if ( style == "esriSFSBackwardDiagonal"_L1 )
1335 return Qt::BDiagPattern;
1336 else if ( style == "esriSFSCross"_L1 )
1337 return Qt::CrossPattern;
1338 else if ( style == "esriSFSDiagonalCross"_L1 )
1339 return Qt::DiagCrossPattern;
1340 else if ( style == "esriSFSForwardDiagonal"_L1 )
1341 return Qt::FDiagPattern;
1342 else if ( style == "esriSFSHorizontal"_L1 )
1343 return Qt::HorPattern;
1344 else if ( style == "esriSFSNull"_L1 )
1345 return Qt::NoBrush;
1346 else if ( style == "esriSFSSolid"_L1 )
1347 return Qt::SolidPattern;
1348 else if ( style == "esriSFSVertical"_L1 )
1349 return Qt::VerPattern;
1350 else
1351 return Qt::SolidPattern;
1352}
1353
1354QDateTime QgsArcGisRestUtils::convertDateTime( const QVariant &value )
1355{
1356 if ( QgsVariantUtils::isNull( value ) )
1357 return QDateTime();
1358 bool ok = false;
1359 QDateTime dt = QDateTime::fromMSecsSinceEpoch( value.toLongLong( &ok ) );
1360 if ( !ok )
1361 {
1362 QgsDebugError( u"Invalid value %1 for datetime"_s.arg( value.toString() ) );
1363 return QDateTime();
1364 }
1365 else
1366 return dt;
1367}
1368
1370{
1371 if ( QgsVariantUtils::isNull( value ) )
1372 return QgsRectangle();
1373
1374 const QVariantMap coords = value.toMap();
1375 if ( coords.isEmpty() ) return QgsRectangle();
1376
1377 bool ok;
1378
1379 const double xmin = coords.value( u"xmin"_s ).toDouble( &ok );
1380 if ( ! ok ) return QgsRectangle();
1381
1382 const double ymin = coords.value( u"ymin"_s ).toDouble( &ok );
1383 if ( ! ok ) return QgsRectangle();
1384
1385 const double xmax = coords.value( u"xmax"_s ).toDouble( &ok );
1386 if ( ! ok ) return QgsRectangle();
1387
1388 const double ymax = coords.value( u"ymax"_s ).toDouble( &ok );
1389 if ( ! ok ) return QgsRectangle();
1390
1391 return QgsRectangle( xmin, ymin, xmax, ymax );
1392
1393}
1394
1395
1397{
1398 QVariantMap res;
1399 if ( geometry.isNull() )
1400 return QVariantMap();
1401
1402 const QgsAbstractGeometry *geom = geometry.constGet()->simplifiedTypeRef();
1403 switch ( QgsWkbTypes::flatType( geom->wkbType() ) )
1404 {
1407 return QVariantMap();
1408
1410 res = pointToJson( qgsgeometry_cast< const QgsPoint * >( geom ) );
1411 break;
1412
1414 res = lineStringToJson( qgsgeometry_cast< const QgsLineString * >( geom ) );
1415 break;
1416
1419 res = curveToJson( qgsgeometry_cast< const QgsCurve * >( geom ) );
1420 break;
1421
1423 res = polygonToJson( qgsgeometry_cast< const QgsPolygon * >( geom ) );
1424 break;
1425
1427 res = multiPointToJson( qgsgeometry_cast< const QgsMultiPoint * >( geom ) );
1428 break;
1429
1431 res = multiLineStringToJson( qgsgeometry_cast< const QgsMultiLineString * >( geom ) );
1432 break;
1433
1435 res = multiCurveToJson( qgsgeometry_cast< const QgsMultiCurve * >( geom ) );
1436 break;
1437
1439 res = multiPolygonToJson( qgsgeometry_cast< const QgsMultiPolygon * >( geom ) );
1440 break;
1441
1443 res = curvePolygonToJson( qgsgeometry_cast< const QgsCurvePolygon * >( geom ) );
1444 break;
1445
1447 res = multiSurfaceToJson( qgsgeometry_cast< const QgsMultiSurface * >( geom ) );
1448 break;
1449
1451 return QVariantMap(); // not supported by REST API
1452
1454 return QVariantMap(); //not yet supported, but could be
1455
1456 default:
1457 return QVariantMap(); //unreachable
1458
1459 }
1460
1461 if ( crs.isValid() )
1462 {
1463 // add spatialReference information
1464 res.insert( u"spatialReference"_s, crsToJson( crs ) );
1465 }
1466
1467 return res;
1468}
1469
1470QVariantMap QgsArcGisRestUtils::pointToJson( const QgsPoint *point )
1471{
1472 QVariantMap data;
1473 if ( point->isEmpty() )
1474 data[u"x"_s] = u"NaN"_s;
1475 else
1476 {
1477 data[u"x"_s] = point->x();
1478 data[u"y"_s] = point->y();
1479
1480 if ( point->is3D() )
1481 data[u"z"_s] = !std::isnan( point->z() ) ? QVariant( point->z() ) : QVariant( u"NaN"_s );
1482
1483 if ( point->isMeasure() )
1484 data[u"m"_s] = !std::isnan( point->m() ) ? QVariant( point->m() ) : QVariant( u"NaN"_s );
1485 }
1486 return data;
1487}
1488
1489QVariantMap QgsArcGisRestUtils::multiPointToJson( const QgsMultiPoint *multiPoint )
1490{
1491 QVariantMap data;
1492 const bool hasZ = multiPoint->is3D();
1493 const bool hasM = multiPoint->isMeasure();
1494 data[u"hasM"_s] = hasM;
1495 data[u"hasZ"_s] = hasZ;
1496
1497 QVariantList pointsList;
1498 const int size = multiPoint->numGeometries();
1499 pointsList.reserve( size );
1500
1501 QVariantList pointList;
1502 for ( int i = 0; i < size; ++i )
1503 {
1504 const QgsPoint *point = multiPoint->pointN( i );
1505
1506 pointList.clear();
1507 pointList.append( point->x() );
1508 pointList.append( point->y() );
1509 if ( hasZ )
1510 pointList.append( point->z() );
1511 if ( hasM && !std::isnan( point->m() ) )
1512 pointList.append( point->m() );
1513
1514 pointsList.push_back( pointList );
1515 }
1516
1517 data[u"points"_s] = pointsList;
1518 return data;
1519}
1520
1521QVariantList QgsArcGisRestUtils::lineStringToJsonPath( const QgsLineString *line )
1522{
1523 const bool hasZ = line->is3D();
1524 const bool hasM = line->isMeasure();
1525
1526 QVariantList pointsList;
1527 const int size = line->numPoints();
1528 pointsList.reserve( size );
1529
1530 QVariantList pointList;
1531 const double *xData = line->xData();
1532 const double *yData = line->yData();
1533 const double *zData = hasZ ? line->zData() : nullptr;
1534 const double *mData = hasM ? line->mData() : nullptr;
1535
1536 for ( int i = 0; i < size; ++i )
1537 {
1538 pointList.clear();
1539 pointList.append( *xData++ );
1540 pointList.append( *yData++ );
1541
1542 if ( hasZ )
1543 pointList.append( *zData++ );
1544
1545 if ( hasM && !std::isnan( *mData ) )
1546 pointList.append( *mData );
1547 if ( hasM )
1548 mData++;
1549
1550 pointsList.push_back( pointList );
1551 }
1552 return pointsList;
1553}
1554
1555QVariantList QgsArcGisRestUtils::curveToJsonCurve( const QgsCurve *curve, bool includeStart )
1556{
1557 const bool hasZ = curve->is3D();
1558 const bool hasM = curve->isMeasure();
1559
1560 auto pointToList = [hasZ, hasM]( const QgsPoint & point ) -> QVariantList
1561 {
1562 QVariantList pointList;
1563
1564 pointList.append( point.x() );
1565 pointList.append( point.y() );
1566
1567 if ( hasZ )
1568 pointList.append( point.z() );
1569
1570 if ( hasM && !std::isnan( point.m() ) )
1571 pointList.append( point.m() );
1572
1573 return pointList;
1574 };
1575
1576 QVariantList res;
1577 switch ( QgsWkbTypes::flatType( curve->wkbType() ) )
1578 {
1580 {
1581 QVariantList part = lineStringToJsonPath( qgsgeometry_cast< const QgsLineString *>( curve ) );
1582 if ( !part.isEmpty() && !includeStart )
1583 part.removeAt( 0 );
1584 res = part;
1585 break;
1586 }
1587
1589 {
1590 const QgsCircularString *circularString = qgsgeometry_cast<const QgsCircularString * >( curve );
1591 if ( includeStart && !circularString->isEmpty() )
1592 {
1593 res.push_back( pointToList( circularString->startPoint() ) );
1594 }
1595
1596 const int size = circularString->numPoints();
1597 for ( int i = 1; i + 1 < size; i += 2 )
1598 {
1599 // end point comes BEFORE interior point!
1600 QVariantMap curvePart;
1601 QVariantList curveList;
1602 curveList.push_back( pointToList( circularString->pointN( i + 1 ) ) );
1603
1604 curveList.push_back( pointToList( circularString->pointN( i ) ) );
1605
1606 curvePart.insert( u"c"_s, curveList );
1607 res.push_back( curvePart );
1608 }
1609 break;
1610 }
1611
1613 {
1614 const QgsCompoundCurve *compoundCurve = qgsgeometry_cast<const QgsCompoundCurve * >( curve );
1615
1616 const int size = compoundCurve->nCurves();
1617 for ( int i = 0; i < size; ++i )
1618 {
1619 const QgsCurve *subCurve = compoundCurve->curveAt( i );
1620 res.append( curveToJsonCurve( subCurve, i == 0 ) );
1621 }
1622 break;
1623 }
1624
1625 default:
1626 break;
1627 }
1628 return res;
1629}
1630
1631QVariantMap QgsArcGisRestUtils::lineStringToJson( const QgsLineString *line )
1632{
1633 QVariantMap data;
1634 const bool hasZ = line->is3D();
1635 const bool hasM = line->isMeasure();
1636 data[u"hasM"_s] = hasM;
1637 data[u"hasZ"_s] = hasZ;
1638
1639 const QVariantList pointsList = lineStringToJsonPath( line );
1640
1641 QVariantList pointsData = QVariantList();
1642 pointsData.push_back( pointsList );
1643 data[u"paths"_s] = pointsData;
1644
1645 return data;
1646}
1647
1648QVariantMap QgsArcGisRestUtils::curveToJson( const QgsCurve *curve )
1649{
1650 QVariantMap data;
1651 const bool hasZ = curve->is3D();
1652 const bool hasM = curve->isMeasure();
1653 data[u"hasM"_s] = hasM;
1654 data[u"hasZ"_s] = hasZ;
1655
1656 const QVariantList curveList = curveToJsonCurve( curve, true );
1657
1658 QVariantList curveData = QVariantList();
1659 curveData.push_back( curveList );
1660 data[u"curvePaths"_s] = curveData;
1661
1662 return data;
1663}
1664
1665QVariantMap QgsArcGisRestUtils::multiLineStringToJson( const QgsMultiLineString *multiLine )
1666{
1667 QVariantMap data;
1668 const bool hasZ = multiLine->is3D();
1669 const bool hasM = multiLine->isMeasure();
1670 data[u"hasM"_s] = hasM;
1671 data[u"hasZ"_s] = hasZ;
1672
1673 const int size = multiLine->numGeometries();
1674 QVariantList paths;
1675 paths.reserve( size );
1676 for ( int i = 0; i < size; ++i )
1677 {
1678 const QgsLineString *line = multiLine->lineStringN( i );
1679 paths.push_back( lineStringToJsonPath( line ) );
1680 }
1681
1682 data[u"paths"_s] = paths;
1683 return data;
1684}
1685
1686QVariantMap QgsArcGisRestUtils::multiCurveToJson( const QgsMultiCurve *multiCurve )
1687{
1688 QVariantMap data;
1689 const bool hasZ = multiCurve->is3D();
1690 const bool hasM = multiCurve->isMeasure();
1691 data[u"hasM"_s] = hasM;
1692 data[u"hasZ"_s] = hasZ;
1693
1694 const int size = multiCurve->numGeometries();
1695 QVariantList paths;
1696 paths.reserve( size );
1697 for ( int i = 0; i < size; ++i )
1698 {
1699 const QgsCurve *curve = multiCurve->curveN( i );
1700 paths.push_back( curveToJsonCurve( curve, true ) );
1701 }
1702
1703 data[u"curvePaths"_s] = paths;
1704 return data;
1705}
1706
1707QVariantList QgsArcGisRestUtils::polygonToJsonRings( const QgsPolygon *polygon )
1708{
1709 QVariantList rings;
1710 const int numInteriorRings = polygon->numInteriorRings();
1711 rings.reserve( numInteriorRings + 1 );
1712
1713 if ( const QgsLineString *exterior = qgsgeometry_cast< const QgsLineString * >( polygon->exteriorRing() ) )
1714 {
1715 // exterior ring MUST be clockwise
1716 switch ( exterior->orientation() )
1717 {
1719 rings.push_back( lineStringToJsonPath( exterior ) );
1720 break;
1721
1723 {
1724 std::unique_ptr< QgsLineString > reversed( exterior->reversed() );
1725 rings.push_back( lineStringToJsonPath( reversed.get() ) );
1726 break;
1727 }
1729 break;
1730 }
1731 }
1732
1733 for ( int i = 0; i < numInteriorRings; ++i )
1734 {
1735 const QgsLineString *ring = qgsgeometry_cast< const QgsLineString * >( polygon->interiorRing( i ) );
1736 // holes MUST be counter-clockwise
1737 switch ( ring->orientation() )
1738 {
1740 rings.push_back( lineStringToJsonPath( ring ) );
1741 break;
1742
1744 {
1745 std::unique_ptr< QgsLineString > reversed( ring->reversed() );
1746 rings.push_back( lineStringToJsonPath( reversed.get() ) );
1747 break;
1748 }
1750 break;
1751 }
1752 }
1753 return rings;
1754}
1755
1756QVariantList QgsArcGisRestUtils::curvePolygonToJsonRings( const QgsCurvePolygon *polygon )
1757{
1758 QVariantList rings;
1759 const int numInteriorRings = polygon->numInteriorRings();
1760 rings.reserve( numInteriorRings + 1 );
1761
1762 if ( const QgsCurve *exterior = qgsgeometry_cast< const QgsCurve * >( polygon->exteriorRing() ) )
1763 {
1764 // exterior ring MUST be clockwise
1765 switch ( exterior->orientation() )
1766 {
1768 rings.push_back( curveToJsonCurve( exterior, true ) );
1769 break;
1770
1772 {
1773 std::unique_ptr< QgsCurve > reversed( exterior->reversed() );
1774 rings.push_back( curveToJsonCurve( reversed.get(), true ) );
1775 break;
1776 }
1778 break;
1779 }
1780 }
1781
1782 for ( int i = 0; i < numInteriorRings; ++i )
1783 {
1784 const QgsCurve *ring = qgsgeometry_cast< const QgsCurve * >( polygon->interiorRing( i ) );
1785 // holes MUST be counter-clockwise
1786 switch ( ring->orientation() )
1787 {
1789 rings.push_back( curveToJsonCurve( ring, true ) );
1790 break;
1791
1793 {
1794 std::unique_ptr< QgsCurve > reversed( ring->reversed() );
1795 rings.push_back( curveToJsonCurve( reversed.get(), true ) );
1796 break;
1797 }
1799 break;
1800 }
1801 }
1802 return rings;
1803}
1804
1805QVariantMap QgsArcGisRestUtils::polygonToJson( const QgsPolygon *polygon )
1806{
1807 QVariantMap data;
1808 const bool hasZ = polygon->is3D();
1809 const bool hasM = polygon->isMeasure();
1810 data[u"hasM"_s] = hasM;
1811 data[u"hasZ"_s] = hasZ;
1812 data[u"rings"_s] = polygonToJsonRings( polygon );
1813 return data;
1814}
1815
1816QVariantMap QgsArcGisRestUtils::curvePolygonToJson( const QgsCurvePolygon *polygon )
1817{
1818 QVariantMap data;
1819 const bool hasZ = polygon->is3D();
1820 const bool hasM = polygon->isMeasure();
1821 data[u"hasM"_s] = hasM;
1822 data[u"hasZ"_s] = hasZ;
1823 data[u"curveRings"_s] = curvePolygonToJsonRings( polygon );
1824 return data;
1825}
1826
1827QVariantMap QgsArcGisRestUtils::multiPolygonToJson( const QgsMultiPolygon *multiPolygon )
1828{
1829 QVariantMap data;
1830 const bool hasZ = multiPolygon->is3D();
1831 const bool hasM = multiPolygon->isMeasure();
1832 data[u"hasM"_s] = hasM;
1833 data[u"hasZ"_s] = hasZ;
1834
1835 const int size = multiPolygon->numGeometries();
1836 QVariantList rings;
1837 for ( int i = 0; i < size; ++i )
1838 {
1839 const QgsPolygon *polygon = multiPolygon->polygonN( i );
1840 rings.append( polygonToJsonRings( polygon ) );
1841 }
1842
1843 data[u"rings"_s] = rings;
1844 return data;
1845}
1846
1847QVariantMap QgsArcGisRestUtils::multiSurfaceToJson( const QgsMultiSurface *multiSurface )
1848{
1849 QVariantMap data;
1850 const bool hasZ = multiSurface->is3D();
1851 const bool hasM = multiSurface->isMeasure();
1852 data[u"hasM"_s] = hasM;
1853 data[u"hasZ"_s] = hasZ;
1854
1855 const int size = multiSurface->numGeometries();
1856 QVariantList rings;
1857 for ( int i = 0; i < size; ++i )
1858 {
1859 const QgsCurvePolygon *polygon = qgsgeometry_cast< const QgsCurvePolygon * >( multiSurface->geometryN( i ) );
1860 if ( !polygon )
1861 continue;
1862
1863 rings.append( curvePolygonToJsonRings( polygon ) );
1864 }
1865
1866 data[u"curveRings"_s] = rings;
1867 return data;
1868}
1869
1871{
1872 QVariantMap res;
1873 if ( !crs.isValid() )
1874 return res;
1875
1876 const QString authid = crs.authid();
1877 if ( !authid.isEmpty() )
1878 {
1879 const thread_local QRegularExpression rxAuthid( u"(\\w+):(\\d+)"_s );
1880 const QRegularExpressionMatch match = rxAuthid.match( authid );
1881 if ( match.hasMatch()
1882 && (
1883 ( match.captured( 1 ).compare( "EPSG"_L1, Qt::CaseInsensitive ) == 0 )
1884 || ( match.captured( 1 ).compare( "ESRI"_L1, Qt::CaseInsensitive ) == 0 )
1885 )
1886 )
1887 {
1888 const QString wkid = match.captured( 2 );
1889 res.insert( u"wkid"_s, wkid );
1890 return res;
1891 }
1892 }
1893
1894 // docs don't mention the WKT version support, so let's hope for 2.0...
1895 res.insert( u"wkt"_s, crs.toWkt( Qgis::CrsWktVariant::Wkt2_2019Simplified ) );
1896
1897 return res;
1898}
1899
1901{
1902 QVariantMap res;
1903 if ( ( flags & FeatureToJsonFlag::IncludeGeometry ) && feature.hasGeometry() )
1904 {
1905 res.insert( u"geometry"_s, geometryToJson( feature.geometry(), context, crs ) );
1906 }
1907
1908 QVariantMap attributes;
1909 const QgsFields fields = feature.fields();
1910 for ( const QgsField &field : fields )
1911 {
1912 QVariant value = feature.attribute( field.name() );
1913 if ( value.userType() == qMetaTypeId< QgsUnsetAttributeValue >() )
1914 {
1915 if ( flags.testFlag( FeatureToJsonFlag::SkipUnsetAttributes ) )
1916 continue;
1917 else
1918 value = QVariant(); // reset to null, we can't store 'QgsUnsetAttributeValue' as json
1919 }
1920
1921 if ( ( flags & FeatureToJsonFlag::IncludeNonObjectIdAttributes ) || field.name() == context.objectIdFieldName() )
1922 attributes.insert( field.name(), variantToAttributeValue( value, field.type(), context ) );
1923 }
1924 if ( !attributes.isEmpty() )
1925 {
1926 res.insert( u"attributes"_s, attributes );
1927 }
1928 return res;
1929}
1930
1931QVariant QgsArcGisRestUtils::variantToAttributeValue( const QVariant &variant, QMetaType::Type expectedType, const QgsArcGisRestContext &context )
1932{
1933 if ( QgsVariantUtils::isNull( variant ) )
1934 return QVariant();
1935
1936 switch ( expectedType )
1937 {
1938 case QMetaType::Type::QString:
1939 {
1940 const QString escaped = variant.toString().replace( '\\', "\\\\"_L1 ).replace( '"', "\\\""_L1 );
1941 return QString( QUrl::toPercentEncoding( escaped, "'" ) );
1942 }
1943
1944 case QMetaType::Type::QDateTime:
1945 case QMetaType::Type::QDate:
1946 {
1947 switch ( variant.userType() )
1948 {
1949 case QMetaType::Type::QDateTime:
1950 return variant.toDateTime().toMSecsSinceEpoch();
1951
1952 case QMetaType::Type::QDate:
1953 // for date values, assume start of day -- the REST api requires datetime values only, not plain dates
1954 if ( context.timeZone().isValid() )
1955 return QDateTime( variant.toDate(), QTime( 0, 0, 0 ), context.timeZone() ).toMSecsSinceEpoch();
1956 else
1957 return QDateTime( variant.toDate(), QTime( 0, 0, 0 ) ).toMSecsSinceEpoch();
1958
1959 default:
1960 return QVariant();
1961 }
1962 }
1963
1964 default:
1965 return variant;
1966 }
1967}
1968
1970{
1971 QVariantMap res;
1972 res.insert( u"name"_s, field.name() );
1973
1974 QString fieldType;
1975 switch ( field.type() )
1976 {
1977 case QMetaType::Type::LongLong:
1978 fieldType = u"esriFieldTypeInteger"_s;
1979 break;
1980
1981 case QMetaType::Type::Int:
1982 fieldType = u"esriFieldTypeSmallInteger"_s;
1983 break;
1984
1985 case QMetaType::Type::Double:
1986 fieldType = u"esriFieldTypeDouble"_s;
1987 break;
1988
1989 case QMetaType::Type::QString:
1990 fieldType = u"esriFieldTypeString"_s;
1991 break;
1992
1993 case QMetaType::Type::QDateTime:
1994 case QMetaType::Type::QDate:
1995 fieldType = u"esriFieldTypeDate"_s;
1996 break;
1997
1998 case QMetaType::Type::QByteArray:
1999 fieldType = u"esriFieldTypeBlob"_s;
2000 break;
2001
2002 default:
2003 // fallback to string
2004 fieldType = u"esriFieldTypeString"_s;
2005 break;
2006 }
2007 res.insert( u"type"_s, fieldType );
2008
2009 if ( !field.alias().isEmpty() )
2010 res.insert( u"alias"_s, field.alias() );
2011
2012 // nullable
2014 res.insert( u"nullable"_s, !notNullable );
2015
2016 // editable
2017 res.insert( u"editable"_s, true );
2018
2019 return res;
2020}
2021
2023{
2024 if ( type.compare( "FeatureServer"_L1, Qt::CaseInsensitive ) == 0 )
2026 else if ( type.compare( "MapServer"_L1, Qt::CaseInsensitive ) == 0 )
2028 else if ( type.compare( "ImageServer"_L1, Qt::CaseInsensitive ) == 0 )
2030 else if ( type.compare( "GlobeServer"_L1, Qt::CaseInsensitive ) == 0 )
2032 else if ( type.compare( "GPServer"_L1, Qt::CaseInsensitive ) == 0 )
2034 else if ( type.compare( "GeocodeServer"_L1, Qt::CaseInsensitive ) == 0 )
2036 else if ( type.compare( "SceneServer"_L1, Qt::CaseInsensitive ) == 0 )
2038
2040}
2041
@ BelowLine
Labels can be placed below a line feature. Unless MapOrientation is also specified this mode respects...
Definition qgis.h:1338
@ MapOrientation
Signifies that the AboveLine and BelowLine flags should respect the map's orientation rather than the...
Definition qgis.h:1339
@ OnLine
Labels can be placed directly over a line feature.
Definition qgis.h:1336
@ AboveLine
Labels can be placed above a line feature. Unless MapOrientation is also specified this mode respects...
Definition qgis.h:1337
@ NoOrientation
Unknown orientation or sentinel value.
Definition qgis.h:3521
@ CounterClockwise
Counter-clockwise direction.
Definition qgis.h:3520
@ Clockwise
Clockwise direction.
Definition qgis.h:3519
@ OverPoint
Arranges candidates over a point (or centroid of a polygon), or at a preset offset from the point....
Definition qgis.h:1229
@ Line
Arranges candidates parallel to a generalised line representing the feature or parallel to a polygon'...
Definition qgis.h:1230
@ Horizontal
Arranges horizontal candidates scattered throughout a polygon feature. Applies to polygon layers only...
Definition qgis.h:1232
@ ScaleArea
Calculate scale by the area.
Definition qgis.h:646
ArcGisRestServiceType
Available ArcGIS REST service types.
Definition qgis.h:4480
@ GeocodeServer
GeocodeServer.
Definition qgis.h:4486
@ SceneServer
SceneServer.
Definition qgis.h:4488
@ Unknown
Other unknown/unsupported type.
Definition qgis.h:4487
@ GlobeServer
GlobeServer.
Definition qgis.h:4484
@ ImageServer
ImageServer.
Definition qgis.h:4483
@ FeatureServer
FeatureServer.
Definition qgis.h:4481
MarkerShape
Marker shapes.
Definition qgis.h:3166
@ Circle
Circle.
Definition qgis.h:3175
@ Triangle
Triangle.
Definition qgis.h:3171
@ Cross2
Rotated cross (lines only), 'x' shape.
Definition qgis.h:3178
@ Diamond
Diamond.
Definition qgis.h:3168
@ Square
Square.
Definition qgis.h:3167
@ Cross
Cross (lines only).
Definition qgis.h:3176
@ AboveRight
Above right.
Definition qgis.h:1318
@ BelowLeft
Below left.
Definition qgis.h:1322
@ Above
Above center.
Definition qgis.h:1317
@ BelowRight
Below right.
Definition qgis.h:1324
@ Right
Right middle.
Definition qgis.h:1321
@ AboveLeft
Above left.
Definition qgis.h:1316
@ Below
Below center.
Definition qgis.h:1323
@ Over
Center middle.
Definition qgis.h:1320
VerticalAnchorPoint
Marker symbol vertical anchor points.
Definition qgis.h:835
@ Bottom
Align to bottom of symbol.
Definition qgis.h:838
@ Center
Align to vertical center of symbol.
Definition qgis.h:837
@ Top
Align to top of symbol.
Definition qgis.h:836
@ Points
Points (e.g., for font sizes).
Definition qgis.h:5310
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:291
@ CompoundCurve
CompoundCurve.
Definition qgis.h:302
@ Point
Point.
Definition qgis.h:293
@ LineString
LineString.
Definition qgis.h:294
@ MultiPoint
MultiPoint.
Definition qgis.h:297
@ Polygon
Polygon.
Definition qgis.h:295
@ MultiPolygon
MultiPolygon.
Definition qgis.h:299
@ Triangle
Triangle.
Definition qgis.h:296
@ NoGeometry
No geometry.
Definition qgis.h:309
@ MultiLineString
MultiLineString.
Definition qgis.h:298
@ Unknown
Unknown.
Definition qgis.h:292
@ CircularString
CircularString.
Definition qgis.h:301
@ GeometryCollection
GeometryCollection.
Definition qgis.h:300
@ MultiCurve
MultiCurve.
Definition qgis.h:304
@ CurvePolygon
CurvePolygon.
Definition qgis.h:303
@ MultiSurface
MultiSurface.
Definition qgis.h:305
@ Wkt2_2019Simplified
WKT2_2019 with the simplification rule of WKT2_SIMPLIFIED.
Definition qgis.h:2511
HorizontalAnchorPoint
Marker symbol horizontal anchor points.
Definition qgis.h:821
@ Center
Align to horizontal center of symbol.
Definition qgis.h:823
@ Right
Align to right side of symbol.
Definition qgis.h:824
@ Left
Align to left side of symbol.
Definition qgis.h:822
Abstract base class for all geometries.
virtual const QgsAbstractGeometry * simplifiedTypeRef() const
Returns a reference to the simplest lossless representation of this geometry, e.g.
bool isMeasure() const
Returns true if the geometry contains m values.
bool is3D() const
Returns true if the geometry is 3D and contains a z-value.
Qgis::WkbType wkbType() const
Returns the WKB type of the geometry.
Contains the context of a ArcGIS REST service operation.
QTimeZone timeZone() const
Returns the time zone for datetime values.
QString objectIdFieldName() const
Returns the name of the objectId field.
static std::unique_ptr< QgsAbstractGeometry > convertGeometry(const QVariantMap &geometry, const QString &esriGeometryType, bool hasM, bool hasZ, bool allowCurves=true, QgsCoordinateReferenceSystem *crs=nullptr)
Converts an ESRI REST geometry JSON definition to a QgsAbstractGeometry.
static QVariantMap fieldDefinitionToJson(const QgsField &field)
Converts a field's definition to an ArcGIS REST JSON representation.
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 Qgis::WkbType convertGeometryType(const QString &type)
Converts an ESRI REST geometry type to a WKB type.
static QString convertLabelingExpression(const QString &string)
Converts an ESRI labeling expression to a QGIS expression string.
static QVariantMap geometryToJson(const QgsGeometry &geometry, const QgsArcGisRestContext &context, const QgsCoordinateReferenceSystem &crs=QgsCoordinateReferenceSystem())
Converts a geometry to an ArcGIS REST JSON representation.
static Qgis::ArcGisRestServiceType serviceTypeFromString(const QString &type)
Converts a string value to a REST service type.
static QVariant variantToAttributeValue(const QVariant &variant, QMetaType::Type expectedType, const QgsArcGisRestContext &context)
Converts a variant to a REST attribute value.
static QVariantMap featureToJson(const QgsFeature &feature, const QgsArcGisRestContext &context, const QgsCoordinateReferenceSystem &crs=QgsCoordinateReferenceSystem(), QgsArcGisRestUtils::FeatureToJsonFlags flags=QgsArcGisRestUtils::FeatureToJsonFlags(static_cast< int >(QgsArcGisRestUtils::FeatureToJsonFlag::IncludeGeometry)|static_cast< int >(QgsArcGisRestUtils::FeatureToJsonFlag::IncludeNonObjectIdAttributes)))
Converts a feature to an ArcGIS REST JSON representation.
static Qt::PenStyle convertLineStyle(const QString &style)
Converts an ESRI line style to a Qt pen style.
@ IncludeGeometry
Whether to include the geometry definition.
@ IncludeNonObjectIdAttributes
Whether to include any non-objectId attributes.
static std::unique_ptr< QgsSymbol > convertSymbol(const QVariantMap &definition)
Converts a symbol JSON definition to a QgsSymbol.
static std::unique_ptr< QgsFeatureRenderer > convertRenderer(const QVariantMap &rendererData)
Converts renderer JSON data to an equivalent QgsFeatureRenderer.
static QMetaType::Type convertFieldType(const QString &type)
Converts an ESRI REST field type to a QVariant type.
static Qt::BrushStyle convertFillStyle(const QString &style)
Converts an ESRI fill style to a Qt brush style.
static QVariantMap crsToJson(const QgsCoordinateReferenceSystem &crs)
Converts a crs to an ArcGIS REST JSON representation.
static std::unique_ptr< QgsAbstractVectorLayerLabeling > convertLabeling(const QVariantList &data)
Converts labeling JSON data to an equivalent QGIS vector labeling.
QFlags< FeatureToJsonFlag > FeatureToJsonFlags
Flags which control the behavior of converting features to JSON.
static QColor convertColor(const QVariant &data)
Converts ESRI JSON color data to a QColor object.
static QgsRectangle convertRectangle(const QVariant &value)
Converts a rectangle value to a QgsRectangle.
QgsPoint startPoint() const override
Returns the starting point of the curve.
bool isEmpty() const override
Returns true if the geometry is empty.
int numPoints() const override
Returns the number of points in the curve.
QgsPoint pointN(int i) const
Returns the point at index i within the circular string.
A dummy implementation class method which does not compute any breaks.
A classification method which uses equal width intervals.
Implementation of a fixed interval classification.
A classification method for natural breaks, based on Jenks method.
A classification method which creates classes based on quantiles.
A classification method which classifies based on standard deviation of values.
QgsPropertyTransformer subclass for transforming a numeric value into a color from a color ramp.
int nCurves() const
Returns the number of curves in the geometry.
const QgsCurve * curveAt(int i) const
Returns the curve at the specified index.
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.
QString toWkt(Qgis::CrsWktVariant variant=Qgis::CrsWktVariant::Wkt1Gdal, bool multiline=false, int indentationWidth=4) const
Returns a WKT representation of this CRS.
Curve polygon geometry type.
int numInteriorRings() const
Returns the number of interior rings contained with the curve polygon.
const QgsCurve * exteriorRing() const
Returns the curve polygon's exterior ring.
const QgsCurve * interiorRing(int i) const
Retrieves an interior ring from the curve polygon.
Abstract base class for curved geometry type.
Definition qgscurve.h:36
Qgis::AngularDirection orientation() const
Returns the curve's orientation, e.g.
Definition qgscurve.cpp:287
virtual QgsCurve * reversed() const =0
Returns a reversed copy of the curve, where the direction of the curve has been flipped.
virtual QgsPoint endPoint() const =0
Returns the end point of the curve.
Handles parsing and evaluation of expressions (formerly called "search strings").
bool isValid() const
Checks if this expression is valid.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:60
QgsFields fields
Definition qgsfeature.h:70
QgsGeometry geometry
Definition qgsfeature.h:71
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Q_INVOKABLE QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
@ ConstraintNotNull
Field may not be null.
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:56
QMetaType::Type type
Definition qgsfield.h:63
QString name
Definition qgsfield.h:65
QString alias
Definition qgsfield.h:66
QgsFieldConstraints constraints
Definition qgsfield.h:68
Container of fields for a vector layer.
Definition qgsfields.h:46
int numGeometries() const
Returns the number of geometries within the collection.
const QgsAbstractGeometry * geometryN(int n) const
Returns a const reference to a geometry from within the collection.
A geometry is the spatial representation of a feature.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
Represents a color stop within a QgsGradientColorRamp color ramp.
void setPlacementFlags(Qgis::LabelLinePlacementFlags flags)
Returns the line placement flags, which dictate how line labels can be placed above or below the line...
void setQuadrant(Qgis::LabelQuadrantPosition quadrant)
Sets the quadrant in which to offset labels from the point.
Line string geometry type, with support for z-dimension and m-values.
const double * yData() const
Returns a const pointer to the y vertex data.
const double * xData() const
Returns a const pointer to the x vertex data.
const double * zData() const
Returns a const pointer to the z vertex data, or nullptr if the linestring does not have z values.
int numPoints() const override
Returns the number of points in the curve.
QgsLineString * reversed() const override
Returns a reversed copy of the curve, where the direction of the curve has been flipped.
const double * mData() const
Returns a const pointer to the m vertex data, or nullptr if the linestring does not have m values.
Multi curve geometry collection.
QgsCurve * curveN(int index)
Returns the curve with the specified index.
Multi line string geometry collection.
QgsLineString * lineStringN(int index)
Returns the line string with the specified index.
Multi point geometry collection.
QgsPoint * pointN(int index)
Returns the point with the specified index.
Multi polygon geometry collection.
QgsPolygon * polygonN(int index)
Returns the polygon with the specified index.
Multi surface geometry collection.
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.
Qgis::LabelPlacement placement
Label placement mode.
bool isExpression
true if this label is made from a expression string, e.g., FieldName || 'mm'
const QgsLabelLineSettings & lineSettings() const
Returns the label line settings, which contain settings related to how the label engine places and fo...
QString fieldName
Name of field (or an expression) to use for label text.
const QgsLabelPointSettings & pointSettings() const
Returns the label point settings, which contain settings related to how the label engine places and f...
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:53
void clear() override
Clears the geometry, ie reset it to a null geometry.
Definition qgspoint.cpp:385
double z
Definition qgspoint.h:58
double x
Definition qgspoint.h:56
bool isEmpty() const override
Returns true if the geometry is empty.
Definition qgspoint.cpp:760
double m
Definition qgspoint.h:59
double y
Definition qgspoint.h:57
Polygon geometry type.
Definition qgspolygon.h:37
A store for object properties.
void setTransformer(QgsPropertyTransformer *transformer)
Sets an optional transformer to use for manipulating the calculated values for the property.
static QgsProperty fromField(const QString &fieldName, bool isActive=true)
Returns a new FieldBasedProperty created from the specified field name.
A rectangle specified with double values.
Represents an individual category (class) from a QgsCategorizedSymbolRenderer.
Represents a value range for a QgsGraduatedSymbolRenderer.
void setUpperValue(double upperValue)
Sets the upper bound of the range.
void setSymbol(QgsSymbol *s)
Sets the symbol used for the range.
void setLabel(const QString &label)
Sets the label used for the range.
void setLowerValue(double lowerValue)
Sets the lower bound of the range.
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
Container for settings relating to a text buffer.
void setColor(const QColor &color)
Sets the color for the buffer.
void setSizeUnit(Qgis::RenderUnit unit)
Sets the units used for the buffer size.
void setEnabled(bool enabled)
Sets whether the text buffer will be drawn.
void setSize(double size)
Sets the size of the buffer.
Container for all settings relating to text rendering.
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 setSizeUnit(Qgis::RenderUnit unit)
Sets the units for the size of rendered text.
void setBuffer(const QgsTextBufferSettings &bufferSettings)
Sets the text's buffer settings.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
static Qgis::WkbType zmType(Qgis::WkbType type, bool hasZ, bool hasM)
Returns the modified input geometry type according to hasZ / hasM.
static Q_INVOKABLE bool hasZ(Qgis::WkbType type)
Tests whether a WKB type contains the z-dimension.
static Q_INVOKABLE bool hasM(Qgis::WkbType type)
Tests whether a WKB type contains m values.
static Qgis::WkbType flatType(Qgis::WkbType type)
Returns the flat type for a WKB type.
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:6950
T qgsgeometry_cast(QgsAbstractGeometry *geom)
QList< QgsRendererCategory > QgsCategoryList
QList< QgsGradientStop > QgsGradientStopsList
List of gradient stops.
#define QgsDebugError(str)
Definition qgslogger.h:59
QList< QgsSymbolLayer * > QgsSymbolLayerList
Definition qgssymbol.h:30