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 "qgsnetworkaccessmanager.h"
20 #include "qgsrectangle.h"
21 #include "qgsfeedback.h"
22 #include "qgspallabeling.h"
23 #include "qgssymbol.h"
24 #include "qgssymbollayer.h"
25 #include "qgsauthmanager.h"
26 #include "qgssettings.h"
27 #include "qgslinesymbollayer.h"
28 #include "qgsfillsymbollayer.h"
29 #include "qgsrenderer.h"
30 #include "qgsrulebasedlabeling.h"
31 #include "qgssinglesymbolrenderer.h"
32 #include "qgscategorizedsymbolrenderer.h"
33 #include "qgsvectorlayerlabeling.h"
34 #include "qgsapplication.h"
35 #include "qgsmessagelog.h"
36
37 #include <QEventLoop>
38 #include <QNetworkRequest>
39 #include "qgsblockingnetworkrequest.h"
40 #include <QJsonDocument>
41 #include <QJsonObject>
42 #include <QImageReader>
43
mapEsriFieldType(const QString & esriFieldType)44 QVariant::Type QgsArcGisRestUtils::mapEsriFieldType( 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
mapEsriGeometryType(const QString & esriGeometryType)75 QgsWkbTypes::Type QgsArcGisRestUtils::mapEsriGeometryType( 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
parsePoint(const QVariantList & coordList,QgsWkbTypes::Type pointType)108 std::unique_ptr< QgsPoint > QgsArcGisRestUtils::parsePoint( 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 qgis::make_unique< QgsPoint >( pointType, x, y, z, m );
121 }
122
parseCircularString(const QVariantMap & curveData,QgsWkbTypes::Type pointType,const QgsPoint & startPoint)123 std::unique_ptr< QgsCircularString > QgsArcGisRestUtils::parseCircularString( 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 = parsePoint( coordData.toList(), pointType );
133 if ( !point )
134 {
135 return nullptr;
136 }
137 points.append( *point );
138 }
139 std::unique_ptr< QgsCircularString > curve = qgis::make_unique< QgsCircularString> ();
140 curve->setPoints( points );
141 return curve;
142 }
143
parseCompoundCurve(const QVariantList & curvesList,QgsWkbTypes::Type pointType)144 std::unique_ptr< QgsCompoundCurve > QgsArcGisRestUtils::parseCompoundCurve( 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 = qgis::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 = parsePoint( 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 = parseCircularString( 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
parseEsriGeometryPoint(const QVariantMap & geometryData,QgsWkbTypes::Type pointType)186 std::unique_ptr< QgsPoint > QgsArcGisRestUtils::parseEsriGeometryPoint( 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 qgis::make_unique< QgsPoint >( pointType, x, y, z, m );
197 }
198
parseEsriGeometryMultiPoint(const QVariantMap & geometryData,QgsWkbTypes::Type pointType)199 std::unique_ptr< QgsMultiPoint > QgsArcGisRestUtils::parseEsriGeometryMultiPoint( 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 = qgis::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 = parsePoint( 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 = parseEsriGeometryPoint( 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
parseEsriGeometryPolyline(const QVariantMap & geometryData,QgsWkbTypes::Type pointType)231 std::unique_ptr< QgsMultiCurve > QgsArcGisRestUtils::parseEsriGeometryPolyline( 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 = qgis::make_unique< QgsMultiCurve >();
242 multiCurve->reserve( pathsList.size() );
243 for ( const QVariant &pathData : qgis::as_const( pathsList ) )
244 {
245 std::unique_ptr< QgsCompoundCurve > curve = parseCompoundCurve( pathData.toList(), pointType );
246 if ( !curve )
247 {
248 return nullptr;
249 }
250 multiCurve->addGeometry( curve.release() );
251 }
252 return multiCurve;
253 }
254
parseEsriGeometryPolygon(const QVariantMap & geometryData,QgsWkbTypes::Type pointType)255 std::unique_ptr< QgsMultiSurface > QgsArcGisRestUtils::parseEsriGeometryPolygon( 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 = parseCompoundCurve( 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 = qgis::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
parseEsriEnvelope(const QVariantMap & geometryData)315 std::unique_ptr< QgsPolygon > QgsArcGisRestUtils::parseEsriEnvelope( 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 = qgis::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 = qgis::make_unique< QgsPolygon >();
332 poly->setExteriorRing( ext.release() );
333 return poly;
334 }
335
parseEsriGeoJSON(const QVariantMap & geometryData,const QString & esriGeometryType,bool readM,bool readZ,QgsCoordinateReferenceSystem * crs)336 std::unique_ptr<QgsAbstractGeometry> QgsArcGisRestUtils::parseEsriGeoJSON( 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 = parseSpatialReference( 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 parseEsriGeometryPoint( geometryData, pointType );
349 else if ( esriGeometryType == QLatin1String( "esriGeometryMultipoint" ) )
350 return parseEsriGeometryMultiPoint( geometryData, pointType );
351 else if ( esriGeometryType == QLatin1String( "esriGeometryPolyline" ) )
352 return parseEsriGeometryPolyline( geometryData, pointType );
353 else if ( esriGeometryType == QLatin1String( "esriGeometryPolygon" ) )
354 return parseEsriGeometryPolygon( geometryData, pointType );
355 else if ( esriGeometryType == QLatin1String( "esriGeometryEnvelope" ) )
356 return parseEsriEnvelope( geometryData );
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
parseSpatialReference(const QVariantMap & spatialReferenceMap)375 QgsCoordinateReferenceSystem QgsArcGisRestUtils::parseSpatialReference( 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
394
getServiceInfo(const QString & baseurl,const QString & authcfg,QString & errorTitle,QString & errorText,const QgsStringMap & requestHeaders)395 QVariantMap QgsArcGisRestUtils::getServiceInfo( const QString &baseurl, const QString &authcfg, QString &errorTitle, QString &errorText, const QgsStringMap &requestHeaders )
396 {
397 // http://sampleserver5.arcgisonline.com/arcgis/rest/services/Energy/Geology/FeatureServer?f=json
398 QUrl queryUrl( baseurl );
399 QUrlQuery query( queryUrl );
400 query.addQueryItem( QStringLiteral( "f" ), QStringLiteral( "json" ) );
401 queryUrl.setQuery( query );
402 return queryServiceJSON( queryUrl, authcfg, errorTitle, errorText, requestHeaders );
403 }
404
getLayerInfo(const QString & layerurl,const QString & authcfg,QString & errorTitle,QString & errorText,const QgsStringMap & requestHeaders)405 QVariantMap QgsArcGisRestUtils::getLayerInfo( const QString &layerurl, const QString &authcfg, QString &errorTitle, QString &errorText, const QgsStringMap &requestHeaders )
406 {
407 // http://sampleserver5.arcgisonline.com/arcgis/rest/services/Energy/Geology/FeatureServer/1?f=json
408 QUrl queryUrl( layerurl );
409 QUrlQuery query( queryUrl );
410 query.addQueryItem( QStringLiteral( "f" ), QStringLiteral( "json" ) );
411 queryUrl.setQuery( query );
412 return queryServiceJSON( queryUrl, authcfg, errorTitle, errorText, requestHeaders );
413 }
414
getObjectIds(const QString & layerurl,const QString & authcfg,QString & errorTitle,QString & errorText,const QgsStringMap & requestHeaders,const QgsRectangle & bbox)415 QVariantMap QgsArcGisRestUtils::getObjectIds( const QString &layerurl, const QString &authcfg, QString &errorTitle, QString &errorText, const QgsStringMap &requestHeaders, const QgsRectangle &bbox )
416 {
417 // http://sampleserver5.arcgisonline.com/arcgis/rest/services/Energy/Geology/FeatureServer/1/query?where=1%3D1&returnIdsOnly=true&f=json
418 QUrl queryUrl( layerurl + "/query" );
419 QUrlQuery query( queryUrl );
420 query.addQueryItem( QStringLiteral( "f" ), QStringLiteral( "json" ) );
421 query.addQueryItem( QStringLiteral( "where" ), QStringLiteral( "1=1" ) );
422 query.addQueryItem( QStringLiteral( "returnIdsOnly" ), QStringLiteral( "true" ) );
423 if ( !bbox.isNull() )
424 {
425 query.addQueryItem( QStringLiteral( "geometry" ), QStringLiteral( "%1,%2,%3,%4" )
426 .arg( bbox.xMinimum(), 0, 'f', -1 ).arg( bbox.yMinimum(), 0, 'f', -1 )
427 .arg( bbox.xMaximum(), 0, 'f', -1 ).arg( bbox.yMaximum(), 0, 'f', -1 ) );
428 query.addQueryItem( QStringLiteral( "geometryType" ), QStringLiteral( "esriGeometryEnvelope" ) );
429 query.addQueryItem( QStringLiteral( "spatialRel" ), QStringLiteral( "esriSpatialRelEnvelopeIntersects" ) );
430 }
431 queryUrl.setQuery( query );
432 return queryServiceJSON( queryUrl, authcfg, errorTitle, errorText, requestHeaders );
433 }
434
getObjects(const QString & layerurl,const QString & authcfg,const QList<quint32> & objectIds,const QString & crs,bool fetchGeometry,const QStringList & fetchAttributes,bool fetchM,bool fetchZ,const QgsRectangle & filterRect,QString & errorTitle,QString & errorText,const QgsStringMap & requestHeaders,QgsFeedback * feedback)435 QVariantMap QgsArcGisRestUtils::getObjects( const QString &layerurl, const QString &authcfg, const QList<quint32> &objectIds, const QString &crs,
436 bool fetchGeometry, const QStringList &fetchAttributes,
437 bool fetchM, bool fetchZ,
438 const QgsRectangle &filterRect,
439 QString &errorTitle, QString &errorText, const QgsStringMap &requestHeaders, QgsFeedback *feedback )
440 {
441 QStringList ids;
442 for ( int id : objectIds )
443 {
444 ids.append( QString::number( id ) );
445 }
446 QUrl queryUrl( layerurl + "/query" );
447 QUrlQuery query( queryUrl );
448 query.addQueryItem( QStringLiteral( "f" ), QStringLiteral( "json" ) );
449 query.addQueryItem( QStringLiteral( "objectIds" ), ids.join( QLatin1Char( ',' ) ) );
450 QString wkid = crs.indexOf( QLatin1Char( ':' ) ) >= 0 ? crs.split( ':' )[1] : QString();
451 query.addQueryItem( QStringLiteral( "inSR" ), wkid );
452 query.addQueryItem( QStringLiteral( "outSR" ), wkid );
453
454 query.addQueryItem( QStringLiteral( "returnGeometry" ), fetchGeometry ? QStringLiteral( "true" ) : QStringLiteral( "false" ) );
455
456 QString outFields;
457 if ( fetchAttributes.isEmpty() )
458 outFields = QStringLiteral( "*" );
459 else
460 outFields = fetchAttributes.join( ',' );
461 query.addQueryItem( QStringLiteral( "outFields" ), outFields );
462
463 query.addQueryItem( QStringLiteral( "returnM" ), fetchM ? QStringLiteral( "true" ) : QStringLiteral( "false" ) );
464 query.addQueryItem( QStringLiteral( "returnZ" ), fetchZ ? QStringLiteral( "true" ) : QStringLiteral( "false" ) );
465 if ( !filterRect.isNull() )
466 {
467 query.addQueryItem( QStringLiteral( "geometry" ), QStringLiteral( "%1,%2,%3,%4" )
468 .arg( filterRect.xMinimum(), 0, 'f', -1 ).arg( filterRect.yMinimum(), 0, 'f', -1 )
469 .arg( filterRect.xMaximum(), 0, 'f', -1 ).arg( filterRect.yMaximum(), 0, 'f', -1 ) );
470 query.addQueryItem( QStringLiteral( "geometryType" ), QStringLiteral( "esriGeometryEnvelope" ) );
471 query.addQueryItem( QStringLiteral( "spatialRel" ), QStringLiteral( "esriSpatialRelEnvelopeIntersects" ) );
472 }
473 queryUrl.setQuery( query );
474 return queryServiceJSON( queryUrl, authcfg, errorTitle, errorText, requestHeaders, feedback );
475 }
476
getObjectIdsByExtent(const QString & layerurl,const QgsRectangle & filterRect,QString & errorTitle,QString & errorText,const QString & authcfg,const QgsStringMap & requestHeaders,QgsFeedback * feedback)477 QList<quint32> QgsArcGisRestUtils::getObjectIdsByExtent( const QString &layerurl, const QgsRectangle &filterRect, QString &errorTitle, QString &errorText, const QString &authcfg, const QgsStringMap &requestHeaders, QgsFeedback *feedback )
478 {
479 QUrl queryUrl( layerurl + "/query" );
480 QUrlQuery query( queryUrl );
481 query.addQueryItem( QStringLiteral( "f" ), QStringLiteral( "json" ) );
482 query.addQueryItem( QStringLiteral( "where" ), QStringLiteral( "1=1" ) );
483 query.addQueryItem( QStringLiteral( "returnIdsOnly" ), QStringLiteral( "true" ) );
484 query.addQueryItem( QStringLiteral( "geometry" ), QStringLiteral( "%1,%2,%3,%4" )
485 .arg( filterRect.xMinimum(), 0, 'f', -1 ).arg( filterRect.yMinimum(), 0, 'f', -1 )
486 .arg( filterRect.xMaximum(), 0, 'f', -1 ).arg( filterRect.yMaximum(), 0, 'f', -1 ) );
487 query.addQueryItem( QStringLiteral( "geometryType" ), QStringLiteral( "esriGeometryEnvelope" ) );
488 query.addQueryItem( QStringLiteral( "spatialRel" ), QStringLiteral( "esriSpatialRelEnvelopeIntersects" ) );
489 queryUrl.setQuery( query );
490 const QVariantMap objectIdData = queryServiceJSON( queryUrl, authcfg, errorTitle, errorText, requestHeaders, feedback );
491
492 if ( objectIdData.isEmpty() )
493 {
494 return QList<quint32>();
495 }
496
497 QList<quint32> ids;
498 const QVariantList objectIdsList = objectIdData[QStringLiteral( "objectIds" )].toList();
499 ids.reserve( objectIdsList.size() );
500 for ( const QVariant &objectId : objectIdsList )
501 {
502 ids << objectId.toInt();
503 }
504 return ids;
505 }
506
queryService(const QUrl & u,const QString & authcfg,QString & errorTitle,QString & errorText,const QgsStringMap & requestHeaders,QgsFeedback * feedback,QString * contentType)507 QByteArray QgsArcGisRestUtils::queryService( const QUrl &u, const QString &authcfg, QString &errorTitle, QString &errorText, const QgsStringMap &requestHeaders, QgsFeedback *feedback, QString *contentType )
508 {
509 QUrl url = parseUrl( u );
510
511 QNetworkRequest request( url );
512 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsArcGisRestUtils" ) );
513 for ( auto it = requestHeaders.constBegin(); it != requestHeaders.constEnd(); ++it )
514 {
515 request.setRawHeader( it.key().toUtf8(), it.value().toUtf8() );
516 }
517
518 QgsBlockingNetworkRequest networkRequest;
519 networkRequest.setAuthCfg( authcfg );
520 const QgsBlockingNetworkRequest::ErrorCode error = networkRequest.get( request, false, feedback );
521
522 if ( feedback && feedback->isCanceled() )
523 return QByteArray();
524
525 // Handle network errors
526 if ( error != QgsBlockingNetworkRequest::NoError )
527 {
528 QgsDebugMsg( QStringLiteral( "Network error: %1" ).arg( networkRequest.errorMessage() ) );
529 errorTitle = QStringLiteral( "Network error" );
530 errorText = networkRequest.errorMessage();
531 return QByteArray();
532 }
533
534 const QgsNetworkReplyContent content = networkRequest.reply();
535 if ( contentType )
536 *contentType = content.rawHeader( "Content-Type" );
537 return content.content();
538 }
539
queryServiceJSON(const QUrl & url,const QString & authcfg,QString & errorTitle,QString & errorText,const QgsStringMap & requestHeaders,QgsFeedback * feedback)540 QVariantMap QgsArcGisRestUtils::queryServiceJSON( const QUrl &url, const QString &authcfg, QString &errorTitle, QString &errorText, const QgsStringMap &requestHeaders, QgsFeedback *feedback )
541 {
542 QByteArray reply = queryService( url, authcfg, errorTitle, errorText, requestHeaders, feedback );
543 if ( !errorTitle.isEmpty() )
544 {
545 return QVariantMap();
546 }
547 if ( feedback && feedback->isCanceled() )
548 return QVariantMap();
549
550 // Parse data
551 QJsonParseError err;
552 QJsonDocument doc = QJsonDocument::fromJson( reply, &err );
553 if ( doc.isNull() )
554 {
555 errorTitle = QStringLiteral( "Parsing error" );
556 errorText = err.errorString();
557 QgsDebugMsg( QStringLiteral( "Parsing error: %1" ).arg( err.errorString() ) );
558 return QVariantMap();
559 }
560 const QVariantMap res = doc.object().toVariantMap();
561 if ( res.contains( QStringLiteral( "error" ) ) )
562 {
563 const QVariantMap error = res.value( QStringLiteral( "error" ) ).toMap();
564 errorText = error.value( QStringLiteral( "message" ) ).toString();
565 errorTitle = QObject::tr( "Error %1" ).arg( error.value( QStringLiteral( "code" ) ).toString() );
566 return QVariantMap();
567 }
568 return res;
569 }
570
parseEsriSymbolJson(const QVariantMap & symbolData)571 std::unique_ptr<QgsSymbol> QgsArcGisRestUtils::parseEsriSymbolJson( const QVariantMap &symbolData )
572 {
573 const QString type = symbolData.value( QStringLiteral( "type" ) ).toString();
574 if ( type == QLatin1String( "esriSMS" ) )
575 {
576 // marker symbol
577 return parseEsriMarkerSymbolJson( symbolData );
578 }
579 else if ( type == QLatin1String( "esriSLS" ) )
580 {
581 // line symbol
582 return parseEsriLineSymbolJson( symbolData );
583 }
584 else if ( type == QLatin1String( "esriSFS" ) )
585 {
586 // fill symbol
587 return parseEsriFillSymbolJson( symbolData );
588 }
589 else if ( type == QLatin1String( "esriPFS" ) )
590 {
591 return parseEsriPictureFillSymbolJson( symbolData );
592 }
593 else if ( type == QLatin1String( "esriPMS" ) )
594 {
595 // picture marker
596 return parseEsriPictureMarkerSymbolJson( symbolData );
597 }
598 else if ( type == QLatin1String( "esriTS" ) )
599 {
600 // text symbol - not supported
601 return nullptr;
602 }
603 return nullptr;
604 }
605
parseEsriLineSymbolJson(const QVariantMap & symbolData)606 std::unique_ptr<QgsLineSymbol> QgsArcGisRestUtils::parseEsriLineSymbolJson( const QVariantMap &symbolData )
607 {
608 QColor lineColor = parseEsriColorJson( symbolData.value( QStringLiteral( "color" ) ) );
609 if ( !lineColor.isValid() )
610 return nullptr;
611
612 bool ok = false;
613 double widthInPoints = symbolData.value( QStringLiteral( "width" ) ).toDouble( &ok );
614 if ( !ok )
615 return nullptr;
616
617 QgsSymbolLayerList layers;
618 Qt::PenStyle penStyle = parseEsriLineStyle( symbolData.value( QStringLiteral( "style" ) ).toString() );
619 std::unique_ptr< QgsSimpleLineSymbolLayer > lineLayer = qgis::make_unique< QgsSimpleLineSymbolLayer >( lineColor, widthInPoints, penStyle );
620 lineLayer->setWidthUnit( QgsUnitTypes::RenderPoints );
621 layers.append( lineLayer.release() );
622
623 std::unique_ptr< QgsLineSymbol > symbol = qgis::make_unique< QgsLineSymbol >( layers );
624 return symbol;
625 }
626
parseEsriFillSymbolJson(const QVariantMap & symbolData)627 std::unique_ptr<QgsFillSymbol> QgsArcGisRestUtils::parseEsriFillSymbolJson( const QVariantMap &symbolData )
628 {
629 QColor fillColor = parseEsriColorJson( symbolData.value( QStringLiteral( "color" ) ) );
630 Qt::BrushStyle brushStyle = parseEsriFillStyle( symbolData.value( QStringLiteral( "style" ) ).toString() );
631
632 const QVariantMap outlineData = symbolData.value( QStringLiteral( "outline" ) ).toMap();
633 QColor lineColor = parseEsriColorJson( outlineData.value( QStringLiteral( "color" ) ) );
634 Qt::PenStyle penStyle = parseEsriLineStyle( outlineData.value( QStringLiteral( "style" ) ).toString() );
635 bool ok = false;
636 double penWidthInPoints = outlineData.value( QStringLiteral( "width" ) ).toDouble( &ok );
637
638 QgsSymbolLayerList layers;
639 std::unique_ptr< QgsSimpleFillSymbolLayer > fillLayer = qgis::make_unique< QgsSimpleFillSymbolLayer >( fillColor, brushStyle, lineColor, penStyle, penWidthInPoints );
640 fillLayer->setStrokeWidthUnit( QgsUnitTypes::RenderPoints );
641 layers.append( fillLayer.release() );
642
643 std::unique_ptr< QgsFillSymbol > symbol = qgis::make_unique< QgsFillSymbol >( layers );
644 return symbol;
645 }
646
parseEsriPictureFillSymbolJson(const QVariantMap & symbolData)647 std::unique_ptr<QgsFillSymbol> QgsArcGisRestUtils::parseEsriPictureFillSymbolJson( const QVariantMap &symbolData )
648 {
649 bool ok = false;
650
651 double widthInPixels = symbolData.value( QStringLiteral( "width" ) ).toInt( &ok );
652 if ( !ok )
653 return nullptr;
654
655 const double xScale = symbolData.value( QStringLiteral( "xscale" ) ).toDouble( &ok );
656 if ( !qgsDoubleNear( xScale, 0.0 ) )
657 widthInPixels *= xScale;
658
659 const double angleCCW = symbolData.value( QStringLiteral( "angle" ) ).toDouble( &ok );
660 double angleCW = 0;
661 if ( ok )
662 angleCW = -angleCCW;
663
664 const double xOffset = symbolData.value( QStringLiteral( "xoffset" ) ).toDouble();
665 const double yOffset = symbolData.value( QStringLiteral( "yoffset" ) ).toDouble();
666
667 QString symbolPath( symbolData.value( QStringLiteral( "imageData" ) ).toString() );
668 symbolPath.prepend( QLatin1String( "base64:" ) );
669
670 QgsSymbolLayerList layers;
671 std::unique_ptr< QgsRasterFillSymbolLayer > fillLayer = qgis::make_unique< QgsRasterFillSymbolLayer >( symbolPath );
672 fillLayer->setWidth( widthInPixels );
673 fillLayer->setAngle( angleCW );
674 fillLayer->setWidthUnit( QgsUnitTypes::RenderPoints );
675 fillLayer->setOffset( QPointF( xOffset, yOffset ) );
676 fillLayer->setOffsetUnit( QgsUnitTypes::RenderPoints );
677 layers.append( fillLayer.release() );
678
679 const QVariantMap outlineData = symbolData.value( QStringLiteral( "outline" ) ).toMap();
680 QColor lineColor = parseEsriColorJson( outlineData.value( QStringLiteral( "color" ) ) );
681 Qt::PenStyle penStyle = parseEsriLineStyle( outlineData.value( QStringLiteral( "style" ) ).toString() );
682 double penWidthInPoints = outlineData.value( QStringLiteral( "width" ) ).toDouble( &ok );
683
684 std::unique_ptr< QgsSimpleLineSymbolLayer > lineLayer = qgis::make_unique< QgsSimpleLineSymbolLayer >( lineColor, penWidthInPoints, penStyle );
685 lineLayer->setWidthUnit( QgsUnitTypes::RenderPoints );
686 layers.append( lineLayer.release() );
687
688 std::unique_ptr< QgsFillSymbol > symbol = qgis::make_unique< QgsFillSymbol >( layers );
689 return symbol;
690 }
691
parseEsriMarkerShape(const QString & style)692 QgsSimpleMarkerSymbolLayerBase::Shape QgsArcGisRestUtils::parseEsriMarkerShape( const QString &style )
693 {
694 if ( style == QLatin1String( "esriSMSCircle" ) )
695 return QgsSimpleMarkerSymbolLayerBase::Circle;
696 else if ( style == QLatin1String( "esriSMSCross" ) )
697 return QgsSimpleMarkerSymbolLayerBase::Cross;
698 else if ( style == QLatin1String( "esriSMSDiamond" ) )
699 return QgsSimpleMarkerSymbolLayerBase::Diamond;
700 else if ( style == QLatin1String( "esriSMSSquare" ) )
701 return QgsSimpleMarkerSymbolLayerBase::Square;
702 else if ( style == QLatin1String( "esriSMSX" ) )
703 return QgsSimpleMarkerSymbolLayerBase::Cross2;
704 else if ( style == QLatin1String( "esriSMSTriangle" ) )
705 return QgsSimpleMarkerSymbolLayerBase::Triangle;
706 else
707 return QgsSimpleMarkerSymbolLayerBase::Circle;
708 }
709
parseEsriMarkerSymbolJson(const QVariantMap & symbolData)710 std::unique_ptr<QgsMarkerSymbol> QgsArcGisRestUtils::parseEsriMarkerSymbolJson( const QVariantMap &symbolData )
711 {
712 QColor fillColor = parseEsriColorJson( symbolData.value( QStringLiteral( "color" ) ) );
713 bool ok = false;
714 const double sizeInPoints = symbolData.value( QStringLiteral( "size" ) ).toDouble( &ok );
715 if ( !ok )
716 return nullptr;
717 const double angleCCW = symbolData.value( QStringLiteral( "angle" ) ).toDouble( &ok );
718 double angleCW = 0;
719 if ( ok )
720 angleCW = -angleCCW;
721
722 QgsSimpleMarkerSymbolLayerBase::Shape shape = parseEsriMarkerShape( symbolData.value( QStringLiteral( "style" ) ).toString() );
723
724 const double xOffset = symbolData.value( QStringLiteral( "xoffset" ) ).toDouble();
725 const double yOffset = symbolData.value( QStringLiteral( "yoffset" ) ).toDouble();
726
727 const QVariantMap outlineData = symbolData.value( QStringLiteral( "outline" ) ).toMap();
728 QColor lineColor = parseEsriColorJson( outlineData.value( QStringLiteral( "color" ) ) );
729 Qt::PenStyle penStyle = parseEsriLineStyle( outlineData.value( QStringLiteral( "style" ) ).toString() );
730 double penWidthInPoints = outlineData.value( QStringLiteral( "width" ) ).toDouble( &ok );
731
732 QgsSymbolLayerList layers;
733 std::unique_ptr< QgsSimpleMarkerSymbolLayer > markerLayer = qgis::make_unique< QgsSimpleMarkerSymbolLayer >( shape, sizeInPoints, angleCW, QgsSymbol::ScaleArea, fillColor, lineColor );
734 markerLayer->setSizeUnit( QgsUnitTypes::RenderPoints );
735 markerLayer->setStrokeWidthUnit( QgsUnitTypes::RenderPoints );
736 markerLayer->setStrokeStyle( penStyle );
737 markerLayer->setStrokeWidth( penWidthInPoints );
738 markerLayer->setOffset( QPointF( xOffset, yOffset ) );
739 markerLayer->setOffsetUnit( QgsUnitTypes::RenderPoints );
740 layers.append( markerLayer.release() );
741
742 std::unique_ptr< QgsMarkerSymbol > symbol = qgis::make_unique< QgsMarkerSymbol >( layers );
743 return symbol;
744 }
745
parseEsriPictureMarkerSymbolJson(const QVariantMap & symbolData)746 std::unique_ptr<QgsMarkerSymbol> QgsArcGisRestUtils::parseEsriPictureMarkerSymbolJson( const QVariantMap &symbolData )
747 {
748 bool ok = false;
749 const double widthInPixels = symbolData.value( QStringLiteral( "width" ) ).toInt( &ok );
750 if ( !ok )
751 return nullptr;
752 const double heightInPixels = symbolData.value( QStringLiteral( "height" ) ).toInt( &ok );
753 if ( !ok )
754 return nullptr;
755
756 const double angleCCW = symbolData.value( QStringLiteral( "angle" ) ).toDouble( &ok );
757 double angleCW = 0;
758 if ( ok )
759 angleCW = -angleCCW;
760
761 const double xOffset = symbolData.value( QStringLiteral( "xoffset" ) ).toDouble();
762 const double yOffset = symbolData.value( QStringLiteral( "yoffset" ) ).toDouble();
763
764 //const QString contentType = symbolData.value( QStringLiteral( "contentType" ) ).toString();
765
766 QString symbolPath( symbolData.value( QStringLiteral( "imageData" ) ).toString() );
767 symbolPath.prepend( QLatin1String( "base64:" ) );
768
769 QgsSymbolLayerList layers;
770 std::unique_ptr< QgsRasterMarkerSymbolLayer > markerLayer = qgis::make_unique< QgsRasterMarkerSymbolLayer >( symbolPath, widthInPixels, angleCW, QgsSymbol::ScaleArea );
771 markerLayer->setSizeUnit( QgsUnitTypes::RenderPoints );
772
773 // only change the default aspect ratio if the server height setting requires this
774 if ( !qgsDoubleNear( static_cast< double >( heightInPixels ) / widthInPixels, markerLayer->defaultAspectRatio() ) )
775 markerLayer->setFixedAspectRatio( static_cast< double >( heightInPixels ) / widthInPixels );
776
777 markerLayer->setOffset( QPointF( xOffset, yOffset ) );
778 markerLayer->setOffsetUnit( QgsUnitTypes::RenderPoints );
779 layers.append( markerLayer.release() );
780
781 std::unique_ptr< QgsMarkerSymbol > symbol = qgis::make_unique< QgsMarkerSymbol >( layers );
782 return symbol;
783 }
784
parseEsriLabeling(const QVariantList & labelingData)785 QgsAbstractVectorLayerLabeling *QgsArcGisRestUtils::parseEsriLabeling( const QVariantList &labelingData )
786 {
787 if ( labelingData.empty() )
788 return nullptr;
789
790 QgsRuleBasedLabeling::Rule *root = new QgsRuleBasedLabeling::Rule( new QgsPalLayerSettings(), 0, 0, QString(), QString(), false );
791 root->setActive( true );
792
793 int i = 1;
794 for ( const QVariant &lbl : labelingData )
795 {
796 const QVariantMap labeling = lbl.toMap();
797
798 QgsPalLayerSettings *settings = new QgsPalLayerSettings();
799 QgsTextFormat format;
800
801 const QString placement = labeling.value( QStringLiteral( "labelPlacement" ) ).toString();
802 if ( placement == QLatin1String( "esriServerPointLabelPlacementAboveCenter" ) )
803 {
804 settings->placement = QgsPalLayerSettings::OverPoint;
805 settings->quadOffset = QgsPalLayerSettings::QuadrantAbove;
806 }
807 else if ( placement == QLatin1String( "esriServerPointLabelPlacementBelowCenter" ) )
808 {
809 settings->placement = QgsPalLayerSettings::OverPoint;
810 settings->quadOffset = QgsPalLayerSettings::QuadrantBelow;
811 }
812 else if ( placement == QLatin1String( "esriServerPointLabelPlacementCenterCenter" ) )
813 {
814 settings->placement = QgsPalLayerSettings::OverPoint;
815 settings->quadOffset = QgsPalLayerSettings::QuadrantOver;
816 }
817 else if ( placement == QLatin1String( "esriServerPointLabelPlacementAboveLeft" ) )
818 {
819 settings->placement = QgsPalLayerSettings::OverPoint;
820 settings->quadOffset = QgsPalLayerSettings::QuadrantAboveLeft;
821 }
822 else if ( placement == QLatin1String( "esriServerPointLabelPlacementBelowLeft" ) )
823 {
824 settings->placement = QgsPalLayerSettings::OverPoint;
825 settings->quadOffset = QgsPalLayerSettings::QuadrantBelowLeft;
826 }
827 else if ( placement == QLatin1String( "esriServerPointLabelPlacementCenterLeft" ) )
828 {
829 settings->placement = QgsPalLayerSettings::OverPoint;
830 settings->quadOffset = QgsPalLayerSettings::QuadrantLeft;
831 }
832 else if ( placement == QLatin1String( "esriServerPointLabelPlacementAboveRight" ) )
833 {
834 settings->placement = QgsPalLayerSettings::OverPoint;
835 settings->quadOffset = QgsPalLayerSettings::QuadrantAboveRight;
836 }
837 else if ( placement == QLatin1String( "esriServerPointLabelPlacementBelowRight" ) )
838 {
839 settings->placement = QgsPalLayerSettings::OverPoint;
840 settings->quadOffset = QgsPalLayerSettings::QuadrantBelowRight;
841 }
842 else if ( placement == QLatin1String( "esriServerPointLabelPlacementCenterRight" ) )
843 {
844 settings->placement = QgsPalLayerSettings::OverPoint;
845 settings->quadOffset = QgsPalLayerSettings::QuadrantRight;
846 }
847 else if ( placement == QLatin1String( "esriServerLinePlacementAboveAfter" ) ||
848 placement == QLatin1String( "esriServerLinePlacementAboveStart" ) ||
849 placement == QLatin1String( "esriServerLinePlacementAboveAlong" ) )
850 {
851 settings->placement = QgsPalLayerSettings::Line;
852 settings->lineSettings().setPlacementFlags( QgsLabeling::LinePlacementFlag::AboveLine | QgsLabeling::LinePlacementFlag::MapOrientation );
853 }
854 else if ( placement == QLatin1String( "esriServerLinePlacementBelowAfter" ) ||
855 placement == QLatin1String( "esriServerLinePlacementBelowStart" ) ||
856 placement == QLatin1String( "esriServerLinePlacementBelowAlong" ) )
857 {
858 settings->placement = QgsPalLayerSettings::Line;
859 settings->lineSettings().setPlacementFlags( QgsLabeling::LinePlacementFlag::BelowLine | QgsLabeling::LinePlacementFlag::MapOrientation );
860 }
861 else if ( placement == QLatin1String( "esriServerLinePlacementCenterAfter" ) ||
862 placement == QLatin1String( "esriServerLinePlacementCenterStart" ) ||
863 placement == QLatin1String( "esriServerLinePlacementCenterAlong" ) )
864 {
865 settings->placement = QgsPalLayerSettings::Line;
866 settings->lineSettings().setPlacementFlags( QgsLabeling::LinePlacementFlag::OnLine | QgsLabeling::LinePlacementFlag::MapOrientation );
867 }
868 else if ( placement == QLatin1String( "esriServerPolygonPlacementAlwaysHorizontal" ) )
869 {
870 settings->placement = QgsPalLayerSettings::Horizontal;
871 }
872
873 const double minScale = labeling.value( QStringLiteral( "minScale" ) ).toDouble();
874 const double maxScale = labeling.value( QStringLiteral( "maxScale" ) ).toDouble();
875
876 QVariantMap symbol = labeling.value( QStringLiteral( "symbol" ) ).toMap();
877 format.setColor( parseEsriColorJson( symbol.value( QStringLiteral( "color" ) ) ) );
878 const double haloSize = symbol.value( QStringLiteral( "haloSize" ) ).toDouble();
879 if ( !qgsDoubleNear( haloSize, 0.0 ) )
880 {
881 QgsTextBufferSettings buffer;
882 buffer.setEnabled( true );
883 buffer.setSize( haloSize );
884 buffer.setSizeUnit( QgsUnitTypes::RenderPoints );
885 buffer.setColor( parseEsriColorJson( symbol.value( QStringLiteral( "haloColor" ) ) ) );
886 format.setBuffer( buffer );
887 }
888
889 const QString fontFamily = symbol.value( QStringLiteral( "font" ) ).toMap().value( QStringLiteral( "family" ) ).toString();
890 const QString fontStyle = symbol.value( QStringLiteral( "font" ) ).toMap().value( QStringLiteral( "style" ) ).toString();
891 const QString fontWeight = symbol.value( QStringLiteral( "font" ) ).toMap().value( QStringLiteral( "weight" ) ).toString();
892 const int fontSize = symbol.value( QStringLiteral( "font" ) ).toMap().value( QStringLiteral( "size" ) ).toInt();
893 QFont font( fontFamily, fontSize );
894 font.setStyleName( fontStyle );
895 font.setWeight( fontWeight == QLatin1String( "bold" ) ? QFont::Bold : QFont::Normal );
896
897 format.setFont( font );
898 format.setSize( fontSize );
899 format.setSizeUnit( QgsUnitTypes::RenderPoints );
900
901 settings->setFormat( format );
902
903 QString where = labeling.value( QStringLiteral( "where" ) ).toString();
904 QgsExpression exp( where );
905 // If the where clause isn't parsed as valid, don't use its
906 if ( !exp.isValid() )
907 where.clear();
908
909 settings->fieldName = parseEsriLabelingExpression( labeling.value( QStringLiteral( "labelExpression" ) ).toString() );
910 settings->isExpression = true;
911
912 QgsRuleBasedLabeling::Rule *child = new QgsRuleBasedLabeling::Rule( settings, maxScale, minScale, where, QObject::tr( "ASF label %1" ).arg( i++ ), false );
913 child->setActive( true );
914 root->appendChild( child );
915 }
916
917 return new QgsRuleBasedLabeling( root );
918 }
919
parseEsriRenderer(const QVariantMap & rendererData)920 QgsFeatureRenderer *QgsArcGisRestUtils::parseEsriRenderer( const QVariantMap &rendererData )
921 {
922 const QString type = rendererData.value( QStringLiteral( "type" ) ).toString();
923 if ( type == QLatin1String( "simple" ) )
924 {
925 const QVariantMap symbolProps = rendererData.value( QStringLiteral( "symbol" ) ).toMap();
926 std::unique_ptr< QgsSymbol > symbol = parseEsriSymbolJson( symbolProps );
927 if ( symbol )
928 return new QgsSingleSymbolRenderer( symbol.release() );
929 else
930 return nullptr;
931 }
932 else if ( type == QLatin1String( "uniqueValue" ) )
933 {
934 const QString field1 = rendererData.value( QStringLiteral( "field1" ) ).toString();
935 const QString field2 = rendererData.value( QStringLiteral( "field2" ) ).toString();
936 const QString field3 = rendererData.value( QStringLiteral( "field3" ) ).toString();
937 QString attribute;
938 if ( !field2.isEmpty() || !field3.isEmpty() )
939 {
940 const QString delimiter = rendererData.value( QStringLiteral( "fieldDelimiter" ) ).toString();
941 if ( !field3.isEmpty() )
942 {
943 attribute = QStringLiteral( "concat(\"%1\",'%2',\"%3\",'%4',\"%5\")" ).arg( field1, delimiter, field2, delimiter, field3 );
944 }
945 else
946 {
947 attribute = QStringLiteral( "concat(\"%1\",'%2',\"%3\")" ).arg( field1, delimiter, field2 );
948 }
949 }
950 else
951 {
952 attribute = field1;
953 }
954
955 const QVariantList categories = rendererData.value( QStringLiteral( "uniqueValueInfos" ) ).toList();
956 QgsCategoryList categoryList;
957 for ( const QVariant &category : categories )
958 {
959 const QVariantMap categoryData = category.toMap();
960 const QString value = categoryData.value( QStringLiteral( "value" ) ).toString();
961 const QString label = categoryData.value( QStringLiteral( "label" ) ).toString();
962 std::unique_ptr< QgsSymbol > symbol = QgsArcGisRestUtils::parseEsriSymbolJson( categoryData.value( QStringLiteral( "symbol" ) ).toMap() );
963 if ( symbol )
964 {
965 categoryList.append( QgsRendererCategory( value, symbol.release(), label ) );
966 }
967 }
968
969 std::unique_ptr< QgsSymbol > defaultSymbol = parseEsriSymbolJson( rendererData.value( QStringLiteral( "defaultSymbol" ) ).toMap() );
970 if ( defaultSymbol )
971 {
972 categoryList.append( QgsRendererCategory( QVariant(), defaultSymbol.release(), rendererData.value( QStringLiteral( "defaultLabel" ) ).toString() ) );
973 }
974
975 if ( categoryList.empty() )
976 return nullptr;
977
978 return new QgsCategorizedSymbolRenderer( attribute, categoryList );
979 }
980 else if ( type == QLatin1String( "classBreaks" ) )
981 {
982 // currently unsupported
983 return nullptr;
984 }
985 else if ( type == QLatin1String( "heatmap" ) )
986 {
987 // currently unsupported
988 return nullptr;
989 }
990 else if ( type == QLatin1String( "vectorField" ) )
991 {
992 // currently unsupported
993 return nullptr;
994 }
995 return nullptr;
996 }
997
parseEsriLabelingExpression(const QString & string)998 QString QgsArcGisRestUtils::parseEsriLabelingExpression( const QString &string )
999 {
1000 QString expression = string;
1001
1002 // Replace a few ArcGIS token to QGIS equivalents
1003 expression = expression.replace( QRegularExpression( "(?=([^\"\\\\]*(\\\\.|\"([^\"\\\\]*\\\\.)*[^\"\\\\]*\"))*[^\"]*$)(\\s|^)CONCAT(\\s|$)" ), QStringLiteral( "\\4||\\5" ) );
1004 expression = expression.replace( QRegularExpression( "(?=([^\"\\\\]*(\\\\.|\"([^\"\\\\]*\\\\.)*[^\"\\\\]*\"))*[^\"]*$)(\\s|^)NEWLINE(\\s|$)" ), QStringLiteral( "\\4'\\n'\\5" ) );
1005
1006 // ArcGIS's double quotes are single quotes in QGIS
1007 expression = expression.replace( QRegularExpression( "\"(.*?(?<!\\\\))\"" ), QStringLiteral( "'\\1'" ) );
1008 expression = expression.replace( QRegularExpression( "\\\\\"" ), QStringLiteral( "\"" ) );
1009
1010 // ArcGIS's square brakets are double quotes in QGIS
1011 expression = expression.replace( QRegularExpression( "\\[([^]]*)\\]" ), QStringLiteral( "\"\\1\"" ) );
1012
1013 return expression;
1014 }
1015
parseEsriColorJson(const QVariant & colorData)1016 QColor QgsArcGisRestUtils::parseEsriColorJson( const QVariant &colorData )
1017 {
1018 const QVariantList colorParts = colorData.toList();
1019 if ( colorParts.count() < 4 )
1020 return QColor();
1021
1022 int red = colorParts.at( 0 ).toInt();
1023 int green = colorParts.at( 1 ).toInt();
1024 int blue = colorParts.at( 2 ).toInt();
1025 int alpha = colorParts.at( 3 ).toInt();
1026 return QColor( red, green, blue, alpha );
1027 }
1028
parseEsriLineStyle(const QString & style)1029 Qt::PenStyle QgsArcGisRestUtils::parseEsriLineStyle( const QString &style )
1030 {
1031 if ( style == QLatin1String( "esriSLSSolid" ) )
1032 return Qt::SolidLine;
1033 else if ( style == QLatin1String( "esriSLSDash" ) )
1034 return Qt::DashLine;
1035 else if ( style == QLatin1String( "esriSLSDashDot" ) )
1036 return Qt::DashDotLine;
1037 else if ( style == QLatin1String( "esriSLSDashDotDot" ) )
1038 return Qt::DashDotDotLine;
1039 else if ( style == QLatin1String( "esriSLSDot" ) )
1040 return Qt::DotLine;
1041 else if ( style == QLatin1String( "esriSLSNull" ) )
1042 return Qt::NoPen;
1043 else
1044 return Qt::SolidLine;
1045 }
1046
parseEsriFillStyle(const QString & style)1047 Qt::BrushStyle QgsArcGisRestUtils::parseEsriFillStyle( const QString &style )
1048 {
1049 if ( style == QLatin1String( "esriSFSBackwardDiagonal" ) )
1050 return Qt::BDiagPattern;
1051 else if ( style == QLatin1String( "esriSFSCross" ) )
1052 return Qt::CrossPattern;
1053 else if ( style == QLatin1String( "esriSFSDiagonalCross" ) )
1054 return Qt::DiagCrossPattern;
1055 else if ( style == QLatin1String( "esriSFSForwardDiagonal" ) )
1056 return Qt::FDiagPattern;
1057 else if ( style == QLatin1String( "esriSFSHorizontal" ) )
1058 return Qt::HorPattern;
1059 else if ( style == QLatin1String( "esriSFSNull" ) )
1060 return Qt::NoBrush;
1061 else if ( style == QLatin1String( "esriSFSSolid" ) )
1062 return Qt::SolidPattern;
1063 else if ( style == QLatin1String( "esriSFSVertical" ) )
1064 return Qt::VerPattern;
1065 else
1066 return Qt::SolidPattern;
1067 }
1068
parseDateTime(const QVariant & value)1069 QDateTime QgsArcGisRestUtils::parseDateTime( const QVariant &value )
1070 {
1071 if ( value.isNull() )
1072 return QDateTime();
1073 bool ok = false;
1074 QDateTime dt = QDateTime::fromMSecsSinceEpoch( value.toLongLong( &ok ) );
1075 if ( !ok )
1076 {
1077 QgsDebugMsg( QStringLiteral( "Invalid value %1 for datetime" ).arg( value.toString() ) );
1078 return QDateTime();
1079 }
1080 else
1081 return dt;
1082 }
1083
parseUrl(const QUrl & url)1084 QUrl QgsArcGisRestUtils::parseUrl( const QUrl &url )
1085 {
1086 QUrl modifiedUrl( url );
1087 if ( modifiedUrl.toString().contains( QLatin1String( "fake_qgis_http_endpoint" ) ) )
1088 {
1089 // Just for testing with local files instead of http:// resources
1090 QString modifiedUrlString = modifiedUrl.toString();
1091 // Qt5 does URL encoding from some reason (of the FILTER parameter for example)
1092 modifiedUrlString = QUrl::fromPercentEncoding( modifiedUrlString.toUtf8() );
1093 modifiedUrlString.replace( QLatin1String( "fake_qgis_http_endpoint/" ), QLatin1String( "fake_qgis_http_endpoint_" ) );
1094 QgsDebugMsg( QStringLiteral( "Get %1" ).arg( modifiedUrlString ) );
1095 modifiedUrlString = modifiedUrlString.mid( QStringLiteral( "http://" ).size() );
1096 QString args = modifiedUrlString.mid( modifiedUrlString.indexOf( '?' ) );
1097 if ( modifiedUrlString.size() > 150 )
1098 {
1099 args = QCryptographicHash::hash( args.toUtf8(), QCryptographicHash::Md5 ).toHex();
1100 }
1101 else
1102 {
1103 args.replace( QLatin1String( "?" ), QLatin1String( "_" ) );
1104 args.replace( QLatin1String( "&" ), QLatin1String( "_" ) );
1105 args.replace( QLatin1String( "<" ), QLatin1String( "_" ) );
1106 args.replace( QLatin1String( ">" ), QLatin1String( "_" ) );
1107 args.replace( QLatin1String( "'" ), QLatin1String( "_" ) );
1108 args.replace( QLatin1String( "\"" ), QLatin1String( "_" ) );
1109 args.replace( QLatin1String( " " ), QLatin1String( "_" ) );
1110 args.replace( QLatin1String( ":" ), QLatin1String( "_" ) );
1111 args.replace( QLatin1String( "/" ), QLatin1String( "_" ) );
1112 args.replace( QLatin1String( "\n" ), QLatin1String( "_" ) );
1113 }
1114 #ifdef Q_OS_WIN
1115 // Passing "urls" like "http://c:/path" to QUrl 'eats' the : after c,
1116 // so we must restore it
1117 if ( modifiedUrlString[1] == '/' )
1118 {
1119 modifiedUrlString = modifiedUrlString[0] + ":/" + modifiedUrlString.mid( 2 );
1120 }
1121 #endif
1122 modifiedUrlString = modifiedUrlString.mid( 0, modifiedUrlString.indexOf( '?' ) ) + args;
1123 QgsDebugMsg( QStringLiteral( "Get %1 (after laundering)" ).arg( modifiedUrlString ) );
1124 modifiedUrl = QUrl::fromLocalFile( modifiedUrlString );
1125 }
1126
1127 return modifiedUrl;
1128 }
1129
1130 ///////////////////////////////////////////////////////////////////////////////
1131
QgsArcGisAsyncQuery(QObject * parent)1132 QgsArcGisAsyncQuery::QgsArcGisAsyncQuery( QObject *parent )
1133 : QObject( parent )
1134 {
1135 }
1136
~QgsArcGisAsyncQuery()1137 QgsArcGisAsyncQuery::~QgsArcGisAsyncQuery()
1138 {
1139 if ( mReply )
1140 mReply->deleteLater();
1141 }
1142
start(const QUrl & url,const QString & authCfg,QByteArray * result,bool allowCache,const QgsStringMap & headers)1143 void QgsArcGisAsyncQuery::start( const QUrl &url, const QString &authCfg, QByteArray *result, bool allowCache, const QgsStringMap &headers )
1144 {
1145 mResult = result;
1146 QNetworkRequest request( url );
1147
1148 for ( auto it = headers.constBegin(); it != headers.constEnd(); ++it )
1149 {
1150 request.setRawHeader( it.key().toUtf8(), it.value().toUtf8() );
1151 }
1152
1153 if ( !authCfg.isEmpty() && !QgsApplication::authManager()->updateNetworkRequest( request, authCfg ) )
1154 {
1155 const QString error = tr( "network request update failed for authentication config" );
1156 emit failed( QStringLiteral( "Network" ), error );
1157 return;
1158 }
1159
1160 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsArcGisAsyncQuery" ) );
1161 if ( allowCache )
1162 {
1163 request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
1164 request.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
1165 }
1166 mReply = QgsNetworkAccessManager::instance()->get( request );
1167 connect( mReply, &QNetworkReply::finished, this, &QgsArcGisAsyncQuery::handleReply );
1168 }
1169
handleReply()1170 void QgsArcGisAsyncQuery::handleReply()
1171 {
1172 mReply->deleteLater();
1173 // Handle network errors
1174 if ( mReply->error() != QNetworkReply::NoError )
1175 {
1176 QgsDebugMsg( QStringLiteral( "Network error: %1" ).arg( mReply->errorString() ) );
1177 emit failed( QStringLiteral( "Network error" ), mReply->errorString() );
1178 return;
1179 }
1180
1181 // Handle HTTP redirects
1182 QVariant redirect = mReply->attribute( QNetworkRequest::RedirectionTargetAttribute );
1183 if ( !redirect.isNull() )
1184 {
1185 QNetworkRequest request = mReply->request();
1186 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsArcGisAsyncQuery" ) );
1187 QgsDebugMsg( "redirecting to " + redirect.toUrl().toString() );
1188 request.setUrl( redirect.toUrl() );
1189 mReply = QgsNetworkAccessManager::instance()->get( request );
1190 connect( mReply, &QNetworkReply::finished, this, &QgsArcGisAsyncQuery::handleReply );
1191 return;
1192 }
1193
1194 *mResult = mReply->readAll();
1195 mResult = nullptr;
1196 emit finished();
1197 }
1198
1199 ///////////////////////////////////////////////////////////////////////////////
1200
QgsArcGisAsyncParallelQuery(const QString & authcfg,const QgsStringMap & requestHeaders,QObject * parent)1201 QgsArcGisAsyncParallelQuery::QgsArcGisAsyncParallelQuery( const QString &authcfg, const QgsStringMap &requestHeaders, QObject *parent )
1202 : QObject( parent )
1203 , mAuthCfg( authcfg )
1204 , mRequestHeaders( requestHeaders )
1205 {
1206 }
1207
start(const QVector<QUrl> & urls,QVector<QByteArray> * results,bool allowCache)1208 void QgsArcGisAsyncParallelQuery::start( const QVector<QUrl> &urls, QVector<QByteArray> *results, bool allowCache )
1209 {
1210 Q_ASSERT( results->size() == urls.size() );
1211 mResults = results;
1212 mPendingRequests = mResults->size();
1213 for ( int i = 0, n = urls.size(); i < n; ++i )
1214 {
1215 QNetworkRequest request( urls[i] );
1216 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsArcGisAsyncParallelQuery" ) );
1217 QgsSetRequestInitiatorId( request, QString::number( i ) );
1218
1219 for ( auto it = mRequestHeaders.constBegin(); it != mRequestHeaders.constEnd(); ++it )
1220 {
1221 request.setRawHeader( it.key().toUtf8(), it.value().toUtf8() );
1222 }
1223 if ( !mAuthCfg.isEmpty() && !QgsApplication::authManager()->updateNetworkRequest( request, mAuthCfg ) )
1224 {
1225 const QString error = tr( "network request update failed for authentication config" );
1226 mErrors.append( error );
1227 QgsMessageLog::logMessage( error, tr( "Network" ) );
1228 continue;
1229 }
1230
1231 request.setAttribute( QNetworkRequest::HttpPipeliningAllowedAttribute, true );
1232 if ( allowCache )
1233 {
1234 request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
1235 request.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
1236 request.setRawHeader( "Connection", "keep-alive" );
1237 }
1238 QNetworkReply *reply = QgsNetworkAccessManager::instance()->get( request );
1239 reply->setProperty( "idx", i );
1240 connect( reply, &QNetworkReply::finished, this, &QgsArcGisAsyncParallelQuery::handleReply );
1241 }
1242 }
1243
handleReply()1244 void QgsArcGisAsyncParallelQuery::handleReply()
1245 {
1246 QNetworkReply *reply = qobject_cast<QNetworkReply *>( QObject::sender() );
1247 QVariant redirect = reply->attribute( QNetworkRequest::RedirectionTargetAttribute );
1248 int idx = reply->property( "idx" ).toInt();
1249 reply->deleteLater();
1250 if ( reply->error() != QNetworkReply::NoError )
1251 {
1252 // Handle network errors
1253 mErrors.append( reply->errorString() );
1254 --mPendingRequests;
1255 }
1256 else if ( !redirect.isNull() )
1257 {
1258 // Handle HTTP redirects
1259 QNetworkRequest request = reply->request();
1260 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsArcGisAsyncParallelQuery" ) );
1261 QgsDebugMsg( "redirecting to " + redirect.toUrl().toString() );
1262 request.setUrl( redirect.toUrl() );
1263 reply = QgsNetworkAccessManager::instance()->get( request );
1264 reply->setProperty( "idx", idx );
1265 connect( reply, &QNetworkReply::finished, this, &QgsArcGisAsyncParallelQuery::handleReply );
1266 }
1267 else
1268 {
1269 // All OK
1270 ( *mResults )[idx] = reply->readAll();
1271 --mPendingRequests;
1272 }
1273 if ( mPendingRequests == 0 )
1274 {
1275 emit finished( mErrors );
1276 mResults = nullptr;
1277 mErrors.clear();
1278 }
1279 }
1280
adjustBaseUrl(QString & baseUrl,const QString name)1281 void QgsArcGisRestUtils::adjustBaseUrl( QString &baseUrl, const QString name )
1282 {
1283 QStringList parts = name.split( '/' );
1284 QString checkString;
1285 for ( const QString &part : parts )
1286 {
1287 if ( !checkString.isEmpty() )
1288 checkString += QString( '/' );
1289
1290 checkString += part;
1291 if ( baseUrl.indexOf( QRegularExpression( checkString.replace( '/', QLatin1String( "\\/" ) ) + QStringLiteral( "\\/?$" ) ) ) > -1 )
1292 {
1293 baseUrl = baseUrl.left( baseUrl.length() - checkString.length() - 1 );
1294 break;
1295 }
1296 }
1297 }
1298
visitFolderItems(const std::function<void (const QString &,const QString &)> & visitor,const QVariantMap & serviceData,const QString & baseUrl)1299 void QgsArcGisRestUtils::visitFolderItems( const std::function< void( const QString &, const QString & ) > &visitor, const QVariantMap &serviceData, const QString &baseUrl )
1300 {
1301 QString base( baseUrl );
1302 bool baseChecked = false;
1303 if ( !base.endsWith( '/' ) )
1304 base += QLatin1Char( '/' );
1305
1306 const QStringList folderList = serviceData.value( QStringLiteral( "folders" ) ).toStringList();
1307 for ( const QString &folder : folderList )
1308 {
1309 if ( !baseChecked )
1310 {
1311 adjustBaseUrl( base, folder );
1312 baseChecked = true;
1313 }
1314 visitor( folder, base + folder );
1315 }
1316 }
1317
visitServiceItems(const std::function<void (const QString &,const QString &)> & visitor,const QVariantMap & serviceData,const QString & baseUrl,const ServiceTypeFilter filter)1318 void QgsArcGisRestUtils::visitServiceItems( const std::function< void( const QString &, const QString & ) > &visitor, const QVariantMap &serviceData, const QString &baseUrl, const ServiceTypeFilter filter )
1319 {
1320 QString base( baseUrl );
1321 bool baseChecked = false;
1322 if ( !base.endsWith( '/' ) )
1323 base += QLatin1Char( '/' );
1324
1325 const QVariantList serviceList = serviceData.value( QStringLiteral( "services" ) ).toList();
1326 for ( const QVariant &service : serviceList )
1327 {
1328 const QVariantMap serviceMap = service.toMap();
1329 const QString serviceType = serviceMap.value( QStringLiteral( "type" ) ).toString();
1330 if ( serviceType != QLatin1String( "MapServer" ) && serviceType != QLatin1String( "ImageServer" ) && serviceType != QLatin1String( "FeatureServer" ) )
1331 continue;
1332
1333 // If the requested service type is raster, do not show vector-only services
1334 if ( serviceType == QLatin1String( "FeatureServer" ) && filter == QgsArcGisRestUtils::Raster )
1335 continue;
1336
1337 const QString serviceName = serviceMap.value( QStringLiteral( "name" ) ).toString();
1338 QString displayName = serviceName.split( '/' ).last();
1339 if ( !baseChecked )
1340 {
1341 adjustBaseUrl( base, serviceName );
1342 baseChecked = true;
1343 }
1344
1345 visitor( displayName, base + serviceName + '/' + serviceType );
1346 }
1347 }
1348
addLayerItems(const std::function<void (const QString &,const QString &,const QString &,const QString &,const QString &,bool,const QString &,const QString &)> & visitor,const QVariantMap & serviceData,const QString & parentUrl,const ServiceTypeFilter filter)1349 void QgsArcGisRestUtils::addLayerItems( const std::function< void( const QString &, const QString &, const QString &, const QString &, const QString &, bool, const QString &, const QString & )> &visitor, const QVariantMap &serviceData, const QString &parentUrl, const ServiceTypeFilter filter )
1350 {
1351 const QString authid = QgsArcGisRestUtils::parseSpatialReference( serviceData.value( QStringLiteral( "spatialReference" ) ).toMap() ).authid();
1352
1353 QString format = QStringLiteral( "jpg" );
1354 bool found = false;
1355 const QList<QByteArray> supportedFormats = QImageReader::supportedImageFormats();
1356 const QStringList supportedImageFormatTypes = serviceData.value( QStringLiteral( "supportedImageFormatTypes" ) ).toString().split( ',' );
1357 for ( const QString &encoding : supportedImageFormatTypes )
1358 {
1359 for ( const QByteArray &fmt : supportedFormats )
1360 {
1361 if ( encoding.startsWith( fmt, Qt::CaseInsensitive ) )
1362 {
1363 format = encoding;
1364 found = true;
1365 break;
1366 }
1367 }
1368 if ( found )
1369 break;
1370 }
1371 const QStringList capabilities = serviceData.value( QStringLiteral( "capabilities" ) ).toString().split( ',' );
1372
1373 // If the requested layer type is vector, do not show raster-only layers (i.e. non query-able layers)
1374 const bool serviceMayHaveQueryCapability = capabilities.contains( QStringLiteral( "Query" ) ) ||
1375 serviceData.value( QStringLiteral( "serviceDataType" ) ).toString().startsWith( QLatin1String( "esriImageService" ) );
1376 if ( filter == QgsArcGisRestUtils::Vector && !serviceMayHaveQueryCapability )
1377 return;
1378
1379 const QVariantList layerInfoList = serviceData.value( QStringLiteral( "layers" ) ).toList();
1380 for ( const QVariant &layerInfo : layerInfoList )
1381 {
1382 const QVariantMap layerInfoMap = layerInfo.toMap();
1383 const QString id = layerInfoMap.value( QStringLiteral( "id" ) ).toString();
1384 const QString parentLayerId = layerInfoMap.value( QStringLiteral( "parentLayerId" ) ).toString();
1385 const QString name = layerInfoMap.value( QStringLiteral( "name" ) ).toString();
1386 const QString description = layerInfoMap.value( QStringLiteral( "description" ) ).toString();
1387
1388 if ( !layerInfoMap.value( QStringLiteral( "subLayerIds" ) ).toList().empty() )
1389 {
1390 visitor( parentLayerId, id, name, description, parentUrl + '/' + id, true, QString(), format );
1391 }
1392 else
1393 {
1394 visitor( parentLayerId, id, name, description, parentUrl + '/' + id, false, authid, format );
1395 }
1396 }
1397
1398 // Add root MapServer as raster layer when multiple layers are listed
1399 if ( filter != QgsArcGisRestUtils::Vector && layerInfoList.count() > 1 && serviceData.contains( QStringLiteral( "supportedImageFormatTypes" ) ) )
1400 {
1401 const QString name = QStringLiteral( "(%1)" ).arg( QObject::tr( "All layers" ) );
1402 const QString description = serviceData.value( QStringLiteral( "Comments" ) ).toString();
1403 visitor( 0, 0, name, description, parentUrl, false, authid, format );
1404 }
1405
1406 // Add root ImageServer as layer
1407 if ( serviceData.value( QStringLiteral( "serviceDataType" ) ).toString().startsWith( QLatin1String( "esriImageService" ) ) )
1408 {
1409 const QString name = serviceData.value( QStringLiteral( "name" ) ).toString();
1410 const QString description = serviceData.value( QStringLiteral( "description" ) ).toString();
1411 visitor( 0, 0, name, description, parentUrl, false, authid, format );
1412 }
1413 }
1414