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