QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
Loading...
Searching...
No Matches
qgsarcgisrestquery.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsarcgisrestquery.cpp
3 ----------------------
4 begin : December 2020
5 copyright : (C) 2020 by Nyall Dawson
6 email : nyall dot dawson at gmail dot 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 "qgsarcgisrestquery.h"
17
18#include "qgsapplication.h"
19#include "qgsarcgisrestutils.h"
20#include "qgsauthmanager.h"
22#include "qgslogger.h"
23#include "qgsmessagelog.h"
26#include "qgsvariantutils.h"
27
28#include <QCryptographicHash>
29#include <QFile>
30#include <QImageReader>
31#include <QJsonParseError>
32#include <QRegularExpression>
33#include <QString>
34#include <QUrl>
35#include <QUrlQuery>
36
37#include "moc_qgsarcgisrestquery.cpp"
38
39using namespace Qt::StringLiterals;
40
42 const QString &baseurl, const QString &authcfg, QString &errorTitle, QString &errorText, const QgsHttpHeaders &requestHeaders, const QString &urlPrefix, bool forceRefresh
43)
44{
45 // http://sampleserver5.arcgisonline.com/arcgis/rest/services/Energy/Geology/FeatureServer?f=json
46 QUrl queryUrl( baseurl );
47 QUrlQuery query( queryUrl );
48 query.addQueryItem( u"f"_s, u"json"_s );
49 queryUrl.setQuery( query );
50 return queryServiceJSON( queryUrl, authcfg, errorTitle, errorText, requestHeaders, nullptr, urlPrefix, forceRefresh );
51}
52
53QVariantMap QgsArcGisRestQueryUtils::getLayerInfo( const QString &layerurl, const QString &authcfg, QString &errorTitle, QString &errorText, const QgsHttpHeaders &requestHeaders, const QString &urlPrefix )
54{
55 // http://sampleserver5.arcgisonline.com/arcgis/rest/services/Energy/Geology/FeatureServer/1?f=json
56 QUrl queryUrl( layerurl );
57 QUrlQuery query( queryUrl );
58 query.addQueryItem( u"f"_s, u"json"_s );
59 queryUrl.setQuery( query );
60 return queryServiceJSON( queryUrl, authcfg, errorTitle, errorText, requestHeaders, nullptr, urlPrefix );
61}
62
64 const QString &layerurl, const QString &authcfg, QString &errorTitle, QString &errorText, const QgsHttpHeaders &requestHeaders, const QString &urlPrefix, const QgsRectangle &bbox, const QString &whereClause
65)
66{
67 // http://sampleserver5.arcgisonline.com/arcgis/rest/services/Energy/Geology/FeatureServer/1/query?where=1%3D1&returnIdsOnly=true&f=json
68 QUrl queryUrl( layerurl + "/query" );
69 QUrlQuery query( queryUrl );
70 query.addQueryItem( u"f"_s, u"json"_s );
71 query.addQueryItem( u"where"_s, whereClause.isEmpty() ? u"1=1"_s : whereClause );
72 query.addQueryItem( u"returnIdsOnly"_s, u"true"_s );
73 if ( !bbox.isNull() )
74 {
75 query.addQueryItem( u"geometry"_s, u"%1,%2,%3,%4"_s.arg( bbox.xMinimum(), 0, 'f', -1 ).arg( bbox.yMinimum(), 0, 'f', -1 ).arg( bbox.xMaximum(), 0, 'f', -1 ).arg( bbox.yMaximum(), 0, 'f', -1 ) );
76 query.addQueryItem( u"geometryType"_s, u"esriGeometryEnvelope"_s );
77 query.addQueryItem( u"spatialRel"_s, u"esriSpatialRelEnvelopeIntersects"_s );
78 }
79 queryUrl.setQuery( query );
80 return queryServiceJSON( queryUrl, authcfg, errorTitle, errorText, requestHeaders, nullptr, urlPrefix );
81}
82
83QgsRectangle QgsArcGisRestQueryUtils::getExtent( const QString &layerurl, const QString &whereClause, const QString &authcfg, const QgsHttpHeaders &requestHeaders, const QString &urlPrefix )
84{
85 // http://sampleserver5.arcgisonline.com/arcgis/rest/services/Energy/Geology/FeatureServer/1/query?where=1%3D1&returnExtentOnly=true&f=json
86 QUrl queryUrl( layerurl + "/query" );
87 QUrlQuery query( queryUrl );
88 query.addQueryItem( u"f"_s, u"json"_s );
89 query.addQueryItem( u"where"_s, whereClause );
90 query.addQueryItem( u"returnExtentOnly"_s, u"true"_s );
91 queryUrl.setQuery( query );
92 QString errorTitle;
93 QString errorText;
94 const QVariantMap res = queryServiceJSON( queryUrl, authcfg, errorTitle, errorText, requestHeaders, nullptr, urlPrefix );
95 if ( res.isEmpty() )
96 {
97 QgsDebugError( u"getExtent failed: %1 - %2"_s.arg( errorTitle, errorText ) );
98 return QgsRectangle();
99 }
100
101 return QgsArcGisRestUtils::convertRectangle( res.value( u"extent"_s ) );
102}
103
105 const QString &layerurl,
106 const QString &authcfg,
107 const QList<quint32> &objectIds,
108 const QString &crs,
109 bool fetchGeometry,
110 const QStringList &fetchAttributes,
111 bool fetchM,
112 bool fetchZ,
113 const QgsRectangle &filterRect,
114 QString &errorTitle,
115 QString &errorText,
116 const QgsHttpHeaders &requestHeaders,
117 QgsFeedback *feedback,
118 const QString &urlPrefix
119)
120{
121 QStringList ids;
122 for ( const int id : objectIds )
123 {
124 ids.append( QString::number( id ) );
125 }
126 QUrl queryUrl( layerurl + "/query" );
127 QUrlQuery query( queryUrl );
128 query.addQueryItem( u"f"_s, u"json"_s );
129 query.addQueryItem( u"objectIds"_s, ids.join( ','_L1 ) );
130 if ( !crs.isEmpty() && crs.contains( ':' ) )
131 {
132 const QString wkid = crs.indexOf( ':'_L1 ) >= 0 ? crs.split( ':' )[1] : QString();
133 query.addQueryItem( u"inSR"_s, wkid );
134 query.addQueryItem( u"outSR"_s, wkid );
135 }
136
137 query.addQueryItem( u"returnGeometry"_s, fetchGeometry ? u"true"_s : u"false"_s );
138
139 QString outFields;
140 if ( fetchAttributes.isEmpty() )
141 outFields = u"*"_s;
142 else
143 outFields = fetchAttributes.join( ',' );
144 query.addQueryItem( u"outFields"_s, outFields );
145
146 query.addQueryItem( u"returnM"_s, fetchM ? u"true"_s : u"false"_s );
147 query.addQueryItem( u"returnZ"_s, fetchZ ? u"true"_s : u"false"_s );
148 if ( !filterRect.isNull() )
149 {
150 query.addQueryItem( u"geometry"_s, u"%1,%2,%3,%4"_s.arg( filterRect.xMinimum(), 0, 'f', -1 ).arg( filterRect.yMinimum(), 0, 'f', -1 ).arg( filterRect.xMaximum(), 0, 'f', -1 ).arg( filterRect.yMaximum(), 0, 'f', -1 ) );
151 query.addQueryItem( u"geometryType"_s, u"esriGeometryEnvelope"_s );
152 query.addQueryItem( u"spatialRel"_s, u"esriSpatialRelEnvelopeIntersects"_s );
153 }
154 queryUrl.setQuery( query );
155 return queryServiceJSON( queryUrl, authcfg, errorTitle, errorText, requestHeaders, feedback, urlPrefix );
156}
157
159 const QString &layerurl,
160 const QgsRectangle &filterRect,
161 QString &errorTitle,
162 QString &errorText,
163 const QString &authcfg,
164 const QgsHttpHeaders &requestHeaders,
165 QgsFeedback *feedback,
166 const QString &whereClause,
167 const QString &urlPrefix
168)
169{
170 QUrl queryUrl( layerurl + "/query" );
171 QUrlQuery query( queryUrl );
172 query.addQueryItem( u"f"_s, u"json"_s );
173 query.addQueryItem( u"where"_s, whereClause.isEmpty() ? u"1=1"_s : whereClause );
174 query.addQueryItem( u"returnIdsOnly"_s, u"true"_s );
175 query.addQueryItem( u"geometry"_s, u"%1,%2,%3,%4"_s.arg( filterRect.xMinimum(), 0, 'f', -1 ).arg( filterRect.yMinimum(), 0, 'f', -1 ).arg( filterRect.xMaximum(), 0, 'f', -1 ).arg( filterRect.yMaximum(), 0, 'f', -1 ) );
176 query.addQueryItem( u"geometryType"_s, u"esriGeometryEnvelope"_s );
177 query.addQueryItem( u"spatialRel"_s, u"esriSpatialRelEnvelopeIntersects"_s );
178 queryUrl.setQuery( query );
179 const QVariantMap objectIdData = queryServiceJSON( queryUrl, authcfg, errorTitle, errorText, requestHeaders, feedback, urlPrefix );
180
181 if ( objectIdData.isEmpty() )
182 {
183 return QList<quint32>();
184 }
185
186 QList<quint32> ids;
187 const QVariantList objectIdsList = objectIdData[u"objectIds"_s].toList();
188 ids.reserve( objectIdsList.size() );
189 for ( const QVariant &objectId : objectIdsList )
190 {
191 ids << objectId.toInt();
192 }
193 return ids;
194}
195
197 const QUrl &u, const QString &authcfg, QString &errorTitle, QString &errorText, const QgsHttpHeaders &requestHeaders, QgsFeedback *feedback, QString *contentType, const QString &urlPrefix, bool forceRefresh
198)
199{
200 QUrl url = parseUrl( u );
201
202 if ( !urlPrefix.isEmpty() )
203 url = QUrl( urlPrefix + url.toString() );
204
205 QNetworkRequest request( url );
206 QgsSetRequestInitiatorClass( request, u"QgsArcGisRestUtils"_s );
207 requestHeaders.updateNetworkRequest( request );
208
209 QgsBlockingNetworkRequest networkRequest;
210 networkRequest.setAuthCfg( authcfg );
211 const QgsBlockingNetworkRequest::ErrorCode error = networkRequest.get( request, forceRefresh, feedback );
212
213 if ( feedback && feedback->isCanceled() )
214 return QByteArray();
215
216 // Handle network errors
218 {
219 QgsDebugError( u"Network error: %1"_s.arg( networkRequest.errorMessage() ) );
220 errorTitle = u"Network error"_s;
221 errorText = networkRequest.errorMessage();
222
223 // try to get detailed error message from reply
224 const QString content = networkRequest.reply().content();
225 const thread_local QRegularExpression errorRx( u"Error: <.*?>(.*?)<"_s );
226 const QRegularExpressionMatch match = errorRx.match( content );
227 if ( match.hasMatch() )
228 {
229 errorText = match.captured( 1 );
230 }
231
232 return QByteArray();
233 }
234
235 const QgsNetworkReplyContent content = networkRequest.reply();
236 if ( contentType )
237 *contentType = content.rawHeader( "Content-Type" );
238 return content.content();
239}
240
242 const QUrl &url, const QString &authcfg, QString &errorTitle, QString &errorText, const QgsHttpHeaders &requestHeaders, QgsFeedback *feedback, const QString &urlPrefix, bool forceRefresh
243)
244{
245 const QByteArray reply = queryService( url, authcfg, errorTitle, errorText, requestHeaders, feedback, nullptr, urlPrefix, forceRefresh );
246 if ( !errorTitle.isEmpty() )
247 {
248 return QVariantMap();
249 }
250 if ( feedback && feedback->isCanceled() )
251 return QVariantMap();
252
253 // Parse data
254 QJsonParseError err;
255 const QJsonDocument doc = QJsonDocument::fromJson( reply, &err );
256 if ( doc.isNull() )
257 {
258 errorTitle = u"Parsing error"_s;
259 errorText = err.errorString();
260 QgsDebugError( u"Parsing error: %1"_s.arg( err.errorString() ) );
261 return QVariantMap();
262 }
263 const QVariantMap res = doc.object().toVariantMap();
264 if ( res.contains( u"error"_s ) )
265 {
266 const QVariantMap error = res.value( u"error"_s ).toMap();
267 errorText = error.value( u"message"_s ).toString();
268 errorTitle = QObject::tr( "Error %1" ).arg( error.value( u"code"_s ).toString() );
269 return QVariantMap();
270 }
271 return res;
272}
273
274QUrl QgsArcGisRestQueryUtils::parseUrl( const QUrl &url, bool *isTestEndpoint )
275{
276 if ( isTestEndpoint )
277 *isTestEndpoint = false;
278
279 QUrl modifiedUrl( url );
280 if ( modifiedUrl.toString().contains( "fake_qgis_http_endpoint"_L1 ) )
281 {
282 if ( isTestEndpoint )
283 *isTestEndpoint = true;
284
285 // Just for testing with local files instead of http:// resources
286 QString modifiedUrlString = modifiedUrl.toString();
287 // Qt5 does URL encoding from some reason (of the FILTER parameter for example)
288 modifiedUrlString = QUrl::fromPercentEncoding( modifiedUrlString.toUtf8() );
289 modifiedUrlString.replace( "fake_qgis_http_endpoint/"_L1, "fake_qgis_http_endpoint_"_L1 );
290 QgsDebugMsgLevel( u"Get %1"_s.arg( modifiedUrlString ), 2 );
291 modifiedUrlString = modifiedUrlString.mid( u"http://"_s.size() );
292 QString args = modifiedUrlString.indexOf( '?' ) >= 0 ? modifiedUrlString.mid( modifiedUrlString.indexOf( '?' ) ) : QString();
293 if ( modifiedUrlString.size() > 150 )
294 {
295 args = QCryptographicHash::hash( args.toUtf8(), QCryptographicHash::Md5 ).toHex();
296 }
297 else
298 {
299 args.replace( "?"_L1, "_"_L1 );
300 args.replace( "&"_L1, "_"_L1 );
301 args.replace( "<"_L1, "_"_L1 );
302 args.replace( ">"_L1, "_"_L1 );
303 args.replace( "'"_L1, "_"_L1 );
304 args.replace( "\""_L1, "_"_L1 );
305 args.replace( " "_L1, "_"_L1 );
306 args.replace( ":"_L1, "_"_L1 );
307 args.replace( "/"_L1, "_"_L1 );
308 args.replace( "\n"_L1, "_"_L1 );
309 }
310#ifdef Q_OS_WIN
311 // Passing "urls" like "http://c:/path" to QUrl 'eats' the : after c,
312 // so we must restore it
313 if ( modifiedUrlString[1] == '/' )
314 {
315 modifiedUrlString = modifiedUrlString[0] + ":/" + modifiedUrlString.mid( 2 );
316 }
317#endif
318 modifiedUrlString = modifiedUrlString.mid( 0, modifiedUrlString.indexOf( '?' ) ) + args;
319 QgsDebugMsgLevel( u"Get %1 (after laundering)"_s.arg( modifiedUrlString ), 2 );
320 modifiedUrl = QUrl::fromLocalFile( modifiedUrlString );
321 if ( !QFile::exists( modifiedUrlString ) )
322 {
323 QgsDebugError( u"Local test file %1 for URL %2 does not exist!!!"_s.arg( modifiedUrlString, url.toString() ) );
324 }
325 }
326
327 return modifiedUrl;
328}
329
330void QgsArcGisRestQueryUtils::adjustBaseUrl( QString &baseUrl, const QString &name )
331{
332 const QStringList parts = name.split( '/' );
333 QString checkString;
334 for ( const QString &part : parts )
335 {
336 if ( !checkString.isEmpty() )
337 checkString += QString( '/' );
338
339 checkString += part;
340 if ( baseUrl.indexOf( QRegularExpression( checkString.replace( '/', "\\/"_L1 ) + u"\\/?$"_s ) ) > -1 )
341 {
342 baseUrl = baseUrl.left( baseUrl.length() - checkString.length() - 1 );
343 break;
344 }
345 }
346}
347
348void QgsArcGisRestQueryUtils::visitFolderItems( const std::function< void( const QString &, const QString & ) > &visitor, const QVariantMap &serviceData, const QString &baseUrl )
349{
350 QString base( baseUrl );
351 bool baseChecked = false;
352 if ( !base.endsWith( '/' ) )
353 base += '/'_L1;
354
355 const QStringList folderList = serviceData.value( u"folders"_s ).toStringList();
356 for ( const QString &folder : folderList )
357 {
358 if ( !baseChecked )
359 {
360 adjustBaseUrl( base, folder );
361 baseChecked = true;
362 }
363 visitor( folder, base + folder );
364 }
365}
366
367void QgsArcGisRestQueryUtils::visitServiceItems( const std::function<void( const QString &, const QString &, Qgis::ArcGisRestServiceType )> &visitor, const QVariantMap &serviceData, const QString &baseUrl )
368{
369 QString base( baseUrl );
370 bool baseChecked = false;
371 if ( !base.endsWith( '/' ) )
372 base += '/'_L1;
373
374 const QVariantList serviceList = serviceData.value( u"services"_s ).toList();
375 for ( const QVariant &service : serviceList )
376 {
377 const QVariantMap serviceMap = service.toMap();
378 const QString serviceTypeString = serviceMap.value( u"type"_s ).toString();
379 const Qgis::ArcGisRestServiceType serviceType = QgsArcGisRestUtils::serviceTypeFromString( serviceTypeString );
380
381 switch ( serviceType )
382 {
387 // supported
388 break;
389
394 // unsupported
395 continue;
396 }
397
398 const QString serviceName = serviceMap.value( u"name"_s ).toString();
399 const QString displayName = serviceName.split( '/' ).last();
400 if ( !baseChecked )
401 {
402 adjustBaseUrl( base, serviceName );
403 baseChecked = true;
404 }
405
406 visitor( displayName, base + serviceName + '/' + serviceTypeString, serviceType );
407 }
408}
409
411 const std::function<
412 void( const QString &, ServiceTypeFilter, Qgis::GeometryType, const QString &, const QString &, const QString &, const QString &, bool, const QgsCoordinateReferenceSystem &, const QString & )> &visitor,
413 const QVariantMap &serviceData,
414 const QString &parentUrl,
415 const QString &parentSupportedFormats,
416 const ServiceTypeFilter filter
417)
418{
419 const QgsCoordinateReferenceSystem crs = QgsArcGisRestUtils::convertSpatialReference( serviceData.value( u"spatialReference"_s ).toMap() );
420
421 bool found = false;
422 const QList<QByteArray> supportedFormats = QImageReader::supportedImageFormats();
423 const QStringList supportedImageFormatTypes = serviceData.value( u"supportedImageFormatTypes"_s ).toString().isEmpty() ? parentSupportedFormats.split( ',' )
424 : serviceData.value( u"supportedImageFormatTypes"_s ).toString().split( ',' );
425 QString format = supportedImageFormatTypes.value( 0 );
426 for ( const QString &encoding : supportedImageFormatTypes )
427 {
428 for ( const QByteArray &fmt : supportedFormats )
429 {
430 if ( encoding.startsWith( fmt, Qt::CaseInsensitive ) )
431 {
432 format = encoding;
433 found = true;
434 break;
435 }
436 }
437 if ( found )
438 break;
439 }
440 const QStringList capabilities = serviceData.value( u"capabilities"_s ).toString().split( ',' );
441
442 // If the requested layer type is vector, do not show raster-only layers (i.e. non query-able layers)
443 const bool serviceMayHaveQueryCapability = capabilities.contains( u"Query"_s ) || serviceData.value( u"serviceDataType"_s ).toString().startsWith( "esriImageService"_L1 );
444
445 const bool serviceMayRenderMaps = capabilities.contains( u"Map"_s ) || serviceData.value( u"serviceDataType"_s ).toString().startsWith( "esriImageService"_L1 );
446
447 const QVariantList layerInfoList = serviceData.value( u"layers"_s ).toList();
448 for ( const QVariant &layerInfo : layerInfoList )
449 {
450 const QVariantMap layerInfoMap = layerInfo.toMap();
451 const QString id = layerInfoMap.value( u"id"_s ).toString();
452 const QString parentLayerId = layerInfoMap.value( u"parentLayerId"_s ).toString();
453 const QString name = layerInfoMap.value( u"name"_s ).toString();
454 const QString description = layerInfoMap.value( u"description"_s ).toString();
455
456 if ( filter == ServiceTypeFilter::Scene )
457 {
458 visitor( parentLayerId, ServiceTypeFilter::Scene, Qgis::GeometryType::Unknown, id, name, description, parentUrl, false, crs, format );
459 continue;
460 }
461
462 // Yes, potentially we may visit twice, once as as a raster (if applicable), and once as a vector (if applicable)!
463 if ( serviceMayRenderMaps && ( filter == ServiceTypeFilter::Raster || filter == ServiceTypeFilter::AllTypes ) )
464 {
465 if ( !layerInfoMap.value( u"subLayerIds"_s ).toList().empty() )
466 {
467 visitor( parentLayerId, ServiceTypeFilter::Raster, Qgis::GeometryType::Unknown, id, name, description, parentUrl + '/' + id, true, QgsCoordinateReferenceSystem(), format );
468 }
469 else
470 {
471 visitor( parentLayerId, ServiceTypeFilter::Raster, Qgis::GeometryType::Unknown, id, name, description, parentUrl + '/' + id, false, crs, format );
472 }
473 }
474
475 if ( serviceMayHaveQueryCapability && ( filter == ServiceTypeFilter::Vector || filter == ServiceTypeFilter::AllTypes ) )
476 {
477 const QString geometryType = layerInfoMap.value( u"geometryType"_s ).toString();
478#if 0
479 // we have a choice here -- if geometryType is unknown and the service reflects that it supports Map capabilities,
480 // then we can't be sure whether or not the individual sublayers support Query or Map requests only. So we either:
481 // 1. Send off additional requests for each individual layer's capabilities (too expensive)
482 // 2. Err on the side of only showing services we KNOW will work for layer -- but this has the side effect that layers
483 // which ARE available as feature services will only show as raster mapserver layers, which is VERY bad/restrictive
484 // 3. Err on the side of showing services we THINK may work, even though some of them may or may not work depending on the actual
485 // server configuration
486 // We opt for 3, because otherwise we're making it impossible for users to load valid vector layers into QGIS
487
488 if ( serviceMayRenderMaps )
489 {
490 if ( geometryType.isEmpty() )
491 continue;
492 }
493#endif
494
495 const Qgis::WkbType wkbType = QgsArcGisRestUtils::convertGeometryType( geometryType );
496
497
498 if ( !layerInfoMap.value( u"subLayerIds"_s ).toList().empty() )
499 {
500 visitor( parentLayerId, ServiceTypeFilter::Vector, QgsWkbTypes::geometryType( wkbType ), id, name, description, parentUrl + '/' + id, true, QgsCoordinateReferenceSystem(), format );
501 }
502 else
503 {
504 visitor( parentLayerId, ServiceTypeFilter::Vector, QgsWkbTypes::geometryType( wkbType ), id, name, description, parentUrl + '/' + id, false, crs, format );
505 }
506 }
507 }
508
509 const QVariantList tableInfoList = serviceData.value( u"tables"_s ).toList();
510 for ( const QVariant &tableInfo : tableInfoList )
511 {
512 const QVariantMap tableInfoMap = tableInfo.toMap();
513 const QString id = tableInfoMap.value( u"id"_s ).toString();
514 const QString parentLayerId = tableInfoMap.value( u"parentLayerId"_s ).toString();
515 const QString name = tableInfoMap.value( u"name"_s ).toString();
516 const QString description = tableInfoMap.value( u"description"_s ).toString();
517
518 if ( serviceMayHaveQueryCapability && ( filter == ServiceTypeFilter::Vector || filter == ServiceTypeFilter::AllTypes ) )
519 {
520 if ( !tableInfoMap.value( u"subLayerIds"_s ).toList().empty() )
521 {
522 visitor( parentLayerId, ServiceTypeFilter::Vector, Qgis::GeometryType::Null, id, name, description, parentUrl + '/' + id, true, QgsCoordinateReferenceSystem(), format );
523 }
524 else
525 {
526 visitor( parentLayerId, ServiceTypeFilter::Vector, Qgis::GeometryType::Null, id, name, description, parentUrl + '/' + id, false, crs, format );
527 }
528 }
529 }
530
531 // Add root MapServer as raster layer when multiple layers are listed
532 if ( filter != ServiceTypeFilter::Vector && layerInfoList.count() > 1 && serviceData.contains( u"supportedImageFormatTypes"_s ) )
533 {
534 const QString name = u"(%1)"_s.arg( QObject::tr( "All layers" ) );
535 const QString description = serviceData.value( u"Comments"_s ).toString();
536 visitor( nullptr, ServiceTypeFilter::Raster, Qgis::GeometryType::Unknown, nullptr, name, description, parentUrl, false, crs, format );
537 }
538
539 // Add root ImageServer as layer
540 if ( serviceData.value( u"serviceDataType"_s ).toString().startsWith( "esriImageService"_L1 ) )
541 {
542 const QString name = serviceData.value( u"name"_s ).toString();
543 const QString description = serviceData.value( u"description"_s ).toString();
544 visitor( nullptr, ServiceTypeFilter::Raster, Qgis::GeometryType::Unknown, nullptr, name, description, parentUrl, false, crs, format );
545 }
546}
547
548
550
551//
552// QgsArcGisAsyncQuery
553//
554
555QgsArcGisAsyncQuery::QgsArcGisAsyncQuery( QObject *parent )
556 : QObject( parent )
557{}
558
559QgsArcGisAsyncQuery::~QgsArcGisAsyncQuery()
560{
561 if ( mReply )
562 mReply->deleteLater();
563}
564
565void QgsArcGisAsyncQuery::start( const QUrl &url, const QString &authCfg, QByteArray *result, bool allowCache, const QgsHttpHeaders &headers, const QString &urlPrefix )
566{
567 mResult = result;
568 QUrl mUrl = url;
569 if ( !urlPrefix.isEmpty() )
570 mUrl = QUrl( urlPrefix + url.toString() );
571 QNetworkRequest request( mUrl );
572
573 headers.updateNetworkRequest( request );
574
575 if ( !authCfg.isEmpty() && !QgsApplication::authManager()->updateNetworkRequest( request, authCfg ) )
576 {
577 const QString error = tr( "network request update failed for authentication config" );
578 emit failed( u"Network"_s, error );
579 return;
580 }
581
582 QgsSetRequestInitiatorClass( request, u"QgsArcGisAsyncQuery"_s );
583 request.setAttribute( QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::ManualRedirectPolicy );
584 if ( allowCache )
585 {
586 request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
587 request.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
588 }
589
590 mReply = QgsNetworkAccessManager::instance()->get( request );
591 connect( mReply, &QNetworkReply::finished, this, &QgsArcGisAsyncQuery::handleReply );
592}
593
594void QgsArcGisAsyncQuery::handleReply()
595{
596 mReply->deleteLater();
597 // Handle network errors
598 if ( mReply->error() != QNetworkReply::NoError )
599 {
600 QgsDebugError( u"Network error: %1"_s.arg( mReply->errorString() ) );
601 emit failed( u"Network error"_s, mReply->errorString() );
602 return;
603 }
604
605 // Handle HTTP redirects
606 const QVariant redirect = mReply->attribute( QNetworkRequest::RedirectionTargetAttribute );
607 if ( !QgsVariantUtils::isNull( redirect ) )
608 {
609 QNetworkRequest request = mReply->request();
610 request.setAttribute( QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::ManualRedirectPolicy );
611 QgsSetRequestInitiatorClass( request, u"QgsArcGisAsyncQuery"_s );
612 QgsDebugMsgLevel( "redirecting to " + redirect.toUrl().toString(), 2 );
613 request.setUrl( redirect.toUrl() );
614 mReply = QgsNetworkAccessManager::instance()->get( request );
615 connect( mReply, &QNetworkReply::finished, this, &QgsArcGisAsyncQuery::handleReply );
616 return;
617 }
618
619 *mResult = mReply->readAll();
620 mResult = nullptr;
621 emit finished();
622}
623
624//
625// QgsArcGisAsyncParallelQuery
626//
627
628QgsArcGisAsyncParallelQuery::QgsArcGisAsyncParallelQuery( const QString &authcfg, const QgsHttpHeaders &requestHeaders, QObject *parent )
629 : QObject( parent )
630 , mAuthCfg( authcfg )
631 , mRequestHeaders( requestHeaders )
632{}
633
634void QgsArcGisAsyncParallelQuery::start( const QVector<QUrl> &urls, QVector<QByteArray> *results, bool allowCache )
635{
636 Q_ASSERT( results->size() == urls.size() );
637 mResults = results;
638 mPendingRequests = mResults->size();
639 for ( int i = 0, n = urls.size(); i < n; ++i )
640 {
641 QNetworkRequest request( urls[i] );
642 QgsSetRequestInitiatorClass( request, u"QgsArcGisAsyncParallelQuery"_s );
643 QgsSetRequestInitiatorId( request, QString::number( i ) );
644
645 mRequestHeaders.updateNetworkRequest( request );
646 if ( !mAuthCfg.isEmpty() && !QgsApplication::authManager()->updateNetworkRequest( request, mAuthCfg ) )
647 {
648 const QString error = tr( "network request update failed for authentication config" );
649 mErrors.append( error );
650 QgsMessageLog::logMessage( error, tr( "Network" ) );
651 continue;
652 }
653
654 request.setAttribute( QNetworkRequest::HttpPipeliningAllowedAttribute, true );
655 if ( allowCache )
656 {
657 request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
658 request.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
659 request.setRawHeader( "Connection", "keep-alive" );
660 }
661 QNetworkReply *reply = QgsNetworkAccessManager::instance()->get( request );
662 reply->setProperty( "idx", i );
663 connect( reply, &QNetworkReply::finished, this, &QgsArcGisAsyncParallelQuery::handleReply );
664 }
665}
666
667void QgsArcGisAsyncParallelQuery::handleReply()
668{
669 QNetworkReply *reply = qobject_cast<QNetworkReply *>( QObject::sender() );
670 const QVariant redirect = reply->attribute( QNetworkRequest::RedirectionTargetAttribute );
671 const int idx = reply->property( "idx" ).toInt();
672 reply->deleteLater();
673 if ( reply->error() != QNetworkReply::NoError )
674 {
675 // Handle network errors
676 mErrors.append( reply->errorString() );
677 --mPendingRequests;
678 }
679 else if ( !QgsVariantUtils::isNull( redirect ) )
680 {
681 // Handle HTTP redirects
682 QNetworkRequest request = reply->request();
683 request.setAttribute( QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::ManualRedirectPolicy );
684 QgsSetRequestInitiatorClass( request, u"QgsArcGisAsyncParallelQuery"_s );
685 QgsDebugMsgLevel( "redirecting to " + redirect.toUrl().toString(), 2 );
686 request.setUrl( redirect.toUrl() );
687 reply = QgsNetworkAccessManager::instance()->get( request );
688 reply->setProperty( "idx", idx );
689 connect( reply, &QNetworkReply::finished, this, &QgsArcGisAsyncParallelQuery::handleReply );
690 }
691 else
692 {
693 // All OK
694 ( *mResults )[idx] = reply->readAll();
695 --mPendingRequests;
696 }
697 if ( mPendingRequests == 0 )
698 {
699 emit finished( mErrors );
700 mResults = nullptr;
701 mErrors.clear();
702 }
703}
704
ArcGisRestServiceType
Available ArcGIS REST service types.
Definition qgis.h:4515
@ GeocodeServer
GeocodeServer.
Definition qgis.h:4521
@ SceneServer
SceneServer.
Definition qgis.h:4523
@ Unknown
Other unknown/unsupported type.
Definition qgis.h:4522
@ GlobeServer
GlobeServer.
Definition qgis.h:4519
@ ImageServer
ImageServer.
Definition qgis.h:4518
@ FeatureServer
FeatureServer.
Definition qgis.h:4516
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
Definition qgis.h:379
@ Unknown
Unknown types.
Definition qgis.h:383
@ Null
No geometry.
Definition qgis.h:384
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:294
static QgsAuthManager * authManager()
Returns the application's authentication manager instance.
static void visitFolderItems(const std::function< void(const QString &folderName, const QString &url)> &visitor, const QVariantMap &serviceData, const QString &baseUrl)
Calls the specified visitor function on all folder items found within the given service data.
static QVariantMap queryServiceJSON(const QUrl &url, const QString &authcfg, QString &errorTitle, QString &errorText, const QgsHttpHeaders &requestHeaders=QgsHttpHeaders(), QgsFeedback *feedback=nullptr, const QString &urlPrefix=QString(), bool forceRefresh=false)
Performs a blocking request to a URL and returns the retrieved JSON content.
static QVariantMap 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 QgsHttpHeaders &requestHeaders=QgsHttpHeaders(), QgsFeedback *feedback=nullptr, const QString &urlPrefix=QString())
Retrieves all matching objects from the specified layer URL.
static QgsRectangle getExtent(const QString &layerurl, const QString &whereClause, const QString &authcfg, const QgsHttpHeaders &requestHeaders=QgsHttpHeaders(), const QString &urlPrefix=QString())
Retrieves the extent for the features matching a whereClause.
static QVariantMap getObjectIds(const QString &layerurl, const QString &authcfg, QString &errorTitle, QString &errorText, const QgsHttpHeaders &requestHeaders=QgsHttpHeaders(), const QString &urlPrefix=QString(), const QgsRectangle &bbox=QgsRectangle(), const QString &whereClause=QString())
Retrieves all object IDs for the specified layer URL.
static QUrl parseUrl(const QUrl &url, bool *isTestEndpoint=nullptr)
Parses and processes a url.
static QByteArray queryService(const QUrl &url, const QString &authcfg, QString &errorTitle, QString &errorText, const QgsHttpHeaders &requestHeaders=QgsHttpHeaders(), QgsFeedback *feedback=nullptr, QString *contentType=nullptr, const QString &urlPrefix=QString(), bool forceRefresh=false)
Performs a blocking request to a URL and returns the retrieved data.
static QVariantMap getServiceInfo(const QString &baseurl, const QString &authcfg, QString &errorTitle, QString &errorText, const QgsHttpHeaders &requestHeaders=QgsHttpHeaders(), const QString &urlPrefix=QString(), bool forceRefresh=false)
Retrieves JSON service info for the specified base URL.
static QVariantMap getLayerInfo(const QString &layerurl, const QString &authcfg, QString &errorTitle, QString &errorText, const QgsHttpHeaders &requestHeaders=QgsHttpHeaders(), const QString &urlPrefix=QString())
Retrieves JSON layer info for the specified layer URL.
static void addLayerItems(const std::function< void(const QString &parentLayerId, ServiceTypeFilter serviceType, Qgis::GeometryType geometryType, const QString &layerId, const QString &name, const QString &description, const QString &url, bool isParentLayer, const QgsCoordinateReferenceSystem &crs, const QString &format)> &visitor, const QVariantMap &serviceData, const QString &parentUrl, const QString &parentSupportedFormats, const ServiceTypeFilter filter=ServiceTypeFilter::AllTypes)
Calls the specified visitor function on all layer items found within the given service data.
static void visitServiceItems(const std::function< void(const QString &serviceName, const QString &url, Qgis::ArcGisRestServiceType serviceType)> &visitor, const QVariantMap &serviceData, const QString &baseUrl)
Calls the specified visitor function on all service items found within the given service data.
static QList< quint32 > getObjectIdsByExtent(const QString &layerurl, const QgsRectangle &filterRect, QString &errorTitle, QString &errorText, const QString &authcfg, const QgsHttpHeaders &requestHeaders=QgsHttpHeaders(), QgsFeedback *feedback=nullptr, const QString &whereClause=QString(), const QString &urlPrefix=QString())
Gets a list of object IDs which fall within the specified extent.
static QgsCoordinateReferenceSystem convertSpatialReference(const QVariantMap &spatialReferenceMap)
Converts a spatial reference JSON definition to a QgsCoordinateReferenceSystem value.
static Qgis::WkbType convertGeometryType(const QString &type)
Converts an ESRI REST geometry type to a WKB type.
static Qgis::ArcGisRestServiceType serviceTypeFromString(const QString &type)
Converts a string value to a REST service type.
static QgsRectangle convertRectangle(const QVariant &value)
Converts a rectangle value to a QgsRectangle.
A thread safe class for performing blocking (sync) network requests, with full support for QGIS proxy...
void setAuthCfg(const QString &authCfg)
Sets the authentication config id which should be used during the request.
QString errorMessage() const
Returns the error message string, after a get(), post(), head() or put() request has been made.
ErrorCode get(QNetworkRequest &request, bool forceRefresh=false, QgsFeedback *feedback=nullptr, RequestFlags requestFlags=QgsBlockingNetworkRequest::RequestFlags())
Performs a "get" operation on the specified request.
@ NoError
No error was encountered.
QgsNetworkReplyContent reply() const
Returns the content of the network reply, after a get(), post(), head() or put() request has been mad...
Represents a coordinate reference system (CRS).
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition qgsfeedback.h:44
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:56
Implements simple HTTP header management.
bool updateNetworkRequest(QNetworkRequest &request) const
Updates a request by adding all the HTTP headers.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE(), Qgis::StringFormat format=Qgis::StringFormat::PlainText)
Adds a message to the log instance (and creates it if necessary).
static QgsNetworkAccessManager * instance(Qt::ConnectionType connectionType=Qt::BlockingQueuedConnection)
Returns a pointer to the active QgsNetworkAccessManager for the current thread.
Encapsulates a network reply within a container which is inexpensive to copy and safe to pass between...
QByteArray content() const
Returns the reply content.
QByteArray rawHeader(const QByteArray &headerName) const
Returns the content of the header with the specified headerName, or an empty QByteArray if the specif...
A rectangle specified with double values.
double xMinimum
double yMinimum
double xMaximum
double yMaximum
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
static Qgis::GeometryType geometryType(Qgis::WkbType type)
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63
#define QgsDebugError(str)
Definition qgslogger.h:59
#define QgsSetRequestInitiatorClass(request, _class)
#define QgsSetRequestInitiatorId(request, str)