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