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