LCOV - code coverage report
Current view: top level - core/providers/arcgis - qgsarcgisrestquery.cpp (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 0 355 0.0 %
Date: 2021-04-10 08:29:14 Functions: 0 0 -
Branches: 0 0 -

           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

Generated by: LCOV version 1.14