Branch data Line data Source code
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"
18 : : #include "qgsblockingnetworkrequest.h"
19 : : #include "qgsnetworkaccessmanager.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 : 0 : 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 : 0 : QUrl queryUrl( baseurl );
34 : 0 : QUrlQuery query( queryUrl );
35 : 0 : query.addQueryItem( QStringLiteral( "f" ), QStringLiteral( "json" ) );
36 : 0 : queryUrl.setQuery( query );
37 : 0 : return queryServiceJSON( queryUrl, authcfg, errorTitle, errorText, requestHeaders );
38 : 0 : }
39 : :
40 : 0 : 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 : 0 : QUrl queryUrl( layerurl );
44 : 0 : QUrlQuery query( queryUrl );
45 : 0 : query.addQueryItem( QStringLiteral( "f" ), QStringLiteral( "json" ) );
46 : 0 : queryUrl.setQuery( query );
47 : 0 : return queryServiceJSON( queryUrl, authcfg, errorTitle, errorText, requestHeaders );
48 : 0 : }
49 : :
50 : 0 : 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 : 0 : QUrl queryUrl( layerurl + "/query" );
54 : 0 : QUrlQuery query( queryUrl );
55 : 0 : query.addQueryItem( QStringLiteral( "f" ), QStringLiteral( "json" ) );
56 : 0 : query.addQueryItem( QStringLiteral( "where" ), QStringLiteral( "1=1" ) );
57 : 0 : query.addQueryItem( QStringLiteral( "returnIdsOnly" ), QStringLiteral( "true" ) );
58 : 0 : if ( !bbox.isNull() )
59 : : {
60 : 0 : query.addQueryItem( QStringLiteral( "geometry" ), QStringLiteral( "%1,%2,%3,%4" )
61 : 0 : .arg( bbox.xMinimum(), 0, 'f', -1 ).arg( bbox.yMinimum(), 0, 'f', -1 )
62 : 0 : .arg( bbox.xMaximum(), 0, 'f', -1 ).arg( bbox.yMaximum(), 0, 'f', -1 ) );
63 : 0 : query.addQueryItem( QStringLiteral( "geometryType" ), QStringLiteral( "esriGeometryEnvelope" ) );
64 : 0 : query.addQueryItem( QStringLiteral( "spatialRel" ), QStringLiteral( "esriSpatialRelEnvelopeIntersects" ) );
65 : 0 : }
66 : 0 : queryUrl.setQuery( query );
67 : 0 : return queryServiceJSON( queryUrl, authcfg, errorTitle, errorText, requestHeaders );
68 : 0 : }
69 : :
70 : 0 : 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 : 0 : QStringList ids;
77 : 0 : for ( int id : objectIds )
78 : : {
79 : 0 : ids.append( QString::number( id ) );
80 : : }
81 : 0 : QUrl queryUrl( layerurl + "/query" );
82 : 0 : QUrlQuery query( queryUrl );
83 : 0 : query.addQueryItem( QStringLiteral( "f" ), QStringLiteral( "json" ) );
84 : 0 : query.addQueryItem( QStringLiteral( "objectIds" ), ids.join( QLatin1Char( ',' ) ) );
85 : 0 : QString wkid = crs.indexOf( QLatin1Char( ':' ) ) >= 0 ? crs.split( ':' )[1] : QString();
86 : 0 : query.addQueryItem( QStringLiteral( "inSR" ), wkid );
87 : 0 : query.addQueryItem( QStringLiteral( "outSR" ), wkid );
88 : :
89 : 0 : query.addQueryItem( QStringLiteral( "returnGeometry" ), fetchGeometry ? QStringLiteral( "true" ) : QStringLiteral( "false" ) );
90 : :
91 : 0 : QString outFields;
92 : 0 : if ( fetchAttributes.isEmpty() )
93 : 0 : outFields = QStringLiteral( "*" );
94 : : else
95 : 0 : outFields = fetchAttributes.join( ',' );
96 : 0 : query.addQueryItem( QStringLiteral( "outFields" ), outFields );
97 : :
98 : 0 : query.addQueryItem( QStringLiteral( "returnM" ), fetchM ? QStringLiteral( "true" ) : QStringLiteral( "false" ) );
99 : 0 : query.addQueryItem( QStringLiteral( "returnZ" ), fetchZ ? QStringLiteral( "true" ) : QStringLiteral( "false" ) );
100 : 0 : if ( !filterRect.isNull() )
101 : : {
102 : 0 : query.addQueryItem( QStringLiteral( "geometry" ), QStringLiteral( "%1,%2,%3,%4" )
103 : 0 : .arg( filterRect.xMinimum(), 0, 'f', -1 ).arg( filterRect.yMinimum(), 0, 'f', -1 )
104 : 0 : .arg( filterRect.xMaximum(), 0, 'f', -1 ).arg( filterRect.yMaximum(), 0, 'f', -1 ) );
105 : 0 : query.addQueryItem( QStringLiteral( "geometryType" ), QStringLiteral( "esriGeometryEnvelope" ) );
106 : 0 : query.addQueryItem( QStringLiteral( "spatialRel" ), QStringLiteral( "esriSpatialRelEnvelopeIntersects" ) );
107 : 0 : }
108 : 0 : queryUrl.setQuery( query );
109 : 0 : return queryServiceJSON( queryUrl, authcfg, errorTitle, errorText, requestHeaders, feedback );
110 : 0 : }
111 : :
112 : 0 : QList<quint32> QgsArcGisRestQueryUtils::getObjectIdsByExtent( const QString &layerurl, const QgsRectangle &filterRect, QString &errorTitle, QString &errorText, const QString &authcfg, const QgsStringMap &requestHeaders, QgsFeedback *feedback )
113 : : {
114 : 0 : QUrl queryUrl( layerurl + "/query" );
115 : 0 : QUrlQuery query( queryUrl );
116 : 0 : query.addQueryItem( QStringLiteral( "f" ), QStringLiteral( "json" ) );
117 : 0 : query.addQueryItem( QStringLiteral( "where" ), QStringLiteral( "1=1" ) );
118 : 0 : query.addQueryItem( QStringLiteral( "returnIdsOnly" ), QStringLiteral( "true" ) );
119 : 0 : query.addQueryItem( QStringLiteral( "geometry" ), QStringLiteral( "%1,%2,%3,%4" )
120 : 0 : .arg( filterRect.xMinimum(), 0, 'f', -1 ).arg( filterRect.yMinimum(), 0, 'f', -1 )
121 : 0 : .arg( filterRect.xMaximum(), 0, 'f', -1 ).arg( filterRect.yMaximum(), 0, 'f', -1 ) );
122 : 0 : query.addQueryItem( QStringLiteral( "geometryType" ), QStringLiteral( "esriGeometryEnvelope" ) );
123 : 0 : query.addQueryItem( QStringLiteral( "spatialRel" ), QStringLiteral( "esriSpatialRelEnvelopeIntersects" ) );
124 : 0 : queryUrl.setQuery( query );
125 : 0 : const QVariantMap objectIdData = queryServiceJSON( queryUrl, authcfg, errorTitle, errorText, requestHeaders, feedback );
126 : :
127 : 0 : if ( objectIdData.isEmpty() )
128 : 0 : {
129 : 0 : return QList<quint32>();
130 : : }
131 : :
132 : 0 : QList<quint32> ids;
133 : 0 : const QVariantList objectIdsList = objectIdData[QStringLiteral( "objectIds" )].toList();
134 : 0 : ids.reserve( objectIdsList.size() );
135 : 0 : for ( const QVariant &objectId : objectIdsList )
136 : : {
137 : 0 : ids << objectId.toInt();
138 : : }
139 : 0 : return ids;
140 : 0 : }
141 : :
142 : 0 : QByteArray QgsArcGisRestQueryUtils::queryService( const QUrl &u, const QString &authcfg, QString &errorTitle, QString &errorText, const QgsStringMap &requestHeaders, QgsFeedback *feedback, QString *contentType )
143 : : {
144 : 0 : QUrl url = parseUrl( u );
145 : 0 :
146 : 0 : QNetworkRequest request( url );
147 : 0 : QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsArcGisRestUtils" ) );
148 : 0 : for ( auto it = requestHeaders.constBegin(); it != requestHeaders.constEnd(); ++it )
149 : : {
150 : 0 : request.setRawHeader( it.key().toUtf8(), it.value().toUtf8() );
151 : 0 : }
152 : :
153 : 0 : QgsBlockingNetworkRequest networkRequest;
154 : 0 : networkRequest.setAuthCfg( authcfg );
155 : 0 : const QgsBlockingNetworkRequest::ErrorCode error = networkRequest.get( request, false, feedback );
156 : :
157 : 0 : if ( feedback && feedback->isCanceled() )
158 : 0 : return QByteArray();
159 : :
160 : : // Handle network errors
161 : 0 : if ( error != QgsBlockingNetworkRequest::NoError )
162 : : {
163 : 0 : QgsDebugMsg( QStringLiteral( "Network error: %1" ).arg( networkRequest.errorMessage() ) );
164 : 0 : errorTitle = QStringLiteral( "Network error" );
165 : 0 : errorText = networkRequest.errorMessage();
166 : 0 : return QByteArray();
167 : : }
168 : :
169 : 0 : const QgsNetworkReplyContent content = networkRequest.reply();
170 : 0 : if ( contentType )
171 : 0 : *contentType = content.rawHeader( "Content-Type" );
172 : 0 : return content.content();
173 : 0 : }
174 : :
175 : 0 : QVariantMap QgsArcGisRestQueryUtils::queryServiceJSON( const QUrl &url, const QString &authcfg, QString &errorTitle, QString &errorText, const QgsStringMap &requestHeaders, QgsFeedback *feedback )
176 : : {
177 : 0 : QByteArray reply = queryService( url, authcfg, errorTitle, errorText, requestHeaders, feedback );
178 : 0 : if ( !errorTitle.isEmpty() )
179 : : {
180 : 0 : return QVariantMap();
181 : : }
182 : 0 : if ( feedback && feedback->isCanceled() )
183 : 0 : return QVariantMap();
184 : :
185 : : // Parse data
186 : : QJsonParseError err;
187 : 0 : QJsonDocument doc = QJsonDocument::fromJson( reply, &err );
188 : 0 : if ( doc.isNull() )
189 : : {
190 : 0 : errorTitle = QStringLiteral( "Parsing error" );
191 : 0 : errorText = err.errorString();
192 : 0 : QgsDebugMsg( QStringLiteral( "Parsing error: %1" ).arg( err.errorString() ) );
193 : 0 : return QVariantMap();
194 : : }
195 : 0 : const QVariantMap res = doc.object().toVariantMap();
196 : 0 : if ( res.contains( QStringLiteral( "error" ) ) )
197 : : {
198 : 0 : const QVariantMap error = res.value( QStringLiteral( "error" ) ).toMap();
199 : 0 : errorText = error.value( QStringLiteral( "message" ) ).toString();
200 : 0 : errorTitle = QObject::tr( "Error %1" ).arg( error.value( QStringLiteral( "code" ) ).toString() );
201 : 0 : return QVariantMap();
202 : 0 : }
203 : 0 : return res;
204 : 0 : }
205 : :
206 : 0 : QUrl QgsArcGisRestQueryUtils::parseUrl( const QUrl &url )
207 : : {
208 : 0 : QUrl modifiedUrl( url );
209 : 0 : if ( modifiedUrl.toString().contains( QLatin1String( "fake_qgis_http_endpoint" ) ) )
210 : : {
211 : : // Just for testing with local files instead of http:// resources
212 : 0 : QString modifiedUrlString = modifiedUrl.toString();
213 : : // Qt5 does URL encoding from some reason (of the FILTER parameter for example)
214 : 0 : modifiedUrlString = QUrl::fromPercentEncoding( modifiedUrlString.toUtf8() );
215 : 0 : modifiedUrlString.replace( QLatin1String( "fake_qgis_http_endpoint/" ), QLatin1String( "fake_qgis_http_endpoint_" ) );
216 : 0 : QgsDebugMsg( QStringLiteral( "Get %1" ).arg( modifiedUrlString ) );
217 : 0 : modifiedUrlString = modifiedUrlString.mid( QStringLiteral( "http://" ).size() );
218 : 0 : QString args = modifiedUrlString.mid( modifiedUrlString.indexOf( '?' ) );
219 : 0 : if ( modifiedUrlString.size() > 150 )
220 : : {
221 : 0 : args = QCryptographicHash::hash( args.toUtf8(), QCryptographicHash::Md5 ).toHex();
222 : 0 : }
223 : : else
224 : : {
225 : 0 : args.replace( QLatin1String( "?" ), QLatin1String( "_" ) );
226 : 0 : args.replace( QLatin1String( "&" ), QLatin1String( "_" ) );
227 : 0 : args.replace( QLatin1String( "<" ), QLatin1String( "_" ) );
228 : 0 : args.replace( QLatin1String( ">" ), QLatin1String( "_" ) );
229 : 0 : args.replace( QLatin1String( "'" ), QLatin1String( "_" ) );
230 : 0 : args.replace( QLatin1String( "\"" ), QLatin1String( "_" ) );
231 : 0 : args.replace( QLatin1String( " " ), QLatin1String( "_" ) );
232 : 0 : args.replace( QLatin1String( ":" ), QLatin1String( "_" ) );
233 : 0 : args.replace( QLatin1String( "/" ), QLatin1String( "_" ) );
234 : 0 : 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 : 0 : modifiedUrlString = modifiedUrlString.mid( 0, modifiedUrlString.indexOf( '?' ) ) + args;
245 : 0 : QgsDebugMsg( QStringLiteral( "Get %1 (after laundering)" ).arg( modifiedUrlString ) );
246 : 0 : modifiedUrl = QUrl::fromLocalFile( modifiedUrlString );
247 : 0 : }
248 : :
249 : 0 : return modifiedUrl;
250 : 0 : }
251 : :
252 : 0 : void QgsArcGisRestQueryUtils::adjustBaseUrl( QString &baseUrl, const QString &name )
253 : : {
254 : 0 : const QStringList parts = name.split( '/' );
255 : 0 : QString checkString;
256 : 0 : for ( const QString &part : parts )
257 : : {
258 : 0 : if ( !checkString.isEmpty() )
259 : 0 : checkString += QString( '/' );
260 : :
261 : 0 : checkString += part;
262 : 0 : if ( baseUrl.indexOf( QRegularExpression( checkString.replace( '/', QLatin1String( "\\/" ) ) + QStringLiteral( "\\/?$" ) ) ) > -1 )
263 : : {
264 : 0 : baseUrl = baseUrl.left( baseUrl.length() - checkString.length() - 1 );
265 : 0 : break;
266 : : }
267 : : }
268 : 0 : }
269 : :
270 : 0 : void QgsArcGisRestQueryUtils::visitFolderItems( const std::function< void( const QString &, const QString & ) > &visitor, const QVariantMap &serviceData, const QString &baseUrl )
271 : : {
272 : 0 : QString base( baseUrl );
273 : 0 : bool baseChecked = false;
274 : 0 : if ( !base.endsWith( '/' ) )
275 : 0 : base += QLatin1Char( '/' );
276 : :
277 : 0 : const QStringList folderList = serviceData.value( QStringLiteral( "folders" ) ).toStringList();
278 : 0 : for ( const QString &folder : folderList )
279 : : {
280 : 0 : if ( !baseChecked )
281 : : {
282 : 0 : adjustBaseUrl( base, folder );
283 : 0 : baseChecked = true;
284 : 0 : }
285 : 0 : visitor( folder, base + folder );
286 : : }
287 : 0 : }
288 : :
289 : 0 : void QgsArcGisRestQueryUtils::visitServiceItems( const std::function<void ( const QString &, const QString &, const QString &, const ServiceTypeFilter )> &visitor, const QVariantMap &serviceData, const QString &baseUrl )
290 : : {
291 : 0 : QString base( baseUrl );
292 : 0 : bool baseChecked = false;
293 : 0 : if ( !base.endsWith( '/' ) )
294 : 0 : base += QLatin1Char( '/' );
295 : :
296 : 0 : const QVariantList serviceList = serviceData.value( QStringLiteral( "services" ) ).toList();
297 : 0 : for ( const QVariant &service : serviceList )
298 : : {
299 : 0 : const QVariantMap serviceMap = service.toMap();
300 : 0 : const QString serviceType = serviceMap.value( QStringLiteral( "type" ) ).toString();
301 : 0 : if ( serviceType != QLatin1String( "MapServer" ) && serviceType != QLatin1String( "ImageServer" ) && serviceType != QLatin1String( "FeatureServer" ) )
302 : 0 : continue;
303 : :
304 : 0 : const ServiceTypeFilter type = serviceType == QLatin1String( "FeatureServer" ) ? Vector : Raster;
305 : :
306 : 0 : const QString serviceName = serviceMap.value( QStringLiteral( "name" ) ).toString();
307 : 0 : QString displayName = serviceName.split( '/' ).last();
308 : 0 : if ( !baseChecked )
309 : : {
310 : 0 : adjustBaseUrl( base, serviceName );
311 : 0 : baseChecked = true;
312 : 0 : }
313 : :
314 : 0 : visitor( displayName, base + serviceName + '/' + serviceType, serviceType, type );
315 : 0 : }
316 : 0 : }
317 : :
318 : 0 : 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 : 0 : const QString authid = QgsArcGisRestUtils::convertSpatialReference( serviceData.value( QStringLiteral( "spatialReference" ) ).toMap() ).authid();
321 : :
322 : 0 : bool found = false;
323 : 0 : const QList<QByteArray> supportedFormats = QImageReader::supportedImageFormats();
324 : 0 : const QStringList supportedImageFormatTypes = serviceData.value( QStringLiteral( "supportedImageFormatTypes" ) ).toString().isEmpty() ? parentSupportedFormats.split( ',' ) : serviceData.value( QStringLiteral( "supportedImageFormatTypes" ) ).toString().split( ',' );
325 : 0 : QString format = supportedImageFormatTypes.value( 0 );
326 : 0 : for ( const QString &encoding : supportedImageFormatTypes )
327 : : {
328 : 0 : for ( const QByteArray &fmt : supportedFormats )
329 : : {
330 : 0 : if ( encoding.startsWith( fmt, Qt::CaseInsensitive ) )
331 : : {
332 : 0 : format = encoding;
333 : 0 : found = true;
334 : 0 : break;
335 : : }
336 : : }
337 : 0 : if ( found )
338 : 0 : break;
339 : : }
340 : 0 : 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 : 0 : const bool serviceMayHaveQueryCapability = capabilities.contains( QStringLiteral( "Query" ) ) ||
344 : 0 : serviceData.value( QStringLiteral( "serviceDataType" ) ).toString().startsWith( QLatin1String( "esriImageService" ) );
345 : :
346 : 0 : const bool serviceMayRenderMaps = capabilities.contains( QStringLiteral( "Map" ) ) ||
347 : 0 : serviceData.value( QStringLiteral( "serviceDataType" ) ).toString().startsWith( QLatin1String( "esriImageService" ) );
348 : :
349 : 0 : const QVariantList layerInfoList = serviceData.value( QStringLiteral( "layers" ) ).toList();
350 : 0 : for ( const QVariant &layerInfo : layerInfoList )
351 : : {
352 : 0 : const QVariantMap layerInfoMap = layerInfo.toMap();
353 : 0 : const QString id = layerInfoMap.value( QStringLiteral( "id" ) ).toString();
354 : 0 : const QString parentLayerId = layerInfoMap.value( QStringLiteral( "parentLayerId" ) ).toString();
355 : 0 : const QString name = layerInfoMap.value( QStringLiteral( "name" ) ).toString();
356 : 0 : 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 : 0 : if ( serviceMayRenderMaps && ( filter == Raster || filter == AllTypes ) )
360 : : {
361 : 0 : if ( !layerInfoMap.value( QStringLiteral( "subLayerIds" ) ).toList().empty() )
362 : : {
363 : 0 : visitor( parentLayerId, Raster, QgsWkbTypes::UnknownGeometry, id, name, description, parentUrl + '/' + id, true, QString(), format );
364 : 0 : }
365 : : else
366 : : {
367 : 0 : visitor( parentLayerId, Raster, QgsWkbTypes::UnknownGeometry, id, name, description, parentUrl + '/' + id, false, authid, format );
368 : : }
369 : 0 : }
370 : :
371 : 0 : if ( serviceMayHaveQueryCapability && ( filter == Vector || filter == AllTypes ) )
372 : : {
373 : 0 : 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 : 0 : const QgsWkbTypes::Type wkbType = QgsArcGisRestUtils::convertGeometryType( geometryType );
392 : :
393 : :
394 : 0 : if ( !layerInfoMap.value( QStringLiteral( "subLayerIds" ) ).toList().empty() )
395 : : {
396 : 0 : visitor( parentLayerId, Vector, QgsWkbTypes::geometryType( wkbType ), id, name, description, parentUrl + '/' + id, true, QString(), format );
397 : 0 : }
398 : : else
399 : : {
400 : 0 : visitor( parentLayerId, Vector, QgsWkbTypes::geometryType( wkbType ), id, name, description, parentUrl + '/' + id, false, authid, format );
401 : : }
402 : 0 : }
403 : 0 : }
404 : :
405 : : // Add root MapServer as raster layer when multiple layers are listed
406 : 0 : if ( filter != Vector && layerInfoList.count() > 1 && serviceData.contains( QStringLiteral( "supportedImageFormatTypes" ) ) )
407 : : {
408 : 0 : const QString name = QStringLiteral( "(%1)" ).arg( QObject::tr( "All layers" ) );
409 : 0 : const QString description = serviceData.value( QStringLiteral( "Comments" ) ).toString();
410 : 0 : visitor( nullptr, Raster, QgsWkbTypes::UnknownGeometry, nullptr, name, description, parentUrl, false, authid, format );
411 : 0 : }
412 : :
413 : : // Add root ImageServer as layer
414 : 0 : if ( serviceData.value( QStringLiteral( "serviceDataType" ) ).toString().startsWith( QLatin1String( "esriImageService" ) ) )
415 : : {
416 : 0 : const QString name = serviceData.value( QStringLiteral( "name" ) ).toString();
417 : 0 : const QString description = serviceData.value( QStringLiteral( "description" ) ).toString();
418 : 0 : visitor( nullptr, Raster, QgsWkbTypes::UnknownGeometry, nullptr, name, description, parentUrl, false, authid, format );
419 : 0 : }
420 : 0 : }
421 : :
422 : :
423 : : ///@cond PRIVATE
424 : :
425 : : //
426 : : // QgsArcGisAsyncQuery
427 : : //
428 : :
429 : 0 : QgsArcGisAsyncQuery::QgsArcGisAsyncQuery( QObject *parent )
430 : 0 : : QObject( parent )
431 : 0 : {
432 : 0 : }
433 : :
434 : 0 : QgsArcGisAsyncQuery::~QgsArcGisAsyncQuery()
435 : 0 : {
436 : 0 : if ( mReply )
437 : 0 : mReply->deleteLater();
438 : 0 : }
439 : :
440 : 0 : void QgsArcGisAsyncQuery::start( const QUrl &url, const QString &authCfg, QByteArray *result, bool allowCache, const QgsStringMap &headers )
441 : : {
442 : 0 : mResult = result;
443 : 0 : QNetworkRequest request( url );
444 : :
445 : 0 : for ( auto it = headers.constBegin(); it != headers.constEnd(); ++it )
446 : : {
447 : 0 : request.setRawHeader( it.key().toUtf8(), it.value().toUtf8() );
448 : 0 : }
449 : :
450 : 0 : if ( !authCfg.isEmpty() && !QgsApplication::authManager()->updateNetworkRequest( request, authCfg ) )
451 : : {
452 : 0 : const QString error = tr( "network request update failed for authentication config" );
453 : 0 : emit failed( QStringLiteral( "Network" ), error );
454 : : return;
455 : 0 : }
456 : :
457 : 0 : QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsArcGisAsyncQuery" ) );
458 : 0 : if ( allowCache )
459 : : {
460 : 0 : request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
461 : 0 : request.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
462 : 0 : }
463 : 0 : mReply = QgsNetworkAccessManager::instance()->get( request );
464 : 0 : connect( mReply, &QNetworkReply::finished, this, &QgsArcGisAsyncQuery::handleReply );
465 : 0 : }
466 : :
467 : 0 : void QgsArcGisAsyncQuery::handleReply()
468 : : {
469 : 0 : mReply->deleteLater();
470 : : // Handle network errors
471 : 0 : if ( mReply->error() != QNetworkReply::NoError )
472 : : {
473 : 0 : QgsDebugMsg( QStringLiteral( "Network error: %1" ).arg( mReply->errorString() ) );
474 : 0 : emit failed( QStringLiteral( "Network error" ), mReply->errorString() );
475 : 0 : return;
476 : : }
477 : :
478 : : // Handle HTTP redirects
479 : 0 : QVariant redirect = mReply->attribute( QNetworkRequest::RedirectionTargetAttribute );
480 : 0 : if ( !redirect.isNull() )
481 : : {
482 : 0 : QNetworkRequest request = mReply->request();
483 : 0 : QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsArcGisAsyncQuery" ) );
484 : 0 : QgsDebugMsg( "redirecting to " + redirect.toUrl().toString() );
485 : 0 : request.setUrl( redirect.toUrl() );
486 : 0 : mReply = QgsNetworkAccessManager::instance()->get( request );
487 : 0 : connect( mReply, &QNetworkReply::finished, this, &QgsArcGisAsyncQuery::handleReply );
488 : : return;
489 : 0 : }
490 : :
491 : 0 : *mResult = mReply->readAll();
492 : 0 : mResult = nullptr;
493 : 0 : emit finished();
494 : 0 : }
495 : :
496 : : //
497 : : // QgsArcGisAsyncParallelQuery
498 : : //
499 : :
500 : 0 : QgsArcGisAsyncParallelQuery::QgsArcGisAsyncParallelQuery( const QString &authcfg, const QgsStringMap &requestHeaders, QObject *parent )
501 : 0 : : QObject( parent )
502 : 0 : , mAuthCfg( authcfg )
503 : 0 : , mRequestHeaders( requestHeaders )
504 : 0 : {
505 : 0 : }
506 : :
507 : 0 : void QgsArcGisAsyncParallelQuery::start( const QVector<QUrl> &urls, QVector<QByteArray> *results, bool allowCache )
508 : : {
509 : : Q_ASSERT( results->size() == urls.size() );
510 : 0 : mResults = results;
511 : 0 : mPendingRequests = mResults->size();
512 : 0 : for ( int i = 0, n = urls.size(); i < n; ++i )
513 : : {
514 : 0 : QNetworkRequest request( urls[i] );
515 : 0 : QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsArcGisAsyncParallelQuery" ) );
516 : 0 : QgsSetRequestInitiatorId( request, QString::number( i ) );
517 : :
518 : 0 : for ( auto it = mRequestHeaders.constBegin(); it != mRequestHeaders.constEnd(); ++it )
519 : : {
520 : 0 : request.setRawHeader( it.key().toUtf8(), it.value().toUtf8() );
521 : 0 : }
522 : 0 : if ( !mAuthCfg.isEmpty() && !QgsApplication::authManager()->updateNetworkRequest( request, mAuthCfg ) )
523 : : {
524 : 0 : const QString error = tr( "network request update failed for authentication config" );
525 : 0 : mErrors.append( error );
526 : 0 : QgsMessageLog::logMessage( error, tr( "Network" ) );
527 : : continue;
528 : 0 : }
529 : :
530 : 0 : request.setAttribute( QNetworkRequest::HttpPipeliningAllowedAttribute, true );
531 : 0 : if ( allowCache )
532 : : {
533 : 0 : request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
534 : 0 : request.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
535 : 0 : request.setRawHeader( "Connection", "keep-alive" );
536 : 0 : }
537 : 0 : QNetworkReply *reply = QgsNetworkAccessManager::instance()->get( request );
538 : 0 : reply->setProperty( "idx", i );
539 : 0 : connect( reply, &QNetworkReply::finished, this, &QgsArcGisAsyncParallelQuery::handleReply );
540 : 0 : }
541 : 0 : }
542 : :
543 : 0 : void QgsArcGisAsyncParallelQuery::handleReply()
544 : : {
545 : 0 : QNetworkReply *reply = qobject_cast<QNetworkReply *>( QObject::sender() );
546 : 0 : QVariant redirect = reply->attribute( QNetworkRequest::RedirectionTargetAttribute );
547 : 0 : int idx = reply->property( "idx" ).toInt();
548 : 0 : reply->deleteLater();
549 : 0 : if ( reply->error() != QNetworkReply::NoError )
550 : : {
551 : : // Handle network errors
552 : 0 : mErrors.append( reply->errorString() );
553 : 0 : --mPendingRequests;
554 : 0 : }
555 : 0 : else if ( !redirect.isNull() )
556 : : {
557 : : // Handle HTTP redirects
558 : 0 : QNetworkRequest request = reply->request();
559 : 0 : QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsArcGisAsyncParallelQuery" ) );
560 : 0 : QgsDebugMsg( "redirecting to " + redirect.toUrl().toString() );
561 : 0 : request.setUrl( redirect.toUrl() );
562 : 0 : reply = QgsNetworkAccessManager::instance()->get( request );
563 : 0 : reply->setProperty( "idx", idx );
564 : 0 : connect( reply, &QNetworkReply::finished, this, &QgsArcGisAsyncParallelQuery::handleReply );
565 : 0 : }
566 : : else
567 : : {
568 : : // All OK
569 : 0 : ( *mResults )[idx] = reply->readAll();
570 : 0 : --mPendingRequests;
571 : : }
572 : 0 : if ( mPendingRequests == 0 )
573 : : {
574 : 0 : emit finished( mErrors );
575 : 0 : mResults = nullptr;
576 : 0 : mErrors.clear();
577 : 0 : }
578 : 0 : }
579 : :
580 : : ///@endcond
|