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