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