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