QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgsarcgisvectortileservicedataprovider.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsarcgisvectortileservicedataprovider.cpp
3 --------------------------------------
4 Date : March 2020
5 Copyright : (C) 2020 by Martin Dobias
6 Email : wonder dot sk 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
17#include "qgsthreadingutils.h"
18#include "qgsapplication.h"
22#include "qgsvectortileutils.h"
23#include "qgsarcgisrestutils.h"
24#include "qgslogger.h"
26
27#include <QIcon>
28#include <QUrl>
29#include <QUrlQuery>
30#include <QJsonParseError>
31#include <QJsonDocument>
32#include <QJsonObject>
33
35
36QString QgsArcGisVectorTileServiceDataProvider::ARCGIS_VT_SERVICE_DATA_PROVIDER_KEY = QStringLiteral( "arcgisvectortileservice" );
37QString QgsArcGisVectorTileServiceDataProvider::ARCGIS_VT_SERVICE_DATA_PROVIDER_DESCRIPTION = QObject::tr( "ArcGIS Vector Tile Service data provider" );
38
39
40QgsArcGisVectorTileServiceDataProvider::QgsArcGisVectorTileServiceDataProvider( const QString &uri, const ProviderOptions &providerOptions, ReadFlags flags )
41 : QgsXyzVectorTileDataProviderBase( uri, providerOptions, flags )
42{
43 mIsValid = setupArcgisVectorTileServiceConnection();
44
45 if ( !mIsValid )
46 return;
47
48 // populate default metadata
49 mLayerMetadata.setIdentifier( mArcgisLayerConfiguration.value( QStringLiteral( "serviceUri" ) ).toString() );
50 const QString parentIdentifier = mArcgisLayerConfiguration.value( QStringLiteral( "serviceItemId" ) ).toString();
51 if ( !parentIdentifier.isEmpty() )
52 {
53 mLayerMetadata.setParentIdentifier( parentIdentifier );
54 }
55 mLayerMetadata.setType( QStringLiteral( "dataset" ) );
56 mLayerMetadata.setTitle( mArcgisLayerConfiguration.value( QStringLiteral( "name" ) ).toString() );
57 const QString copyright = mArcgisLayerConfiguration.value( QStringLiteral( "copyrightText" ) ).toString();
58 if ( !copyright.isEmpty() )
59 mLayerMetadata.setRights( QStringList() << copyright );
60 mLayerMetadata.addLink( QgsAbstractMetadataBase::Link( tr( "Source" ), QStringLiteral( "WWW:LINK" ), mArcgisLayerConfiguration.value( QStringLiteral( "serviceUri" ) ).toString() ) );
61}
62
63QgsArcGisVectorTileServiceDataProvider::QgsArcGisVectorTileServiceDataProvider( const QgsArcGisVectorTileServiceDataProvider &other )
64 : QgsXyzVectorTileDataProviderBase( other )
65{
66 mIsValid = other.mIsValid;
67 mExtent = other.mExtent;
68 mMatrixSet = other.mMatrixSet;
69 mSourcePath = other.mSourcePath;
70 mArcgisLayerConfiguration = other.mArcgisLayerConfiguration;
71 mArcgisStyleConfiguration = other.mArcgisStyleConfiguration;
72 mCrs = other.mCrs;
73 mLayerMetadata = other.mLayerMetadata;
74}
75
76Qgis::DataProviderFlags QgsArcGisVectorTileServiceDataProvider::flags() const
77{
79}
80
81Qgis::VectorTileProviderFlags QgsArcGisVectorTileServiceDataProvider::providerFlags() const
82{
83 return QgsXyzVectorTileDataProviderBase::providerFlags() | Qgis::VectorTileProviderFlag::AlwaysUseTileMatrixSetFromProvider;
84}
85
86Qgis::VectorTileProviderCapabilities QgsArcGisVectorTileServiceDataProvider::providerCapabilities() const
87{
89}
90
91QString QgsArcGisVectorTileServiceDataProvider::name() const
92{
94
95 return ARCGIS_VT_SERVICE_DATA_PROVIDER_KEY;
96}
97
98QString QgsArcGisVectorTileServiceDataProvider::description() const
99{
101
102 return ARCGIS_VT_SERVICE_DATA_PROVIDER_DESCRIPTION;
103}
104
105QgsVectorTileDataProvider *QgsArcGisVectorTileServiceDataProvider::clone() const
106{
108
109 return new QgsArcGisVectorTileServiceDataProvider( *this );
110}
111
112QString QgsArcGisVectorTileServiceDataProvider::sourcePath() const
113{
115
116 return mSourcePath;
117}
118
119bool QgsArcGisVectorTileServiceDataProvider::isValid() const
120{
122
123 return mIsValid;
124}
125
126QgsRectangle QgsArcGisVectorTileServiceDataProvider::extent() const
127{
129
130 return mExtent;
131}
132
133const QgsVectorTileMatrixSet &QgsArcGisVectorTileServiceDataProvider::tileMatrixSet() const
134{
136
137 return mMatrixSet;
138}
139
141{
143
144 return mCrs;
145}
146
147QgsLayerMetadata QgsArcGisVectorTileServiceDataProvider::layerMetadata() const
148{
150
151 return mLayerMetadata;
152}
153
154QVariantMap QgsArcGisVectorTileServiceDataProvider::styleDefinition() const
155{
157
158 return mArcgisStyleConfiguration;
159}
160
161QString QgsArcGisVectorTileServiceDataProvider::styleUrl() const
162{
164
165 // for ArcMap VectorTileServices we default to the defaultStyles URL from the layer configuration
166 return mArcgisLayerConfiguration.value( QStringLiteral( "serviceUri" ) ).toString()
167 + '/' + mArcgisLayerConfiguration.value( QStringLiteral( "defaultStyles" ) ).toString();
168}
169
170QString QgsArcGisVectorTileServiceDataProvider::htmlMetadata() const
171{
172 QString metadata;
173
174 if ( !mTileMapUrl.isEmpty() )
175 metadata += QStringLiteral( "<tr><td class=\"highlight\">" ) % tr( "Tilemap" ) % QStringLiteral( "</td><td><a href=\"%1\">%1</a>" ).arg( mTileMapUrl ) % QStringLiteral( "</td></tr>\n" );
176
177 return metadata;
178}
179
180bool QgsArcGisVectorTileServiceDataProvider::setupArcgisVectorTileServiceConnection()
181{
183
184 QgsDataSourceUri dsUri;
185 dsUri.setEncodedUri( dataSourceUri() );
186 QString tileServiceUri = dsUri.param( QStringLiteral( "url" ) );
187
188 QUrl url( tileServiceUri );
189 // some services don't default to json format, while others do... so let's explicitly request it!
190 // (refs https://github.com/qgis/QGIS/issues/4231)
191 QUrlQuery query;
192 query.addQueryItem( QStringLiteral( "f" ), QStringLiteral( "pjson" ) );
193 url.setQuery( query );
194
195 QNetworkRequest request = QNetworkRequest( url );
196
197 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsVectorTileLayer" ) )
198
199 QgsBlockingNetworkRequest networkRequest;
200 switch ( networkRequest.get( request ) )
201 {
203 break;
204
208 return false;
209 }
210
211 const QgsNetworkReplyContent content = networkRequest.reply();
212 const QByteArray raw = content.content();
213
214 // Parse data
215 QJsonParseError err;
216 const QJsonDocument doc = QJsonDocument::fromJson( raw, &err );
217 if ( doc.isNull() )
218 {
219 return false;
220 }
221
222 mArcgisLayerConfiguration = doc.object().toVariantMap();
223 if ( mArcgisLayerConfiguration.contains( QStringLiteral( "error" ) ) )
224 {
225 return false;
226 }
227
228 if ( !mArcgisLayerConfiguration.value( QStringLiteral( "tiles" ) ).isValid() )
229 {
230 // maybe url is pointing to a resources/styles/root.json type url, that's ok too!
231 const QString sourceUri = mArcgisLayerConfiguration.value( QStringLiteral( "sources" ) ).toMap().value( QStringLiteral( "esri" ) ).toMap().value( QStringLiteral( "url" ) ).toString();
232 if ( !sourceUri.isEmpty() )
233 {
234 QUrl url( sourceUri );
235 // some services don't default to json format, while others do... so let's explicitly request it!
236 // (refs https://github.com/qgis/QGIS/issues/4231)
237 QUrlQuery query;
238 query.addQueryItem( QStringLiteral( "f" ), QStringLiteral( "pjson" ) );
239 url.setQuery( query );
240
241 QNetworkRequest request = QNetworkRequest( url );
242
243 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsVectorTileLayer" ) )
244
245 QgsBlockingNetworkRequest networkRequest;
246 switch ( networkRequest.get( request ) )
247 {
249 break;
250
254 return false;
255 }
256
257 const QgsNetworkReplyContent content = networkRequest.reply();
258 const QByteArray raw = content.content();
259
260 // Parse data
261 QJsonParseError err;
262 const QJsonDocument doc = QJsonDocument::fromJson( raw, &err );
263 if ( doc.isNull() )
264 {
265 return false;
266 }
267
268 tileServiceUri = sourceUri;
269
270 // the resources/styles/root.json configuration is actually our style definition
271 mArcgisStyleConfiguration = mArcgisLayerConfiguration;
272 mArcgisLayerConfiguration = doc.object().toVariantMap();
273 if ( mArcgisLayerConfiguration.contains( QStringLiteral( "error" ) ) )
274 {
275 return false;
276 }
277 }
278 }
279
280 // read tileMap if available
281 QVariantMap tileMap;
282 const QString tileMapEndpoint = mArcgisLayerConfiguration.value( QStringLiteral( "tileMap" ) ).toString();
283 if ( !tileMapEndpoint.isEmpty() )
284 {
285 mTileMapUrl = tileServiceUri + '/' + tileMapEndpoint;
286 QUrl tilemapUrl( mTileMapUrl );
287 tilemapUrl.setQuery( query );
288
289 QNetworkRequest tileMapRequest = QNetworkRequest( tilemapUrl );
290 QgsSetRequestInitiatorClass( tileMapRequest, QStringLiteral( "QgsVectorTileLayer" ) )
291
292 QgsBlockingNetworkRequest tileMapNetworkRequest;
293 switch ( tileMapNetworkRequest.get( tileMapRequest ) )
294 {
296 break;
297
301 return false;
302 }
303
304 const QgsNetworkReplyContent tileMapContent = tileMapNetworkRequest.reply();
305 const QByteArray tileMapRaw = tileMapContent.content();
306
307 const QJsonDocument tileMapDoc = QJsonDocument::fromJson( tileMapRaw, &err );
308 if ( !tileMapDoc.isNull() )
309 {
310 tileMap = tileMapDoc.object().toVariantMap();
311 }
312 }
313
314 mSourcePath = tileServiceUri + '/' + mArcgisLayerConfiguration.value( QStringLiteral( "tiles" ) ).toList().value( 0 ).toString();
315 if ( !QgsVectorTileUtils::checkXYZUrlTemplate( mSourcePath ) )
316 {
317 QgsDebugError( QStringLiteral( "Invalid format of URL for XYZ source: " ) + tileServiceUri );
318 return false;
319 }
320
321 mArcgisLayerConfiguration.insert( QStringLiteral( "serviceUri" ), tileServiceUri );
322
323 mMatrixSet.fromEsriJson( mArcgisLayerConfiguration, tileMap );
324 mCrs = mMatrixSet.crs();
325
326 // if hardcoded zoom limits aren't specified, take them from the server
327 if ( dsUri.hasParam( QStringLiteral( "zmin" ) ) )
328 mMatrixSet.dropMatricesOutsideZoomRange( dsUri.param( QStringLiteral( "zmin" ) ).toInt(), 99 );
329
330 if ( dsUri.hasParam( QStringLiteral( "zmax" ) ) )
331 mMatrixSet.dropMatricesOutsideZoomRange( 0, dsUri.param( QStringLiteral( "zmax" ) ).toInt() );
332
333 const QVariantMap fullExtent = mArcgisLayerConfiguration.value( QStringLiteral( "fullExtent" ) ).toMap();
334 if ( !fullExtent.isEmpty() )
335 {
336 const QgsRectangle fullExtentRect(
337 fullExtent.value( QStringLiteral( "xmin" ) ).toDouble(),
338 fullExtent.value( QStringLiteral( "ymin" ) ).toDouble(),
339 fullExtent.value( QStringLiteral( "xmax" ) ).toDouble(),
340 fullExtent.value( QStringLiteral( "ymax" ) ).toDouble()
341 );
342
343 const QgsCoordinateReferenceSystem fullExtentCrs = QgsArcGisRestUtils::convertSpatialReference( fullExtent.value( QStringLiteral( "spatialReference" ) ).toMap() );
344 const QgsCoordinateTransform extentTransform( fullExtentCrs, mCrs, transformContext() );
345 try
346 {
347 mExtent = extentTransform.transformBoundingBox( fullExtentRect );
348 }
349 catch ( QgsCsException & )
350 {
351 QgsDebugError( QStringLiteral( "Could not transform layer fullExtent to layer CRS" ) );
352 }
353 }
354 else
355 {
356 // if no fullExtent specified in JSON, default to web mercator specs full extent
357 const QgsCoordinateTransform extentTransform( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3857" ) ), mCrs, transformContext() );
358 try
359 {
360 mExtent = extentTransform.transformBoundingBox( QgsRectangle( -20037508.3427892, -20037508.3427892, 20037508.3427892, 20037508.3427892 ) );
361 }
362 catch ( QgsCsException & )
363 {
364 QgsDebugError( QStringLiteral( "Could not transform layer extent to layer CRS" ) );
365 }
366 }
367
368 return true;
369}
370
371
372//
373// QgsArcGisVectorTileServiceDataProviderMetadata
374//
375
376QgsArcGisVectorTileServiceDataProviderMetadata::QgsArcGisVectorTileServiceDataProviderMetadata()
377 : QgsProviderMetadata( QgsArcGisVectorTileServiceDataProvider::ARCGIS_VT_SERVICE_DATA_PROVIDER_KEY,
378 QgsArcGisVectorTileServiceDataProvider::ARCGIS_VT_SERVICE_DATA_PROVIDER_DESCRIPTION )
379{
380}
381
382QIcon QgsArcGisVectorTileServiceDataProviderMetadata::icon() const
383{
384 return QgsApplication::getThemeIcon( QStringLiteral( "mIconVectorTileLayer.svg" ) );
385}
386
387QgsProviderMetadata::ProviderCapabilities QgsArcGisVectorTileServiceDataProviderMetadata::providerCapabilities() const
388{
390}
391
392QgsArcGisVectorTileServiceDataProvider *QgsArcGisVectorTileServiceDataProviderMetadata::createProvider( const QString &uri, const QgsDataProvider::ProviderOptions &options, QgsDataProvider::ReadFlags flags )
393{
394 return new QgsArcGisVectorTileServiceDataProvider( uri, options, flags );
395}
396
397QVariantMap QgsArcGisVectorTileServiceDataProviderMetadata::decodeUri( const QString &uri ) const
398{
399 QgsDataSourceUri dsUri;
400 dsUri.setEncodedUri( uri );
401
402 QVariantMap uriComponents;
403 uriComponents.insert( QStringLiteral( "type" ), QStringLiteral( "xyz" ) );
404 uriComponents.insert( QStringLiteral( "serviceType" ), QStringLiteral( "arcgis" ) );
405 uriComponents.insert( QStringLiteral( "url" ), dsUri.param( QStringLiteral( "url" ) ) );
406
407 if ( dsUri.hasParam( QStringLiteral( "zmin" ) ) )
408 uriComponents.insert( QStringLiteral( "zmin" ), dsUri.param( QStringLiteral( "zmin" ) ) );
409 if ( dsUri.hasParam( QStringLiteral( "zmax" ) ) )
410 uriComponents.insert( QStringLiteral( "zmax" ), dsUri.param( QStringLiteral( "zmax" ) ) );
411
412 dsUri.httpHeaders().updateMap( uriComponents );
413
414 if ( dsUri.hasParam( QStringLiteral( "styleUrl" ) ) )
415 uriComponents.insert( QStringLiteral( "styleUrl" ), dsUri.param( QStringLiteral( "styleUrl" ) ) );
416
417 const QString authcfg = dsUri.authConfigId();
418 if ( !authcfg.isEmpty() )
419 uriComponents.insert( QStringLiteral( "authcfg" ), authcfg );
420
421 return uriComponents;
422}
423
424QString QgsArcGisVectorTileServiceDataProviderMetadata::encodeUri( const QVariantMap &parts ) const
425{
426 QgsDataSourceUri dsUri;
427 dsUri.setParam( QStringLiteral( "type" ), QStringLiteral( "xyz" ) );
428 dsUri.setParam( QStringLiteral( "serviceType" ), QStringLiteral( "arcgis" ) );
429 dsUri.setParam( QStringLiteral( "url" ), parts.value( QStringLiteral( "url" ) ).toString() );
430
431 if ( parts.contains( QStringLiteral( "zmin" ) ) )
432 dsUri.setParam( QStringLiteral( "zmin" ), parts[ QStringLiteral( "zmin" ) ].toString() );
433 if ( parts.contains( QStringLiteral( "zmax" ) ) )
434 dsUri.setParam( QStringLiteral( "zmax" ), parts[ QStringLiteral( "zmax" ) ].toString() );
435
436 dsUri.httpHeaders().setFromMap( parts );
437
438 if ( parts.contains( QStringLiteral( "styleUrl" ) ) )
439 dsUri.setParam( QStringLiteral( "styleUrl" ), parts[ QStringLiteral( "styleUrl" ) ].toString() );
440
441 if ( parts.contains( QStringLiteral( "authcfg" ) ) )
442 dsUri.setAuthConfigId( parts[ QStringLiteral( "authcfg" ) ].toString() );
443
444 return dsUri.encodedUri();
445}
446
447QString QgsArcGisVectorTileServiceDataProviderMetadata::absoluteToRelativeUri( const QString &uri, const QgsReadWriteContext & ) const
448{
449 QgsDataSourceUri dsUri;
450 dsUri.setEncodedUri( uri );
451 return uri;
452}
453
454QString QgsArcGisVectorTileServiceDataProviderMetadata::relativeToAbsoluteUri( const QString &uri, const QgsReadWriteContext & ) const
455{
456 QgsDataSourceUri dsUri;
457 dsUri.setEncodedUri( uri );
458 return uri;
459}
460
461QList<Qgis::LayerType> QgsArcGisVectorTileServiceDataProviderMetadata::supportedLayerTypes() const
462{
464}
465
467
468
@ ReadLayerMetadata
Provider can read layer metadata from data store. See QgsDataProvider::layerMetadata()
@ AlwaysUseTileMatrixSetFromProvider
Vector tile layer must always use the tile matrix set from the data provider, and should never store,...
QFlags< DataProviderFlag > DataProviderFlags
Data provider flags.
Definition: qgis.h:1853
@ FastExtent2D
Provider's 2D extent retrieval via QgsDataProvider::extent() is always guaranteed to be trivial/fast ...
QFlags< VectorTileProviderCapability > VectorTileProviderCapabilities
Vector tile data provider capabilities.
Definition: qgis.h:4518
@ VectorTile
Vector tile layer. Added in QGIS 3.14.
QFlags< VectorTileProviderFlag > VectorTileProviderFlags
Vector tile data provider flags.
Definition: qgis.h:4500
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
static QgsCoordinateReferenceSystem convertSpatialReference(const QVariantMap &spatialReferenceMap)
Converts a spatial reference JSON definition to a QgsCoordinateReferenceSystem value.
A thread safe class for performing blocking (sync) network requests, with full support for QGIS proxy...
@ NetworkError
A network error occurred.
@ ServerExceptionError
An exception was raised by the server.
@ NoError
No error was encountered.
@ TimeoutError
Timeout was reached before a reply was received.
This class represents a coordinate reference system (CRS).
Class for doing transforms between two map coordinate systems.
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:67
QFlags< ReadFlag > ReadFlags
Class for storing the component parts of a RDBMS data source URI (e.g.
QByteArray encodedUri() const
Returns the complete encoded URI as a byte array.
bool hasParam(const QString &key) const
Returns true if a parameter with the specified key exists.
void setEncodedUri(const QByteArray &uri)
Sets the complete encoded uri.
void setAuthConfigId(const QString &authcfg)
Sets the authentication configuration ID for the URI.
QgsHttpHeaders httpHeaders() const
Returns http headers.
QString param(const QString &key) const
Returns a generic parameter value corresponding to the specified key.
void setParam(const QString &key, const QString &value)
Sets a generic parameter value on the URI.
QString authConfigId() const
Returns any associated authentication configuration ID stored in the URI.
bool updateMap(QVariantMap &map) const
Updates a map by adding all the HTTP headers.
void setFromMap(const QVariantMap &map)
Loads headers from the map.
A structured metadata store for a map layer.
Encapsulates a network reply within a container which is inexpensive to copy and safe to pass between...
QByteArray content() const
Returns the reply content.
Holds data provider key, description, and associated shared library file or function pointer informat...
QFlags< ProviderCapability > ProviderCapabilities
The class is used as a container of context for various read/write operations on other objects.
A rectangle specified with double values.
Definition: qgsrectangle.h:42
Base class for vector tile layer data providers.
Encapsulates properties of a vector tile matrix set, including tile origins and scaling information.
static bool checkXYZUrlTemplate(const QString &url)
Checks whether the URL template string is correct (contains {x}, {y} / {-y}, {z} placeholders)
#define QgsDebugError(str)
Definition: qgslogger.h:38
#define QgsSetRequestInitiatorClass(request, _class)
#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS
const QgsCoordinateReferenceSystem & crs
Setting options for creating vector data providers.