28 #include <QImageReader>
29 #include <QRegularExpression>
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 );
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 );
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" ) );
61 query.addQueryItem( QStringLiteral(
"geometry" ), QStringLiteral(
"%1,%2,%3,%4" )
64 query.addQueryItem( QStringLiteral(
"geometryType" ), QStringLiteral(
"esriGeometryEnvelope" ) );
65 query.addQueryItem( QStringLiteral(
"spatialRel" ), QStringLiteral(
"esriSpatialRelEnvelopeIntersects" ) );
67 queryUrl.setQuery( query );
68 return queryServiceJSON( queryUrl, authcfg, errorTitle, errorText, requestHeaders );
72 bool fetchGeometry,
const QStringList &fetchAttributes,
73 bool fetchM,
bool fetchZ,
78 for (
const int id : objectIds )
80 ids.append( QString::number(
id ) );
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 );
90 query.addQueryItem( QStringLiteral(
"returnGeometry" ), fetchGeometry ? QStringLiteral(
"true" ) : QStringLiteral(
"false" ) );
93 if ( fetchAttributes.isEmpty() )
94 outFields = QStringLiteral(
"*" );
96 outFields = fetchAttributes.join(
',' );
97 query.addQueryItem( QStringLiteral(
"outFields" ), outFields );
99 query.addQueryItem( QStringLiteral(
"returnM" ), fetchM ? QStringLiteral(
"true" ) : QStringLiteral(
"false" ) );
100 query.addQueryItem( QStringLiteral(
"returnZ" ), fetchZ ? QStringLiteral(
"true" ) : QStringLiteral(
"false" ) );
101 if ( !filterRect.
isNull() )
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" ) );
109 queryUrl.setQuery( query );
110 return queryServiceJSON( queryUrl, authcfg, errorTitle, errorText, requestHeaders, feedback );
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 );
128 if ( objectIdData.isEmpty() )
130 return QList<quint32>();
134 const QVariantList objectIdsList = objectIdData[QStringLiteral(
"objectIds" )].toList();
135 ids.reserve( objectIdsList.size() );
136 for (
const QVariant &objectId : objectIdsList )
138 ids << objectId.toInt();
145 const QUrl url = parseUrl( u );
147 QNetworkRequest request( url );
162 errorTitle = QStringLiteral(
"Network error" );
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() )
171 errorText = match.captured( 1 );
179 *contentType = content.
rawHeader(
"Content-Type" );
185 const QByteArray reply =
queryService( url, authcfg, errorTitle, errorText, requestHeaders, feedback );
186 if ( !errorTitle.isEmpty() )
188 return QVariantMap();
191 return QVariantMap();
195 const QJsonDocument doc = QJsonDocument::fromJson( reply, &err );
198 errorTitle = QStringLiteral(
"Parsing error" );
199 errorText = err.errorString();
200 QgsDebugMsg( QStringLiteral(
"Parsing error: %1" ).arg( err.errorString() ) );
201 return QVariantMap();
203 const QVariantMap res = doc.object().toVariantMap();
204 if ( res.contains( QStringLiteral(
"error" ) ) )
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();
214 QUrl QgsArcGisRestQueryUtils::parseUrl(
const QUrl &url )
216 QUrl modifiedUrl( url );
217 if ( modifiedUrl.toString().contains( QLatin1String(
"fake_qgis_http_endpoint" ) ) )
220 QString modifiedUrlString = modifiedUrl.toString();
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 )
229 args = QCryptographicHash::hash( args.toUtf8(), QCryptographicHash::Md5 ).toHex();
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(
"_" ) );
247 if ( modifiedUrlString[1] ==
'/' )
249 modifiedUrlString = modifiedUrlString[0] +
":/" + modifiedUrlString.mid( 2 );
252 modifiedUrlString = modifiedUrlString.mid( 0, modifiedUrlString.indexOf(
'?' ) ) + args;
253 QgsDebugMsg( QStringLiteral(
"Get %1 (after laundering)" ).arg( modifiedUrlString ) );
254 modifiedUrl = QUrl::fromLocalFile( modifiedUrlString );
260 void QgsArcGisRestQueryUtils::adjustBaseUrl( QString &baseUrl,
const QString &name )
262 const QStringList parts = name.split(
'/' );
264 for (
const QString &part : parts )
266 if ( !checkString.isEmpty() )
267 checkString += QString(
'/' );
270 if ( baseUrl.indexOf( QRegularExpression( checkString.replace(
'/', QLatin1String(
"\\/" ) ) + QStringLiteral(
"\\/?$" ) ) ) > -1 )
272 baseUrl = baseUrl.left( baseUrl.length() - checkString.length() - 1 );
280 QString base( baseUrl );
281 bool baseChecked =
false;
282 if ( !base.endsWith(
'/' ) )
283 base += QLatin1Char(
'/' );
285 const QStringList folderList = serviceData.value( QStringLiteral(
"folders" ) ).toStringList();
286 for (
const QString &folder : folderList )
290 adjustBaseUrl( base, folder );
293 visitor( folder, base + folder );
299 QString base( baseUrl );
300 bool baseChecked =
false;
301 if ( !base.endsWith(
'/' ) )
302 base += QLatin1Char(
'/' );
304 const QVariantList serviceList = serviceData.value( QStringLiteral(
"services" ) ).toList();
305 for (
const QVariant &service : serviceList )
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" ) )
314 const QString serviceName = serviceMap.value( QStringLiteral(
"name" ) ).toString();
315 const QString displayName = serviceName.split(
'/' ).last();
318 adjustBaseUrl( base, serviceName );
322 visitor( displayName, base + serviceName +
'/' + serviceType, serviceType, type );
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 )
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 )
336 for (
const QByteArray &fmt : supportedFormats )
338 if ( encoding.startsWith( fmt, Qt::CaseInsensitive ) )
348 const QStringList capabilities = serviceData.value( QStringLiteral(
"capabilities" ) ).toString().split(
',' );
351 const bool serviceMayHaveQueryCapability = capabilities.contains( QStringLiteral(
"Query" ) ) ||
352 serviceData.value( QStringLiteral(
"serviceDataType" ) ).toString().startsWith( QLatin1String(
"esriImageService" ) );
354 const bool serviceMayRenderMaps = capabilities.contains( QStringLiteral(
"Map" ) ) ||
355 serviceData.value( QStringLiteral(
"serviceDataType" ) ).toString().startsWith( QLatin1String(
"esriImageService" ) );
357 const QVariantList layerInfoList = serviceData.value( QStringLiteral(
"layers" ) ).toList();
358 for (
const QVariant &layerInfo : layerInfoList )
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();
367 if ( serviceMayRenderMaps && ( filter ==
Raster || filter ==
AllTypes ) )
369 if ( !layerInfoMap.value( QStringLiteral(
"subLayerIds" ) ).toList().empty() )
379 if ( serviceMayHaveQueryCapability && ( filter ==
Vector || filter ==
AllTypes ) )
381 const QString geometryType = layerInfoMap.value( QStringLiteral(
"geometryType" ) ).toString();
392 if ( serviceMayRenderMaps )
394 if ( geometryType.isEmpty() )
402 if ( !layerInfoMap.value( QStringLiteral(
"subLayerIds" ) ).toList().empty() )
414 if ( filter !=
Vector && layerInfoList.count() > 1 && serviceData.contains( QStringLiteral(
"supportedImageFormatTypes" ) ) )
416 const QString name = QStringLiteral(
"(%1)" ).arg( QObject::tr(
"All layers" ) );
417 const QString description = serviceData.value( QStringLiteral(
"Comments" ) ).toString();
422 if ( serviceData.value( QStringLiteral(
"serviceDataType" ) ).toString().startsWith( QLatin1String(
"esriImageService" ) ) )
424 const QString name = serviceData.value( QStringLiteral(
"name" ) ).toString();
425 const QString description = serviceData.value( QStringLiteral(
"description" ) ).toString();
437 QgsArcGisAsyncQuery::QgsArcGisAsyncQuery( QObject *parent )
442 QgsArcGisAsyncQuery::~QgsArcGisAsyncQuery()
445 mReply->deleteLater();
448 void QgsArcGisAsyncQuery::start(
const QUrl &url,
const QString &authCfg, QByteArray *result,
bool allowCache,
const QgsHttpHeaders &headers )
451 QNetworkRequest request( url );
457 const QString error = tr(
"network request update failed for authentication config" );
458 emit failed( QStringLiteral(
"Network" ), error );
465 request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
466 request.setAttribute( QNetworkRequest::CacheSaveControlAttribute,
true );
469 connect( mReply, &QNetworkReply::finished,
this, &QgsArcGisAsyncQuery::handleReply );
472 void QgsArcGisAsyncQuery::handleReply()
474 mReply->deleteLater();
476 if ( mReply->error() != QNetworkReply::NoError )
478 QgsDebugMsg( QStringLiteral(
"Network error: %1" ).arg( mReply->errorString() ) );
479 emit failed( QStringLiteral(
"Network error" ), mReply->errorString() );
484 const QVariant redirect = mReply->attribute( QNetworkRequest::RedirectionTargetAttribute );
485 if ( !redirect.isNull() )
487 QNetworkRequest request = mReply->request();
489 QgsDebugMsg(
"redirecting to " + redirect.toUrl().toString() );
490 request.setUrl( redirect.toUrl() );
492 connect( mReply, &QNetworkReply::finished,
this, &QgsArcGisAsyncQuery::handleReply );
496 *mResult = mReply->readAll();
505 QgsArcGisAsyncParallelQuery::QgsArcGisAsyncParallelQuery(
const QString &authcfg,
const QgsHttpHeaders &requestHeaders, QObject *parent )
507 , mAuthCfg( authcfg )
508 , mRequestHeaders( requestHeaders )
512 void QgsArcGisAsyncParallelQuery::start(
const QVector<QUrl> &urls, QVector<QByteArray> *results,
bool allowCache )
514 Q_ASSERT( results->size() == urls.size() );
516 mPendingRequests = mResults->size();
517 for (
int i = 0, n = urls.size(); i < n; ++i )
519 QNetworkRequest request( urls[i] );
523 mRequestHeaders.updateNetworkRequest( request );
526 const QString error = tr(
"network request update failed for authentication config" );
527 mErrors.append( error );
532 request.setAttribute( QNetworkRequest::HttpPipeliningAllowedAttribute,
true );
535 request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
536 request.setAttribute( QNetworkRequest::CacheSaveControlAttribute,
true );
537 request.setRawHeader(
"Connection",
"keep-alive" );
540 reply->setProperty(
"idx", i );
541 connect( reply, &QNetworkReply::finished,
this, &QgsArcGisAsyncParallelQuery::handleReply );
545 void QgsArcGisAsyncParallelQuery::handleReply()
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 )
554 mErrors.append( reply->errorString() );
557 else if ( !redirect.isNull() )
560 QNetworkRequest request = reply->request();
562 QgsDebugMsg(
"redirecting to " + redirect.toUrl().toString() );
563 request.setUrl( redirect.toUrl() );
565 reply->setProperty(
"idx", idx );
566 connect( reply, &QNetworkReply::finished,
this, &QgsArcGisAsyncParallelQuery::handleReply );
571 ( *mResults )[idx] = reply->readAll();
574 if ( mPendingRequests == 0 )
576 emit finished( mErrors );