QGIS API Documentation 4.1.0-Master (5bf3c20f3c9)
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
242 = QgsPoint( lineX.at( actualLineSize - 1 ), lineY.at( actualLineSize - 1 ), hasZ ? lineZ.at( actualLineSize - 1 ) : std::numeric_limits< double >::quiet_NaN(), hasM ? lineM.at( actualLineSize - 1 ) : std::numeric_limits< double >::quiet_NaN() );
243 }
244 std::unique_ptr< QgsCircularString > circularString( convertCircularString( curveData.toMap(), pointType, lastLineStringPoint ) );
245 if ( !circularString )
246 {
247 return nullptr;
248 }
249
250 if ( actualLineSize > 0 )
251 {
252 lineX.resize( actualLineSize );
253 lineY.resize( actualLineSize );
254 if ( hasZ )
255 lineZ.resize( actualLineSize );
256 if ( hasM )
257 lineM.resize( actualLineSize );
258
259 compoundCurve->addCurve( new QgsLineString( lineX, lineY, lineZ, lineM ) );
260 lineX.resize( maxCurveListSize - curveListIndex );
261 lineY.resize( maxCurveListSize - curveListIndex );
262 if ( hasZ )
263 lineZ.resize( maxCurveListSize - curveListIndex );
264 if ( hasM )
265 lineM.resize( maxCurveListSize - curveListIndex );
266 outLineX = lineX.data();
267 outLineY = lineY.data();
268 outLineZ = lineZ.data();
269 outLineM = lineM.data();
270 }
271
272 // If the previous curve had less than two points, remove it
273 if ( compoundCurve->curveAt( compoundCurve->nCurves() - 1 )->nCoordinates() < 2 )
274 compoundCurve->removeCurve( compoundCurve->nCurves() - 1 );
275
276 const QgsPoint endPointCircularString = circularString->endPoint();
277 compoundCurve->addCurve( circularString.release() );
278
279 // Prepare a new line string
280 actualLineSize = 1;
281 *outLineX++ = endPointCircularString.x();
282 *outLineY++ = endPointCircularString.y();
283 if ( hasZ )
284 *outLineZ++ = endPointCircularString.z();
285 if ( hasM )
286 *outLineM++ = endPointCircularString.m();
287 }
288 curveListIndex++;
289 }
290
291 if ( actualLineSize == 1 && compoundCurve->nCurves() > 0 )
292 {
293 const QgsCurve *finalCurve = compoundCurve->curveAt( compoundCurve->nCurves() - 1 );
294 const QgsPoint finalCurveEndPoint = finalCurve->endPoint();
295 if ( qgsDoubleNear( finalCurveEndPoint.x(), lineX.at( 0 ) )
296 && qgsDoubleNear( finalCurveEndPoint.y(), lineY.at( 0 ) )
297 && ( !hasZ || qgsDoubleNear( finalCurveEndPoint.z(), lineZ.at( 0 ) ) )
298 && ( !hasM || qgsDoubleNear( finalCurveEndPoint.m(), lineM.at( 0 ) ) ) )
299 {
300 actualLineSize = 0; // redundant final curve containing a duplicate vertex
301 }
302 }
303
304 if ( actualLineSize > 0 )
305 {
306 lineX.resize( actualLineSize );
307 lineY.resize( actualLineSize );
308 if ( hasZ )
309 lineZ.resize( actualLineSize );
310 if ( hasM )
311 lineM.resize( actualLineSize );
312 compoundCurve->addCurve( new QgsLineString( lineX, lineY, lineZ, lineM ) );
313 }
314
315 return compoundCurve;
316}
317
318std::unique_ptr<QgsLineString> QgsArcGisRestUtils::convertLineString( const QVariantList &curvesList, Qgis::WkbType pointType )
319{
320 auto linestring = std::make_unique< QgsLineString >();
321
322 QVector< double > lineX;
323 QVector< double > lineY;
324 QVector< double > lineZ;
325 QVector< double > lineM;
326 const int maxCurveListSize = static_cast< int >( curvesList.size() );
327 lineX.resize( maxCurveListSize );
328 lineY.resize( maxCurveListSize );
329
330 const bool hasZ = QgsWkbTypes::hasZ( pointType );
331 if ( hasZ )
332 lineZ.resize( maxCurveListSize );
333 const bool hasM = QgsWkbTypes::hasM( pointType );
334 if ( hasM )
335 lineM.resize( maxCurveListSize );
336
337 double *outLineX = lineX.data();
338 double *outLineY = lineY.data();
339 double *outLineZ = lineZ.data();
340 double *outLineM = lineM.data();
341
342 bool xok = false;
343 bool yok = false;
344
345 int actualLineSize = 0;
346 for ( const QVariant &curveData : curvesList )
347 {
348 if ( curveData.userType() == QMetaType::Type::QVariantList )
349 {
350 const QVariantList coordList = curveData.toList();
351 const int nCoords = static_cast< int >( coordList.size() );
352 if ( nCoords < 2 )
353 return nullptr;
354
355 const double x = coordList[0].toDouble( &xok );
356 const double y = coordList[1].toDouble( &yok );
357 if ( !xok || !yok )
358 return nullptr;
359
360 actualLineSize++;
361 *outLineX++ = x;
362 *outLineY++ = y;
363 if ( hasZ )
364 {
365 *outLineZ++ = nCoords >= 3 ? coordList[2].toDouble() : std::numeric_limits< double >::quiet_NaN();
366 }
367
368 if ( hasM )
369 {
370 // 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
371 *outLineM++ = ( ( hasZ && nCoords >= 4 ) || ( !hasZ && nCoords >= 3 ) ) ? coordList[hasZ ? 3 : 2].toDouble() : std::numeric_limits< double >::quiet_NaN();
372 }
373 }
374 else
375 {
376 QgsDebugError( u"Found unexpected value when parsing ESRI json line string. Expected list, got %1"_s.arg( curveData.metaType().name() ) );
377 return nullptr;
378 }
379 }
380
381 if ( actualLineSize == 0 )
382 return nullptr;
383
384 lineX.resize( actualLineSize );
385 lineY.resize( actualLineSize );
386 if ( hasZ )
387 lineZ.resize( actualLineSize );
388 if ( hasM )
389 lineM.resize( actualLineSize );
390 return std::make_unique< QgsLineString>( lineX, lineY, lineZ, lineM );
391}
392
393std::unique_ptr< QgsPoint > QgsArcGisRestUtils::convertGeometryPoint( const QVariantMap &geometryData, Qgis::WkbType pointType )
394{
395 // {"x" : <x>, "y" : <y>, "z" : <z>, "m" : <m>}
396 bool xok = false, yok = false;
397 double x = geometryData[u"x"_s].toDouble( &xok );
398 double y = geometryData[u"y"_s].toDouble( &yok );
399 if ( !xok || !yok )
400 return nullptr;
401 double z = geometryData[u"z"_s].toDouble();
402 double m = geometryData[u"m"_s].toDouble();
403 return std::make_unique< QgsPoint >( pointType, x, y, z, m );
404}
405
406std::unique_ptr< QgsMultiPoint > QgsArcGisRestUtils::convertMultiPoint( const QVariantMap &geometryData, Qgis::WkbType pointType )
407{
408 // {"points" : [[ <x1>, <y1>, <z1>, <m1> ] , [ <x2>, <y2>, <z2>, <m2> ], ... ]}
409 const QVariantList coordsList = geometryData[u"points"_s].toList();
410
411 auto multiPoint = std::make_unique< QgsMultiPoint >();
412 multiPoint->reserve( static_cast< int >( coordsList.size() ) );
413 for ( const QVariant &coordData : coordsList )
414 {
415 const QVariantList coordList = coordData.toList();
416 std::unique_ptr< QgsPoint > p = convertPoint( coordList, pointType );
417 if ( !p )
418 {
419 continue;
420 }
421 multiPoint->addGeometry( p.release() );
422 }
423
424 // second chance -- sometimes layers are reported as multipoint but features have single
425 // point geometries. Silently handle this and upgrade to multipoint.
426 std::unique_ptr< QgsPoint > p = convertGeometryPoint( geometryData, pointType );
427 if ( p )
428 multiPoint->addGeometry( p.release() );
429
430 if ( multiPoint->numGeometries() == 0 )
431 {
432 // didn't find any points, so reset geometry to null
433 multiPoint.reset();
434 }
435 return multiPoint;
436}
437
438std::unique_ptr< QgsMultiCurve > QgsArcGisRestUtils::convertGeometryPolyline( const QVariantMap &geometryData, Qgis::WkbType pointType, bool allowCurves )
439{
440 // {"curvePaths": [[[0,0], {"c": [[3,3],[1,4]]} ]]}
441 QVariantList pathsList;
442 if ( geometryData[u"paths"_s].isValid() )
443 pathsList = geometryData[u"paths"_s].toList();
444 else if ( geometryData[u"curvePaths"_s].isValid() )
445 pathsList = geometryData[u"curvePaths"_s].toList();
446 if ( pathsList.isEmpty() )
447 return nullptr;
448 std::unique_ptr< QgsMultiCurve > multiCurve = allowCurves ? std::make_unique< QgsMultiCurve >() : std::make_unique< QgsMultiLineString >();
449 multiCurve->reserve( static_cast< int >( pathsList.size() ) );
450 for ( const QVariant &pathData : std::as_const( pathsList ) )
451 {
452 std::unique_ptr< QgsCurve > curve = allowCurves ? convertCompoundCurve( pathData.toList(), pointType ) : convertLineString( pathData.toList(), pointType );
453 if ( !curve )
454 {
455 return nullptr;
456 }
457 multiCurve->addGeometry( curve.release() );
458 }
459 return multiCurve;
460}
461
462std::unique_ptr< QgsMultiSurface > QgsArcGisRestUtils::convertGeometryPolygon( const QVariantMap &geometryData, Qgis::WkbType pointType, bool allowCurves )
463{
464 // {"curveRings": [[[0,0], {"c": [[3,3],[1,4]]} ]]}
465 QVariantList ringsList;
466 if ( geometryData[u"rings"_s].isValid() )
467 ringsList = geometryData[u"rings"_s].toList();
468 else if ( geometryData[u"ringPaths"_s].isValid() )
469 ringsList = geometryData[u"ringPaths"_s].toList();
470 if ( ringsList.isEmpty() )
471 return nullptr;
472
473 QList< QgsCurve * > curves;
474 for ( int i = 0, n = static_cast< int >( ringsList.size() ); i < n; ++i )
475 {
476 std::unique_ptr< QgsCurve > curve = allowCurves ? convertCompoundCurve( ringsList[i].toList(), pointType ) : convertLineString( ringsList[i].toList(), pointType );
477 if ( !curve )
478 {
479 continue;
480 }
481 curves.append( curve.release() );
482 }
483 if ( curves.count() == 0 )
484 return nullptr;
485
486 std::unique_ptr< QgsMultiSurface > result = allowCurves ? std::make_unique< QgsMultiSurface >() : std::make_unique< QgsMultiPolygon >();
487 if ( curves.count() == 1 )
488 {
489 // shortcut for exterior ring only
490 std::unique_ptr< QgsCurvePolygon > newPolygon = allowCurves ? std::make_unique< QgsCurvePolygon >() : std::make_unique< QgsPolygon >();
491 newPolygon->setExteriorRing( curves.takeAt( 0 ) );
492 result->addGeometry( newPolygon.release() );
493 return result;
494 }
495
496 std::sort( curves.begin(), curves.end(), []( const QgsCurve *a, const QgsCurve *b ) -> bool {
497 double a_area = 0.0;
498 double b_area = 0.0;
499 a->sumUpArea( a_area );
500 b->sumUpArea( b_area );
501 return std::abs( a_area ) > std::abs( b_area );
502 } );
503 result->reserve( static_cast< int >( curves.size() ) );
504 while ( !curves.isEmpty() )
505 {
506 QgsCurve *exterior = curves.takeFirst();
507 QgsCurvePolygon *newPolygon = allowCurves ? new QgsCurvePolygon() : new QgsPolygon();
508 newPolygon->setExteriorRing( exterior );
509 std::unique_ptr<QgsGeometryEngine> engine( QgsGeometry::createGeometryEngine( newPolygon ) );
510 engine->prepareGeometry();
511
512 QMutableListIterator< QgsCurve * > it( curves );
513 while ( it.hasNext() )
514 {
515 QgsCurve *curve = it.next();
516 QgsRectangle boundingBox = newPolygon->boundingBox();
517 if ( boundingBox.intersects( curve->boundingBox() ) )
518 {
519 QgsPoint point = curve->startPoint();
520 if ( engine->contains( &point ) )
521 {
522 newPolygon->addInteriorRing( curve );
523 it.remove();
524 engine.reset( QgsGeometry::createGeometryEngine( newPolygon ) );
525 engine->prepareGeometry();
526 }
527 }
528 }
529 result->addGeometry( newPolygon );
530 }
531 if ( result->numGeometries() == 0 )
532 return nullptr;
533
534 return result;
535}
536
537std::unique_ptr< QgsPolygon > QgsArcGisRestUtils::convertEnvelope( const QVariantMap &geometryData )
538{
539 // {"xmin" : -109.55, "ymin" : 25.76, "xmax" : -86.39, "ymax" : 49.94}
540 bool xminOk = false, yminOk = false, xmaxOk = false, ymaxOk = false;
541 double xmin = geometryData[u"xmin"_s].toDouble( &xminOk );
542 double ymin = geometryData[u"ymin"_s].toDouble( &yminOk );
543 double xmax = geometryData[u"xmax"_s].toDouble( &xmaxOk );
544 double ymax = geometryData[u"ymax"_s].toDouble( &ymaxOk );
545 if ( !xminOk || !yminOk || !xmaxOk || !ymaxOk )
546 return nullptr;
547 auto ext = std::make_unique< QgsLineString>();
548 ext->addVertex( QgsPoint( xmin, ymin ) );
549 ext->addVertex( QgsPoint( xmax, ymin ) );
550 ext->addVertex( QgsPoint( xmax, ymax ) );
551 ext->addVertex( QgsPoint( xmin, ymax ) );
552 ext->addVertex( QgsPoint( xmin, ymin ) );
553 auto poly = std::make_unique< QgsPolygon >();
554 poly->setExteriorRing( ext.release() );
555 return poly;
556}
557
558std::unique_ptr< QgsAbstractGeometry > QgsArcGisRestUtils::convertGeometry(
559 const QVariantMap &geometryData, const QString &esriGeometryType, bool readM, bool readZ, bool allowCurves, QgsCoordinateReferenceSystem *crs
560)
561{
562 Qgis::WkbType pointType = QgsWkbTypes::zmType( Qgis::WkbType::Point, readZ, readM );
563 if ( crs )
564 {
565 *crs = convertSpatialReference( geometryData[u"spatialReference"_s].toMap() );
566 }
567
568 // http://resources.arcgis.com/en/help/arcgis-rest-api/index.html#/Geometry_Objects/02r3000000n1000000/
569 if ( esriGeometryType == "esriGeometryNull"_L1 )
570 return nullptr;
571 else if ( esriGeometryType == "esriGeometryPoint"_L1 )
572 return convertGeometryPoint( geometryData, pointType );
573 else if ( esriGeometryType == "esriGeometryMultipoint"_L1 )
574 return convertMultiPoint( geometryData, pointType );
575 else if ( esriGeometryType == "esriGeometryPolyline"_L1 )
576 return convertGeometryPolyline( geometryData, pointType, allowCurves );
577 else if ( esriGeometryType == "esriGeometryPolygon"_L1 )
578 return convertGeometryPolygon( geometryData, pointType, allowCurves );
579 else if ( esriGeometryType == "esriGeometryEnvelope"_L1 )
580 return convertEnvelope( geometryData );
581 // Unsupported (either by qgis, or format unspecified by the specification)
582 // esriGeometryCircularArc
583 // esriGeometryEllipticArc
584 // esriGeometryBezier3Curve
585 // esriGeometryPath
586 // esriGeometryRing
587 // esriGeometryLine
588 // esriGeometryAny
589 // esriGeometryMultiPatch
590 // esriGeometryTriangleStrip
591 // esriGeometryTriangleFan
592 // esriGeometryRay
593 // esriGeometrySphere
594 // esriGeometryTriangles
595 // esriGeometryBag
596 return nullptr;
597}
598
600{
602
603 QString spatialReference = spatialReferenceMap[u"latestWkid"_s].toString();
604 if ( spatialReference.isEmpty() )
605 spatialReference = spatialReferenceMap[u"wkid"_s].toString();
606
607 // prefer using authority/id wherever we can
608 if ( !spatialReference.isEmpty() )
609 {
610 crs.createFromString( u"EPSG:%1"_s.arg( spatialReference ) );
611 if ( !crs.isValid() )
612 {
613 // Try as an ESRI auth
614 crs.createFromString( u"ESRI:%1"_s.arg( spatialReference ) );
615 }
616 }
617 else if ( !spatialReferenceMap[u"wkt"_s].toString().isEmpty() )
618 {
619 // otherwise fallback to WKT
620 crs.createFromWkt( spatialReferenceMap[u"wkt"_s].toString() );
621 }
622
623 if ( !crs.isValid() )
624 {
625 // If no spatial reference, just use WGS84
626 // TODO -- this needs further investigation! Most ESRI server services default to 3857, so that would likely be
627 // a safer fallback to use...
628 crs.createFromString( u"EPSG:4326"_s );
629 }
630 return crs;
631}
632
633std::unique_ptr< QgsSymbol > QgsArcGisRestUtils::convertSymbol( const QVariantMap &symbolData )
634{
635 const QString type = symbolData.value( u"type"_s ).toString();
636 if ( type == "esriSMS"_L1 )
637 {
638 // marker symbol
639 return parseEsriMarkerSymbolJson( symbolData );
640 }
641 else if ( type == "esriSLS"_L1 )
642 {
643 // line symbol
644 return parseEsriLineSymbolJson( symbolData );
645 }
646 else if ( type == "esriSFS"_L1 )
647 {
648 // fill symbol
649 return parseEsriFillSymbolJson( symbolData );
650 }
651 else if ( type == "esriPFS"_L1 )
652 {
653 return parseEsriPictureFillSymbolJson( symbolData );
654 }
655 else if ( type == "esriPMS"_L1 )
656 {
657 // picture marker
658 return parseEsriPictureMarkerSymbolJson( symbolData );
659 }
660 else if ( type == "esriTS"_L1 )
661 {
662 return parseEsriTextMarkerSymbolJson( symbolData );
663 }
664 return nullptr;
665}
666
667std::unique_ptr<QgsLineSymbol> QgsArcGisRestUtils::parseEsriLineSymbolJson( const QVariantMap &symbolData )
668{
669 QColor lineColor = convertColor( symbolData.value( u"color"_s ) );
670 if ( !lineColor.isValid() )
671 return nullptr;
672
673 bool ok = false;
674 double widthInPoints = symbolData.value( u"width"_s ).toDouble( &ok );
675 if ( !ok )
676 return nullptr;
677
678 QgsSymbolLayerList layers;
679 Qt::PenStyle penStyle = convertLineStyle( symbolData.value( u"style"_s ).toString() );
680 auto lineLayer = std::make_unique< QgsSimpleLineSymbolLayer >( lineColor, widthInPoints, penStyle );
681 lineLayer->setWidthUnit( Qgis::RenderUnit::Points );
682 layers.append( lineLayer.release() );
683
684 auto symbol = std::make_unique< QgsLineSymbol >( layers );
685 return symbol;
686}
687
688std::unique_ptr<QgsFillSymbol> QgsArcGisRestUtils::parseEsriFillSymbolJson( const QVariantMap &symbolData )
689{
690 QColor fillColor = convertColor( symbolData.value( u"color"_s ) );
691 Qt::BrushStyle brushStyle = convertFillStyle( symbolData.value( u"style"_s ).toString() );
692
693 const QVariantMap outlineData = symbolData.value( u"outline"_s ).toMap();
694 QColor lineColor = convertColor( outlineData.value( u"color"_s ) );
695 Qt::PenStyle penStyle = convertLineStyle( outlineData.value( u"style"_s ).toString() );
696 bool ok = false;
697 double penWidthInPoints = outlineData.value( u"width"_s ).toDouble( &ok );
698
699 QgsSymbolLayerList layers;
700 auto fillLayer = std::make_unique< QgsSimpleFillSymbolLayer >( fillColor, brushStyle, lineColor, penStyle, penWidthInPoints );
701 fillLayer->setStrokeWidthUnit( Qgis::RenderUnit::Points );
702 layers.append( fillLayer.release() );
703
704 auto symbol = std::make_unique< QgsFillSymbol >( layers );
705 return symbol;
706}
707
708std::unique_ptr<QgsFillSymbol> QgsArcGisRestUtils::parseEsriPictureFillSymbolJson( const QVariantMap &symbolData )
709{
710 bool ok = false;
711
712 double widthInPixels = symbolData.value( u"width"_s ).toInt( &ok );
713 if ( !ok )
714 return nullptr;
715
716 const double xScale = symbolData.value( u"xscale"_s ).toDouble( &ok );
717 if ( !qgsDoubleNear( xScale, 0.0 ) )
718 widthInPixels *= xScale;
719
720 const double angleCCW = symbolData.value( u"angle"_s ).toDouble( &ok );
721 double angleCW = 0;
722 if ( ok )
723 angleCW = -angleCCW;
724
725 const double xOffset = symbolData.value( u"xoffset"_s ).toDouble();
726 const double yOffset = symbolData.value( u"yoffset"_s ).toDouble();
727
728 QString symbolPath( symbolData.value( u"imageData"_s ).toString() );
729 symbolPath.prepend( "base64:"_L1 );
730
731 QgsSymbolLayerList layers;
732 auto fillLayer = std::make_unique< QgsRasterFillSymbolLayer >( symbolPath );
733 fillLayer->setWidth( widthInPixels );
734 fillLayer->setAngle( angleCW );
735 fillLayer->setSizeUnit( Qgis::RenderUnit::Points );
736 fillLayer->setOffset( QPointF( xOffset, yOffset ) );
737 fillLayer->setOffsetUnit( Qgis::RenderUnit::Points );
738 layers.append( fillLayer.release() );
739
740 const QVariantMap outlineData = symbolData.value( u"outline"_s ).toMap();
741 QColor lineColor = convertColor( outlineData.value( u"color"_s ) );
742 Qt::PenStyle penStyle = convertLineStyle( outlineData.value( u"style"_s ).toString() );
743 double penWidthInPoints = outlineData.value( u"width"_s ).toDouble( &ok );
744
745 auto lineLayer = std::make_unique< QgsSimpleLineSymbolLayer >( lineColor, penWidthInPoints, penStyle );
746 lineLayer->setWidthUnit( Qgis::RenderUnit::Points );
747 layers.append( lineLayer.release() );
748
749 auto symbol = std::make_unique< QgsFillSymbol >( layers );
750 return symbol;
751}
752
753Qgis::MarkerShape QgsArcGisRestUtils::parseEsriMarkerShape( const QString &style )
754{
755 if ( style == "esriSMSCircle"_L1 )
757 else if ( style == "esriSMSCross"_L1 )
759 else if ( style == "esriSMSDiamond"_L1 )
761 else if ( style == "esriSMSSquare"_L1 )
763 else if ( style == "esriSMSX"_L1 )
765 else if ( style == "esriSMSTriangle"_L1 )
767 else
769}
770
771std::unique_ptr<QgsMarkerSymbol> QgsArcGisRestUtils::parseEsriMarkerSymbolJson( const QVariantMap &symbolData )
772{
773 QColor fillColor = convertColor( symbolData.value( u"color"_s ) );
774 bool ok = false;
775 const double sizeInPoints = symbolData.value( u"size"_s ).toDouble( &ok );
776 if ( !ok )
777 return nullptr;
778 const double angleCCW = symbolData.value( u"angle"_s ).toDouble( &ok );
779 double angleCW = 0;
780 if ( ok )
781 angleCW = -angleCCW;
782
783 Qgis::MarkerShape shape = parseEsriMarkerShape( symbolData.value( u"style"_s ).toString() );
784
785 const double xOffset = symbolData.value( u"xoffset"_s ).toDouble();
786 const double yOffset = symbolData.value( u"yoffset"_s ).toDouble();
787
788 const QVariantMap outlineData = symbolData.value( u"outline"_s ).toMap();
789 QColor lineColor = convertColor( outlineData.value( u"color"_s ) );
790 Qt::PenStyle penStyle = convertLineStyle( outlineData.value( u"style"_s ).toString() );
791 double penWidthInPoints = outlineData.value( u"width"_s ).toDouble( &ok );
792
793 QgsSymbolLayerList layers;
794 auto markerLayer = std::make_unique< QgsSimpleMarkerSymbolLayer >( shape, sizeInPoints, angleCW, Qgis::ScaleMethod::ScaleArea, fillColor, lineColor );
795 markerLayer->setSizeUnit( Qgis::RenderUnit::Points );
796 markerLayer->setStrokeWidthUnit( Qgis::RenderUnit::Points );
797 markerLayer->setStrokeStyle( penStyle );
798 markerLayer->setStrokeWidth( penWidthInPoints );
799 markerLayer->setOffset( QPointF( xOffset, yOffset ) );
800 markerLayer->setOffsetUnit( Qgis::RenderUnit::Points );
801 layers.append( markerLayer.release() );
802
803 auto symbol = std::make_unique< QgsMarkerSymbol >( layers );
804 return symbol;
805}
806
807std::unique_ptr<QgsMarkerSymbol> QgsArcGisRestUtils::parseEsriPictureMarkerSymbolJson( const QVariantMap &symbolData )
808{
809 bool ok = false;
810 const double widthInPixels = symbolData.value( u"width"_s ).toInt( &ok );
811 if ( !ok )
812 return nullptr;
813 const double heightInPixels = symbolData.value( u"height"_s ).toInt( &ok );
814 if ( !ok )
815 return nullptr;
816
817 const double angleCCW = symbolData.value( u"angle"_s ).toDouble( &ok );
818 double angleCW = 0;
819 if ( ok )
820 angleCW = -angleCCW;
821
822 const double xOffset = symbolData.value( u"xoffset"_s ).toDouble();
823 const double yOffset = symbolData.value( u"yoffset"_s ).toDouble();
824
825 //const QString contentType = symbolData.value( u"contentType"_s ).toString();
826
827 QString symbolPath( symbolData.value( u"imageData"_s ).toString() );
828 symbolPath.prepend( "base64:"_L1 );
829
830 QgsSymbolLayerList layers;
831 auto markerLayer = std::make_unique< QgsRasterMarkerSymbolLayer >( symbolPath, widthInPixels, angleCW, Qgis::ScaleMethod::ScaleArea );
832 markerLayer->setSizeUnit( Qgis::RenderUnit::Points );
833
834 // only change the default aspect ratio if the server height setting requires this
835 if ( !qgsDoubleNear( static_cast< double >( heightInPixels ) / widthInPixels, markerLayer->defaultAspectRatio() ) )
836 markerLayer->setFixedAspectRatio( static_cast< double >( heightInPixels ) / widthInPixels );
837
838 markerLayer->setOffset( QPointF( xOffset, yOffset ) );
839 markerLayer->setOffsetUnit( Qgis::RenderUnit::Points );
840 layers.append( markerLayer.release() );
841
842 auto symbol = std::make_unique< QgsMarkerSymbol >( layers );
843 return symbol;
844}
845
846std::unique_ptr<QgsMarkerSymbol> QgsArcGisRestUtils::parseEsriTextMarkerSymbolJson( const QVariantMap &symbolData )
847{
848 QgsSymbolLayerList layers;
849
850 const QString fontFamily = symbolData.value( u"font"_s ).toMap().value( u"family"_s ).toString();
851
852 const QString chr = symbolData.value( u"text"_s ).toString();
853
854 const double pointSize = symbolData.value( u"font"_s ).toMap().value( u"size"_s ).toDouble();
855
856 const QColor color = convertColor( symbolData.value( u"color"_s ) );
857
858 const double esriAngle = symbolData.value( u"angle"_s ).toDouble();
859
860 const double angle = 90.0 - esriAngle;
861
862 auto markerLayer = std::make_unique< QgsFontMarkerSymbolLayer >( fontFamily, chr, pointSize, color, angle );
863
864 QColor strokeColor = convertColor( symbolData.value( u"borderLineColor"_s ) );
865 markerLayer->setStrokeColor( strokeColor );
866
867 double borderLineSize = symbolData.value( u"borderLineSize"_s ).toDouble();
868 markerLayer->setStrokeWidth( borderLineSize );
869
870 const QString fontStyle = symbolData.value( u"font"_s ).toMap().value( u"style"_s ).toString();
871 markerLayer->setFontStyle( fontStyle );
872
873 double xOffset = symbolData.value( u"xoffset"_s ).toDouble();
874 double yOffset = symbolData.value( u"yoffset"_s ).toDouble();
875
876 markerLayer->setOffset( QPointF( xOffset, yOffset ) );
877 markerLayer->setOffsetUnit( Qgis::RenderUnit::Points );
878
879 markerLayer->setSizeUnit( Qgis::RenderUnit::Points );
880 markerLayer->setStrokeWidthUnit( Qgis::RenderUnit::Points );
881
884
885 QString horizontalAnchorPoint = symbolData.value( u"horizontalAlignment"_s ).toString();
886 QString verticalAnchorPoint = symbolData.value( u"verticalAlignment"_s ).toString();
887
888 if ( horizontalAnchorPoint == QString( "center" ) )
889 {
891 }
892 else if ( horizontalAnchorPoint == QString( "left" ) )
893 {
895 }
896 else if ( horizontalAnchorPoint == QString( "right" ) )
897 {
899 }
900
901 if ( verticalAnchorPoint == QString( "center" ) )
902 {
904 }
905 else if ( verticalAnchorPoint == QString( "top" ) )
906 {
908 }
909 else if ( verticalAnchorPoint == QString( "bottom" ) )
910 {
912 }
913
914 markerLayer->setHorizontalAnchorPoint( hAlign );
915 markerLayer->setVerticalAnchorPoint( vAlign );
916
917 layers.append( markerLayer.release() );
918
919 auto symbol = std::make_unique< QgsMarkerSymbol >( layers );
920 return symbol;
921}
922
923std::unique_ptr<QgsAbstractVectorLayerLabeling > QgsArcGisRestUtils::convertLabeling( const QVariantList &labelingData )
924{
925 if ( labelingData.empty() )
926 return nullptr;
927
928 QgsRuleBasedLabeling::Rule *root = new QgsRuleBasedLabeling::Rule( new QgsPalLayerSettings(), 0, 0, QString(), QString(), false );
929 root->setActive( true );
930
931 int i = 1;
932 for ( const QVariant &lbl : labelingData )
933 {
934 const QVariantMap labeling = lbl.toMap();
935
937 QgsTextFormat format;
938
939 const QString placement = labeling.value( u"labelPlacement"_s ).toString();
940 if ( placement == "esriServerPointLabelPlacementAboveCenter"_L1 )
941 {
944 }
945 else if ( placement == "esriServerPointLabelPlacementBelowCenter"_L1 )
946 {
949 }
950 else if ( placement == "esriServerPointLabelPlacementCenterCenter"_L1 )
951 {
954 }
955 else if ( placement == "esriServerPointLabelPlacementAboveLeft"_L1 )
956 {
959 }
960 else if ( placement == "esriServerPointLabelPlacementBelowLeft"_L1 )
961 {
964 }
965 else if ( placement == "esriServerPointLabelPlacementCenterLeft"_L1 )
966 {
969 }
970 else if ( placement == "esriServerPointLabelPlacementAboveRight"_L1 )
971 {
974 }
975 else if ( placement == "esriServerPointLabelPlacementBelowRight"_L1 )
976 {
979 }
980 else if ( placement == "esriServerPointLabelPlacementCenterRight"_L1 )
981 {
984 }
985 else if ( placement == "esriServerLinePlacementAboveAfter"_L1 || placement == "esriServerLinePlacementAboveStart"_L1 || placement == "esriServerLinePlacementAboveAlong"_L1 )
986 {
989 }
990 else if ( placement == "esriServerLinePlacementBelowAfter"_L1 || placement == "esriServerLinePlacementBelowStart"_L1 || placement == "esriServerLinePlacementBelowAlong"_L1 )
991 {
994 }
995 else if ( placement == "esriServerLinePlacementCenterAfter"_L1 || placement == "esriServerLinePlacementCenterStart"_L1 || 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 >( minColor, maxColor, false, gradientStops );
1178
1179 QgsProperty colorProperty = QgsProperty::fromField( attrName );
1180 colorProperty.setTransformer( new QgsColorRampTransformer( minValue, maxValue, colorRamp.release() ) );
1181 for ( int layer = 0; layer < symbol->symbolLayerCount(); ++layer )
1182 {
1183 symbol->symbolLayer( layer )->setDataDefinedProperty( QgsSymbolLayer::Property::FillColor, colorProperty );
1184 }
1185
1186 return std::make_unique< QgsSingleSymbolRenderer >( symbol.release() );
1187 }
1188 else
1189 {
1190 QgsDebugError( u"ESRI visualVariable type %1 is not currently supported"_s.arg( variableType ) );
1191 }
1192 }
1193
1194 double lastValue = rendererData.value( u"minValue"_s ).toDouble();
1195
1196 auto graduatedRenderer = std::make_unique< QgsGraduatedSymbolRenderer >( attrName );
1197
1198 graduatedRenderer->setSourceSymbol( symbol.release() );
1199
1200 if ( esriMode == "esriClassifyDefinedInterval"_L1 )
1201 {
1203 graduatedRenderer->setClassificationMethod( method );
1204 }
1205 else if ( esriMode == "esriClassifyEqualInterval"_L1 )
1206 {
1208 graduatedRenderer->setClassificationMethod( method );
1209 }
1210 else if ( esriMode == "esriClassifyGeometricalInterval"_L1 )
1211 {
1213 graduatedRenderer->setClassificationMethod( method );
1214 }
1215 else if ( esriMode == "esriClassifyManual"_L1 )
1216 {
1218 graduatedRenderer->setClassificationMethod( method );
1219 }
1220 else if ( esriMode == "esriClassifyNaturalBreaks"_L1 )
1221 {
1223 graduatedRenderer->setClassificationMethod( method );
1224 }
1225 else if ( esriMode == "esriClassifyQuantile"_L1 )
1226 {
1228 graduatedRenderer->setClassificationMethod( method );
1229 }
1230 else if ( esriMode == "esriClassifyStandardDeviation"_L1 )
1231 {
1233 graduatedRenderer->setClassificationMethod( method );
1234 }
1235 else if ( !esriMode.isEmpty() )
1236 {
1237 QgsDebugError( u"ESRI classification mode %1 is not currently supported"_s.arg( esriMode ) );
1238 }
1239
1240 for ( const QVariant &classBreakInfo : classBreakInfos )
1241 {
1242 const QVariantMap symbolData = classBreakInfo.toMap().value( u"symbol"_s ).toMap();
1243 std::unique_ptr< QgsSymbol > symbol( QgsArcGisRestUtils::convertSymbol( symbolData ) );
1244 double classMaxValue = classBreakInfo.toMap().value( u"classMaxValue"_s ).toDouble();
1245 const QString label = classBreakInfo.toMap().value( u"label"_s ).toString();
1246
1247 QgsRendererRange range;
1248
1249 range.setLowerValue( lastValue );
1250 range.setUpperValue( classMaxValue );
1251 range.setLabel( label );
1252 range.setSymbol( symbol.release() );
1253
1254 lastValue = classMaxValue;
1255 graduatedRenderer->addClass( range );
1256 }
1257
1258 return graduatedRenderer;
1259 }
1260 else if ( type == "heatmap"_L1 )
1261 {
1262 // currently unsupported
1263 return nullptr;
1264 }
1265 else if ( type == "vectorField"_L1 )
1266 {
1267 // currently unsupported
1268 return nullptr;
1269 }
1270 return nullptr;
1271}
1272
1274{
1275 QString expression = string;
1276
1277 // Replace a few ArcGIS token to QGIS equivalents
1278 const thread_local QRegularExpression rx1 = QRegularExpression( u"(?=([^\"\\\\]*(\\\\.|\"([^\"\\\\]*\\\\.)*[^\"\\\\]*\"))*[^\"]*$)(\\s|^)CONCAT(\\s|$)"_s );
1279 expression = expression.replace( rx1, u"\\4||\\5"_s );
1280
1281 const thread_local QRegularExpression rx2 = QRegularExpression( u"(?=([^\"\\\\]*(\\\\.|\"([^\"\\\\]*\\\\.)*[^\"\\\\]*\"))*[^\"]*$)(\\s|^)NEWLINE(\\s|$)"_s );
1282 expression = expression.replace( rx2, u"\\4'\\n'\\5"_s );
1283
1284 // ArcGIS's double quotes are single quotes in QGIS
1285 const thread_local QRegularExpression rx3 = QRegularExpression( u"\"(.*?(?<!\\\\))\""_s );
1286 expression = expression.replace( rx3, u"'\\1'"_s );
1287 const thread_local QRegularExpression rx4 = QRegularExpression( u"\\\\\""_s );
1288 expression = expression.replace( rx4, u"\""_s );
1289
1290 // ArcGIS's square brakets are double quotes in QGIS
1291 const thread_local QRegularExpression rx5 = QRegularExpression( u"\\[([^]]*)\\]"_s );
1292 expression = expression.replace( rx5, u"\"\\1\""_s );
1293
1294 return expression;
1295}
1296
1297QColor QgsArcGisRestUtils::convertColor( const QVariant &colorData )
1298{
1299 const QVariantList colorParts = colorData.toList();
1300 if ( colorParts.count() < 4 )
1301 return QColor();
1302
1303 int red = colorParts.at( 0 ).toInt();
1304 int green = colorParts.at( 1 ).toInt();
1305 int blue = colorParts.at( 2 ).toInt();
1306 int alpha = colorParts.at( 3 ).toInt();
1307 return QColor( red, green, blue, alpha );
1308}
1309
1310Qt::PenStyle QgsArcGisRestUtils::convertLineStyle( const QString &style )
1311{
1312 if ( style == "esriSLSSolid"_L1 )
1313 return Qt::SolidLine;
1314 else if ( style == "esriSLSDash"_L1 )
1315 return Qt::DashLine;
1316 else if ( style == "esriSLSDashDot"_L1 )
1317 return Qt::DashDotLine;
1318 else if ( style == "esriSLSDashDotDot"_L1 )
1319 return Qt::DashDotDotLine;
1320 else if ( style == "esriSLSDot"_L1 )
1321 return Qt::DotLine;
1322 else if ( style == "esriSLSNull"_L1 )
1323 return Qt::NoPen;
1324 else
1325 return Qt::SolidLine;
1326}
1327
1328Qt::BrushStyle QgsArcGisRestUtils::convertFillStyle( const QString &style )
1329{
1330 if ( style == "esriSFSBackwardDiagonal"_L1 )
1331 return Qt::BDiagPattern;
1332 else if ( style == "esriSFSCross"_L1 )
1333 return Qt::CrossPattern;
1334 else if ( style == "esriSFSDiagonalCross"_L1 )
1335 return Qt::DiagCrossPattern;
1336 else if ( style == "esriSFSForwardDiagonal"_L1 )
1337 return Qt::FDiagPattern;
1338 else if ( style == "esriSFSHorizontal"_L1 )
1339 return Qt::HorPattern;
1340 else if ( style == "esriSFSNull"_L1 )
1341 return Qt::NoBrush;
1342 else if ( style == "esriSFSSolid"_L1 )
1343 return Qt::SolidPattern;
1344 else if ( style == "esriSFSVertical"_L1 )
1345 return Qt::VerPattern;
1346 else
1347 return Qt::SolidPattern;
1348}
1349
1350QDateTime QgsArcGisRestUtils::convertDateTime( const QVariant &value )
1351{
1352 if ( QgsVariantUtils::isNull( value ) )
1353 return QDateTime();
1354 bool ok = false;
1355 QDateTime dt = QDateTime::fromMSecsSinceEpoch( value.toLongLong( &ok ) );
1356 if ( !ok )
1357 {
1358 QgsDebugError( u"Invalid value %1 for datetime"_s.arg( value.toString() ) );
1359 return QDateTime();
1360 }
1361 else
1362 return dt;
1363}
1364
1366{
1367 if ( QgsVariantUtils::isNull( value ) )
1368 return QgsRectangle();
1369
1370 const QVariantMap coords = value.toMap();
1371 if ( coords.isEmpty() )
1372 return QgsRectangle();
1373
1374 bool ok;
1375
1376 const double xmin = coords.value( u"xmin"_s ).toDouble( &ok );
1377 if ( !ok )
1378 return QgsRectangle();
1379
1380 const double ymin = coords.value( u"ymin"_s ).toDouble( &ok );
1381 if ( !ok )
1382 return QgsRectangle();
1383
1384 const double xmax = coords.value( u"xmax"_s ).toDouble( &ok );
1385 if ( !ok )
1386 return QgsRectangle();
1387
1388 const double ymax = coords.value( u"ymax"_s ).toDouble( &ok );
1389 if ( !ok )
1390 return QgsRectangle();
1391
1392 return QgsRectangle( xmin, ymin, xmax, ymax );
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 if ( crs.isValid() )
1461 {
1462 // add spatialReference information
1463 res.insert( u"spatialReference"_s, crsToJson( crs ) );
1464 }
1465
1466 return res;
1467}
1468
1469QVariantMap QgsArcGisRestUtils::pointToJson( const QgsPoint *point )
1470{
1471 QVariantMap data;
1472 if ( point->isEmpty() )
1473 data[u"x"_s] = u"NaN"_s;
1474 else
1475 {
1476 data[u"x"_s] = point->x();
1477 data[u"y"_s] = point->y();
1478
1479 if ( point->is3D() )
1480 data[u"z"_s] = !std::isnan( point->z() ) ? QVariant( point->z() ) : QVariant( u"NaN"_s );
1481
1482 if ( point->isMeasure() )
1483 data[u"m"_s] = !std::isnan( point->m() ) ? QVariant( point->m() ) : QVariant( u"NaN"_s );
1484 }
1485 return data;
1486}
1487
1488QVariantMap QgsArcGisRestUtils::multiPointToJson( const QgsMultiPoint *multiPoint )
1489{
1490 QVariantMap data;
1491 const bool hasZ = multiPoint->is3D();
1492 const bool hasM = multiPoint->isMeasure();
1493 data[u"hasM"_s] = hasM;
1494 data[u"hasZ"_s] = hasZ;
1495
1496 QVariantList pointsList;
1497 const int size = multiPoint->numGeometries();
1498 pointsList.reserve( size );
1499
1500 QVariantList pointList;
1501 for ( int i = 0; i < size; ++i )
1502 {
1503 const QgsPoint *point = multiPoint->pointN( i );
1504
1505 pointList.clear();
1506 pointList.append( point->x() );
1507 pointList.append( point->y() );
1508 if ( hasZ )
1509 pointList.append( point->z() );
1510 if ( hasM && !std::isnan( point->m() ) )
1511 pointList.append( point->m() );
1512
1513 pointsList.push_back( pointList );
1514 }
1515
1516 data[u"points"_s] = pointsList;
1517 return data;
1518}
1519
1520QVariantList QgsArcGisRestUtils::lineStringToJsonPath( const QgsLineString *line )
1521{
1522 const bool hasZ = line->is3D();
1523 const bool hasM = line->isMeasure();
1524
1525 QVariantList pointsList;
1526 const int size = line->numPoints();
1527 pointsList.reserve( size );
1528
1529 QVariantList pointList;
1530 const double *xData = line->xData();
1531 const double *yData = line->yData();
1532 const double *zData = hasZ ? line->zData() : nullptr;
1533 const double *mData = hasM ? line->mData() : nullptr;
1534
1535 for ( int i = 0; i < size; ++i )
1536 {
1537 pointList.clear();
1538 pointList.append( *xData++ );
1539 pointList.append( *yData++ );
1540
1541 if ( hasZ )
1542 pointList.append( *zData++ );
1543
1544 if ( hasM && !std::isnan( *mData ) )
1545 pointList.append( *mData );
1546 if ( hasM )
1547 mData++;
1548
1549 pointsList.push_back( pointList );
1550 }
1551 return pointsList;
1552}
1553
1554QVariantList QgsArcGisRestUtils::curveToJsonCurve( const QgsCurve *curve, bool includeStart )
1555{
1556 const bool hasZ = curve->is3D();
1557 const bool hasM = curve->isMeasure();
1558
1559 auto pointToList = [hasZ, hasM]( const QgsPoint &point ) -> QVariantList {
1560 QVariantList pointList;
1561
1562 pointList.append( point.x() );
1563 pointList.append( point.y() );
1564
1565 if ( hasZ )
1566 pointList.append( point.z() );
1567
1568 if ( hasM && !std::isnan( point.m() ) )
1569 pointList.append( point.m() );
1570
1571 return pointList;
1572 };
1573
1574 QVariantList res;
1575 switch ( QgsWkbTypes::flatType( curve->wkbType() ) )
1576 {
1578 {
1579 QVariantList part = lineStringToJsonPath( qgsgeometry_cast< const QgsLineString *>( curve ) );
1580 if ( !part.isEmpty() && !includeStart )
1581 part.removeAt( 0 );
1582 res = part;
1583 break;
1584 }
1585
1587 {
1588 const QgsCircularString *circularString = qgsgeometry_cast<const QgsCircularString * >( curve );
1589 if ( includeStart && !circularString->isEmpty() )
1590 {
1591 res.push_back( pointToList( circularString->startPoint() ) );
1592 }
1593
1594 const int size = circularString->numPoints();
1595 for ( int i = 1; i + 1 < size; i += 2 )
1596 {
1597 // end point comes BEFORE interior point!
1598 QVariantMap curvePart;
1599 QVariantList curveList;
1600 curveList.push_back( pointToList( circularString->pointN( i + 1 ) ) );
1601
1602 curveList.push_back( pointToList( circularString->pointN( i ) ) );
1603
1604 curvePart.insert( u"c"_s, curveList );
1605 res.push_back( curvePart );
1606 }
1607 break;
1608 }
1609
1611 {
1612 const QgsCompoundCurve *compoundCurve = qgsgeometry_cast<const QgsCompoundCurve * >( curve );
1613
1614 const int size = compoundCurve->nCurves();
1615 for ( int i = 0; i < size; ++i )
1616 {
1617 const QgsCurve *subCurve = compoundCurve->curveAt( i );
1618 res.append( curveToJsonCurve( subCurve, i == 0 ) );
1619 }
1620 break;
1621 }
1622
1623 default:
1624 break;
1625 }
1626 return res;
1627}
1628
1629QVariantMap QgsArcGisRestUtils::lineStringToJson( const QgsLineString *line )
1630{
1631 QVariantMap data;
1632 const bool hasZ = line->is3D();
1633 const bool hasM = line->isMeasure();
1634 data[u"hasM"_s] = hasM;
1635 data[u"hasZ"_s] = hasZ;
1636
1637 const QVariantList pointsList = lineStringToJsonPath( line );
1638
1639 QVariantList pointsData = QVariantList();
1640 pointsData.push_back( pointsList );
1641 data[u"paths"_s] = pointsData;
1642
1643 return data;
1644}
1645
1646QVariantMap QgsArcGisRestUtils::curveToJson( const QgsCurve *curve )
1647{
1648 QVariantMap data;
1649 const bool hasZ = curve->is3D();
1650 const bool hasM = curve->isMeasure();
1651 data[u"hasM"_s] = hasM;
1652 data[u"hasZ"_s] = hasZ;
1653
1654 const QVariantList curveList = curveToJsonCurve( curve, true );
1655
1656 QVariantList curveData = QVariantList();
1657 curveData.push_back( curveList );
1658 data[u"curvePaths"_s] = curveData;
1659
1660 return data;
1661}
1662
1663QVariantMap QgsArcGisRestUtils::multiLineStringToJson( const QgsMultiLineString *multiLine )
1664{
1665 QVariantMap data;
1666 const bool hasZ = multiLine->is3D();
1667 const bool hasM = multiLine->isMeasure();
1668 data[u"hasM"_s] = hasM;
1669 data[u"hasZ"_s] = hasZ;
1670
1671 const int size = multiLine->numGeometries();
1672 QVariantList paths;
1673 paths.reserve( size );
1674 for ( int i = 0; i < size; ++i )
1675 {
1676 const QgsLineString *line = multiLine->lineStringN( i );
1677 paths.push_back( lineStringToJsonPath( line ) );
1678 }
1679
1680 data[u"paths"_s] = paths;
1681 return data;
1682}
1683
1684QVariantMap QgsArcGisRestUtils::multiCurveToJson( const QgsMultiCurve *multiCurve )
1685{
1686 QVariantMap data;
1687 const bool hasZ = multiCurve->is3D();
1688 const bool hasM = multiCurve->isMeasure();
1689 data[u"hasM"_s] = hasM;
1690 data[u"hasZ"_s] = hasZ;
1691
1692 const int size = multiCurve->numGeometries();
1693 QVariantList paths;
1694 paths.reserve( size );
1695 for ( int i = 0; i < size; ++i )
1696 {
1697 const QgsCurve *curve = multiCurve->curveN( i );
1698 paths.push_back( curveToJsonCurve( curve, true ) );
1699 }
1700
1701 data[u"curvePaths"_s] = paths;
1702 return data;
1703}
1704
1705QVariantList QgsArcGisRestUtils::polygonToJsonRings( const QgsPolygon *polygon )
1706{
1707 QVariantList rings;
1708 const int numInteriorRings = polygon->numInteriorRings();
1709 rings.reserve( numInteriorRings + 1 );
1710
1711 if ( const QgsLineString *exterior = qgsgeometry_cast< const QgsLineString * >( polygon->exteriorRing() ) )
1712 {
1713 // exterior ring MUST be clockwise
1714 switch ( exterior->orientation() )
1715 {
1717 rings.push_back( lineStringToJsonPath( exterior ) );
1718 break;
1719
1721 {
1722 std::unique_ptr< QgsLineString > reversed( exterior->reversed() );
1723 rings.push_back( lineStringToJsonPath( reversed.get() ) );
1724 break;
1725 }
1727 break;
1728 }
1729 }
1730
1731 for ( int i = 0; i < numInteriorRings; ++i )
1732 {
1733 const QgsLineString *ring = qgsgeometry_cast< const QgsLineString * >( polygon->interiorRing( i ) );
1734 // holes MUST be counter-clockwise
1735 switch ( ring->orientation() )
1736 {
1738 rings.push_back( lineStringToJsonPath( ring ) );
1739 break;
1740
1742 {
1743 std::unique_ptr< QgsLineString > reversed( ring->reversed() );
1744 rings.push_back( lineStringToJsonPath( reversed.get() ) );
1745 break;
1746 }
1748 break;
1749 }
1750 }
1751 return rings;
1752}
1753
1754QVariantList QgsArcGisRestUtils::curvePolygonToJsonRings( const QgsCurvePolygon *polygon )
1755{
1756 QVariantList rings;
1757 const int numInteriorRings = polygon->numInteriorRings();
1758 rings.reserve( numInteriorRings + 1 );
1759
1760 if ( const QgsCurve *exterior = qgsgeometry_cast< const QgsCurve * >( polygon->exteriorRing() ) )
1761 {
1762 // exterior ring MUST be clockwise
1763 switch ( exterior->orientation() )
1764 {
1766 rings.push_back( curveToJsonCurve( exterior, true ) );
1767 break;
1768
1770 {
1771 std::unique_ptr< QgsCurve > reversed( exterior->reversed() );
1772 rings.push_back( curveToJsonCurve( reversed.get(), true ) );
1773 break;
1774 }
1776 break;
1777 }
1778 }
1779
1780 for ( int i = 0; i < numInteriorRings; ++i )
1781 {
1782 const QgsCurve *ring = qgsgeometry_cast< const QgsCurve * >( polygon->interiorRing( i ) );
1783 // holes MUST be counter-clockwise
1784 switch ( ring->orientation() )
1785 {
1787 rings.push_back( curveToJsonCurve( ring, true ) );
1788 break;
1789
1791 {
1792 std::unique_ptr< QgsCurve > reversed( ring->reversed() );
1793 rings.push_back( curveToJsonCurve( reversed.get(), true ) );
1794 break;
1795 }
1797 break;
1798 }
1799 }
1800 return rings;
1801}
1802
1803QVariantMap QgsArcGisRestUtils::polygonToJson( const QgsPolygon *polygon )
1804{
1805 QVariantMap data;
1806 const bool hasZ = polygon->is3D();
1807 const bool hasM = polygon->isMeasure();
1808 data[u"hasM"_s] = hasM;
1809 data[u"hasZ"_s] = hasZ;
1810 data[u"rings"_s] = polygonToJsonRings( polygon );
1811 return data;
1812}
1813
1814QVariantMap QgsArcGisRestUtils::curvePolygonToJson( const QgsCurvePolygon *polygon )
1815{
1816 QVariantMap data;
1817 const bool hasZ = polygon->is3D();
1818 const bool hasM = polygon->isMeasure();
1819 data[u"hasM"_s] = hasM;
1820 data[u"hasZ"_s] = hasZ;
1821 data[u"curveRings"_s] = curvePolygonToJsonRings( polygon );
1822 return data;
1823}
1824
1825QVariantMap QgsArcGisRestUtils::multiPolygonToJson( const QgsMultiPolygon *multiPolygon )
1826{
1827 QVariantMap data;
1828 const bool hasZ = multiPolygon->is3D();
1829 const bool hasM = multiPolygon->isMeasure();
1830 data[u"hasM"_s] = hasM;
1831 data[u"hasZ"_s] = hasZ;
1832
1833 const int size = multiPolygon->numGeometries();
1834 QVariantList rings;
1835 for ( int i = 0; i < size; ++i )
1836 {
1837 const QgsPolygon *polygon = multiPolygon->polygonN( i );
1838 rings.append( polygonToJsonRings( polygon ) );
1839 }
1840
1841 data[u"rings"_s] = rings;
1842 return data;
1843}
1844
1845QVariantMap QgsArcGisRestUtils::multiSurfaceToJson( const QgsMultiSurface *multiSurface )
1846{
1847 QVariantMap data;
1848 const bool hasZ = multiSurface->is3D();
1849 const bool hasM = multiSurface->isMeasure();
1850 data[u"hasM"_s] = hasM;
1851 data[u"hasZ"_s] = hasZ;
1852
1853 const int size = multiSurface->numGeometries();
1854 QVariantList rings;
1855 for ( int i = 0; i < size; ++i )
1856 {
1857 const QgsCurvePolygon *polygon = qgsgeometry_cast< const QgsCurvePolygon * >( multiSurface->geometryN( i ) );
1858 if ( !polygon )
1859 continue;
1860
1861 rings.append( curvePolygonToJsonRings( polygon ) );
1862 }
1863
1864 data[u"curveRings"_s] = rings;
1865 return data;
1866}
1867
1869{
1870 QVariantMap res;
1871 if ( !crs.isValid() )
1872 return res;
1873
1874 const QString authid = crs.authid();
1875 if ( !authid.isEmpty() )
1876 {
1877 const thread_local QRegularExpression rxAuthid( u"(\\w+):(\\d+)"_s );
1878 const QRegularExpressionMatch match = rxAuthid.match( authid );
1879 if ( match.hasMatch() && ( ( match.captured( 1 ).compare( "EPSG"_L1, Qt::CaseInsensitive ) == 0 ) || ( match.captured( 1 ).compare( "ESRI"_L1, Qt::CaseInsensitive ) == 0 ) ) )
1880 {
1881 const QString wkid = match.captured( 2 );
1882 res.insert( u"wkid"_s, wkid );
1883 return res;
1884 }
1885 }
1886
1887 // docs don't mention the WKT version support, so let's hope for 2.0...
1888 res.insert( u"wkt"_s, crs.toWkt( Qgis::CrsWktVariant::Wkt2_2019Simplified ) );
1889
1890 return res;
1891}
1892
1894{
1895 QVariantMap res;
1896 if ( ( flags & FeatureToJsonFlag::IncludeGeometry ) && feature.hasGeometry() )
1897 {
1898 res.insert( u"geometry"_s, geometryToJson( feature.geometry(), context, crs ) );
1899 }
1900
1901 QVariantMap attributes;
1902 const QgsFields fields = feature.fields();
1903 for ( const QgsField &field : fields )
1904 {
1905 QVariant value = feature.attribute( field.name() );
1906 if ( value.userType() == qMetaTypeId< QgsUnsetAttributeValue >() )
1907 {
1908 if ( flags.testFlag( FeatureToJsonFlag::SkipUnsetAttributes ) )
1909 continue;
1910 else
1911 value = QVariant(); // reset to null, we can't store 'QgsUnsetAttributeValue' as json
1912 }
1913
1914 if ( ( flags & FeatureToJsonFlag::IncludeNonObjectIdAttributes ) || field.name() == context.objectIdFieldName() )
1915 attributes.insert( field.name(), variantToAttributeValue( value, field.type(), context ) );
1916 }
1917 if ( !attributes.isEmpty() )
1918 {
1919 res.insert( u"attributes"_s, attributes );
1920 }
1921 return res;
1922}
1923
1924QVariant QgsArcGisRestUtils::variantToAttributeValue( const QVariant &variant, QMetaType::Type expectedType, const QgsArcGisRestContext &context )
1925{
1926 if ( QgsVariantUtils::isNull( variant ) )
1927 return QVariant();
1928
1929 switch ( expectedType )
1930 {
1931 case QMetaType::Type::QString:
1932 {
1933 const QString escaped = variant.toString().replace( '\\', "\\\\"_L1 ).replace( '"', "\\\""_L1 );
1934 return QString( QUrl::toPercentEncoding( escaped, "'" ) );
1935 }
1936
1937 case QMetaType::Type::QDateTime:
1938 case QMetaType::Type::QDate:
1939 {
1940 switch ( variant.userType() )
1941 {
1942 case QMetaType::Type::QDateTime:
1943 return variant.toDateTime().toMSecsSinceEpoch();
1944
1945 case QMetaType::Type::QDate:
1946 // for date values, assume start of day -- the REST api requires datetime values only, not plain dates
1947 if ( context.timeZone().isValid() )
1948 return QDateTime( variant.toDate(), QTime( 0, 0, 0 ), context.timeZone() ).toMSecsSinceEpoch();
1949 else
1950 return QDateTime( variant.toDate(), QTime( 0, 0, 0 ) ).toMSecsSinceEpoch();
1951
1952 default:
1953 return QVariant();
1954 }
1955 }
1956
1957 default:
1958 return variant;
1959 }
1960}
1961
1963{
1964 QVariantMap res;
1965 res.insert( u"name"_s, field.name() );
1966
1967 QString fieldType;
1968 switch ( field.type() )
1969 {
1970 case QMetaType::Type::LongLong:
1971 fieldType = u"esriFieldTypeInteger"_s;
1972 break;
1973
1974 case QMetaType::Type::Int:
1975 fieldType = u"esriFieldTypeSmallInteger"_s;
1976 break;
1977
1978 case QMetaType::Type::Double:
1979 fieldType = u"esriFieldTypeDouble"_s;
1980 break;
1981
1982 case QMetaType::Type::QString:
1983 fieldType = u"esriFieldTypeString"_s;
1984 break;
1985
1986 case QMetaType::Type::QDateTime:
1987 case QMetaType::Type::QDate:
1988 fieldType = u"esriFieldTypeDate"_s;
1989 break;
1990
1991 case QMetaType::Type::QByteArray:
1992 fieldType = u"esriFieldTypeBlob"_s;
1993 break;
1994
1995 default:
1996 // fallback to string
1997 fieldType = u"esriFieldTypeString"_s;
1998 break;
1999 }
2000 res.insert( u"type"_s, fieldType );
2001
2002 if ( !field.alias().isEmpty() )
2003 res.insert( u"alias"_s, field.alias() );
2004
2005 // nullable
2007 res.insert( u"nullable"_s, !notNullable );
2008
2009 // editable
2010 res.insert( u"editable"_s, true );
2011
2012 return res;
2013}
2014
2016{
2017 if ( type.compare( "FeatureServer"_L1, Qt::CaseInsensitive ) == 0 )
2019 else if ( type.compare( "MapServer"_L1, Qt::CaseInsensitive ) == 0 )
2021 else if ( type.compare( "ImageServer"_L1, Qt::CaseInsensitive ) == 0 )
2023 else if ( type.compare( "GlobeServer"_L1, Qt::CaseInsensitive ) == 0 )
2025 else if ( type.compare( "GPServer"_L1, Qt::CaseInsensitive ) == 0 )
2027 else if ( type.compare( "GeocodeServer"_L1, Qt::CaseInsensitive ) == 0 )
2029 else if ( type.compare( "SceneServer"_L1, Qt::CaseInsensitive ) == 0 )
2031
2033}
@ BelowLine
Labels can be placed below a line feature. Unless MapOrientation is also specified this mode respects...
Definition qgis.h:1343
@ MapOrientation
Signifies that the AboveLine and BelowLine flags should respect the map's orientation rather than the...
Definition qgis.h:1344
@ OnLine
Labels can be placed directly over a line feature.
Definition qgis.h:1341
@ AboveLine
Labels can be placed above a line feature. Unless MapOrientation is also specified this mode respects...
Definition qgis.h:1342
@ NoOrientation
Unknown orientation or sentinel value.
Definition qgis.h:3549
@ CounterClockwise
Counter-clockwise direction.
Definition qgis.h:3548
@ Clockwise
Clockwise direction.
Definition qgis.h:3547
@ OverPoint
Arranges candidates over a point (or centroid of a polygon), or at a preset offset from the point....
Definition qgis.h:1234
@ Line
Arranges candidates parallel to a generalised line representing the feature or parallel to a polygon'...
Definition qgis.h:1235
@ Horizontal
Arranges horizontal candidates scattered throughout a polygon feature. Applies to polygon layers only...
Definition qgis.h:1237
@ ScaleArea
Calculate scale by the area.
Definition qgis.h:651
ArcGisRestServiceType
Available ArcGIS REST service types.
Definition qgis.h:4515
@ GeocodeServer
GeocodeServer.
Definition qgis.h:4521
@ SceneServer
SceneServer.
Definition qgis.h:4523
@ Unknown
Other unknown/unsupported type.
Definition qgis.h:4522
@ GlobeServer
GlobeServer.
Definition qgis.h:4519
@ ImageServer
ImageServer.
Definition qgis.h:4518
@ FeatureServer
FeatureServer.
Definition qgis.h:4516
MarkerShape
Marker shapes.
Definition qgis.h:3194
@ Circle
Circle.
Definition qgis.h:3203
@ Triangle
Triangle.
Definition qgis.h:3199
@ Cross2
Rotated cross (lines only), 'x' shape.
Definition qgis.h:3206
@ Diamond
Diamond.
Definition qgis.h:3196
@ Square
Square.
Definition qgis.h:3195
@ Cross
Cross (lines only).
Definition qgis.h:3204
@ AboveRight
Above right.
Definition qgis.h:1323
@ BelowLeft
Below left.
Definition qgis.h:1327
@ Above
Above center.
Definition qgis.h:1322
@ BelowRight
Below right.
Definition qgis.h:1329
@ Right
Right middle.
Definition qgis.h:1326
@ AboveLeft
Above left.
Definition qgis.h:1321
@ Below
Below center.
Definition qgis.h:1328
@ Over
Center middle.
Definition qgis.h:1325
VerticalAnchorPoint
Marker symbol vertical anchor points.
Definition qgis.h:840
@ Bottom
Align to bottom of symbol.
Definition qgis.h:843
@ Center
Align to vertical center of symbol.
Definition qgis.h:842
@ Top
Align to top of symbol.
Definition qgis.h:841
@ Points
Points (e.g., for font sizes).
Definition qgis.h:5345
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:294
@ CompoundCurve
CompoundCurve.
Definition qgis.h:305
@ Point
Point.
Definition qgis.h:296
@ LineString
LineString.
Definition qgis.h:297
@ MultiPoint
MultiPoint.
Definition qgis.h:300
@ Polygon
Polygon.
Definition qgis.h:298
@ MultiPolygon
MultiPolygon.
Definition qgis.h:302
@ Triangle
Triangle.
Definition qgis.h:299
@ NoGeometry
No geometry.
Definition qgis.h:312
@ MultiLineString
MultiLineString.
Definition qgis.h:301
@ Unknown
Unknown.
Definition qgis.h:295
@ CircularString
CircularString.
Definition qgis.h:304
@ GeometryCollection
GeometryCollection.
Definition qgis.h:303
@ MultiCurve
MultiCurve.
Definition qgis.h:307
@ CurvePolygon
CurvePolygon.
Definition qgis.h:306
@ MultiSurface
MultiSurface.
Definition qgis.h:308
@ Wkt2_2019Simplified
WKT2_2019 with the simplification rule of WKT2_SIMPLIFIED.
Definition qgis.h:2526
HorizontalAnchorPoint
Marker symbol horizontal anchor points.
Definition qgis.h:826
@ Center
Align to horizontal center of symbol.
Definition qgis.h:828
@ Right
Align to right side of symbol.
Definition qgis.h:829
@ Left
Align to left side of symbol.
Definition qgis.h:827
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:286
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:391
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:766
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:6975
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