QGIS API Documentation 3.99.0-Master (2fe06baccd8)
Loading...
Searching...
No Matches
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
18#include "qgsapplication.h"
19#include "qgsarcgisrestutils.h"
22#include "qgslogger.h"
24#include "qgsthreadingutils.h"
25#include "qgsvectortileutils.h"
26
27#include <QIcon>
28#include <QJsonDocument>
29#include <QJsonObject>
30#include <QJsonParseError>
31#include <QUrl>
32#include <QUrlQuery>
33
34#include "moc_qgsarcgisvectortileservicedataprovider.cpp"
35
37
38QString QgsArcGisVectorTileServiceDataProvider::ARCGIS_VT_SERVICE_DATA_PROVIDER_KEY = QStringLiteral( "arcgisvectortileservice" );
39QString QgsArcGisVectorTileServiceDataProvider::ARCGIS_VT_SERVICE_DATA_PROVIDER_DESCRIPTION = QObject::tr( "ArcGIS Vector Tile Service data provider" );
40
41
42QgsArcGisVectorTileServiceDataProvider::QgsArcGisVectorTileServiceDataProvider( const QString &uri, const ProviderOptions &providerOptions, Qgis::DataProviderReadFlags flags )
43 : QgsXyzVectorTileDataProviderBase( uri, providerOptions, flags )
44{
45 mIsValid = setupArcgisVectorTileServiceConnection();
46
47 if ( !mIsValid )
48 return;
49
50 // populate default metadata
51 mLayerMetadata.setIdentifier( mArcgisLayerConfiguration.value( QStringLiteral( "serviceUri" ) ).toString() );
52 const QString parentIdentifier = mArcgisLayerConfiguration.value( QStringLiteral( "serviceItemId" ) ).toString();
53 if ( !parentIdentifier.isEmpty() )
54 {
55 mLayerMetadata.setParentIdentifier( parentIdentifier );
56 }
57 mLayerMetadata.setType( QStringLiteral( "dataset" ) );
58 mLayerMetadata.setTitle( mArcgisLayerConfiguration.value( QStringLiteral( "name" ) ).toString() );
59 const QString copyright = mArcgisLayerConfiguration.value( QStringLiteral( "copyrightText" ) ).toString();
60 if ( !copyright.isEmpty() )
61 mLayerMetadata.setRights( QStringList() << copyright );
62 mLayerMetadata.addLink( QgsAbstractMetadataBase::Link( tr( "Source" ), QStringLiteral( "WWW:LINK" ), mArcgisLayerConfiguration.value( QStringLiteral( "serviceUri" ) ).toString() ) );
63}
64
65QgsArcGisVectorTileServiceDataProvider::QgsArcGisVectorTileServiceDataProvider( const QgsArcGisVectorTileServiceDataProvider &other )
66 : QgsXyzVectorTileDataProviderBase( other )
67{
68 mIsValid = other.mIsValid;
69 mExtent = other.mExtent;
70 mMatrixSet = other.mMatrixSet;
71 mSourcePath = other.mSourcePath;
72 mArcgisLayerConfiguration = other.mArcgisLayerConfiguration;
73 mArcgisStyleConfiguration = other.mArcgisStyleConfiguration;
74 mCrs = other.mCrs;
75 mLayerMetadata = other.mLayerMetadata;
76}
77
78Qgis::DataProviderFlags QgsArcGisVectorTileServiceDataProvider::flags() const
79{
81}
82
83Qgis::VectorTileProviderFlags QgsArcGisVectorTileServiceDataProvider::providerFlags() const
84{
85 return QgsXyzVectorTileDataProviderBase::providerFlags() | Qgis::VectorTileProviderFlag::AlwaysUseTileMatrixSetFromProvider;
86}
87
88Qgis::VectorTileProviderCapabilities QgsArcGisVectorTileServiceDataProvider::providerCapabilities() const
89{
91}
92
93QString QgsArcGisVectorTileServiceDataProvider::name() const
94{
96
97 return ARCGIS_VT_SERVICE_DATA_PROVIDER_KEY;
98}
99
100QString QgsArcGisVectorTileServiceDataProvider::description() const
101{
103
104 return ARCGIS_VT_SERVICE_DATA_PROVIDER_DESCRIPTION;
105}
106
107QgsVectorTileDataProvider *QgsArcGisVectorTileServiceDataProvider::clone() const
108{
110
111 return new QgsArcGisVectorTileServiceDataProvider( *this );
112}
113
114QString QgsArcGisVectorTileServiceDataProvider::sourcePath() const
115{
117
118 return mSourcePath;
119}
120
121bool QgsArcGisVectorTileServiceDataProvider::isValid() const
122{
124
125 return mIsValid;
126}
127
128QgsRectangle QgsArcGisVectorTileServiceDataProvider::extent() const
129{
131
132 return mExtent;
133}
134
135const QgsVectorTileMatrixSet &QgsArcGisVectorTileServiceDataProvider::tileMatrixSet() const
136{
138
139 return mMatrixSet;
140}
141
142QgsCoordinateReferenceSystem QgsArcGisVectorTileServiceDataProvider::crs() const
143{
145
146 return mCrs;
147}
148
149QgsLayerMetadata QgsArcGisVectorTileServiceDataProvider::layerMetadata() const
150{
152
153 return mLayerMetadata;
154}
155
156QVariantMap QgsArcGisVectorTileServiceDataProvider::styleDefinition() const
157{
159
160 return mArcgisStyleConfiguration;
161}
162
163QString QgsArcGisVectorTileServiceDataProvider::styleUrl() const
164{
166
167 // for ArcMap VectorTileServices we default to the defaultStyles URL from the layer configuration
168 return mArcgisLayerConfiguration.value( QStringLiteral( "serviceUri" ) ).toString()
169 + '/' + mArcgisLayerConfiguration.value( QStringLiteral( "defaultStyles" ) ).toString();
170}
171
172QString QgsArcGisVectorTileServiceDataProvider::htmlMetadata() const
173{
174 QString metadata;
175
176 if ( !mTileMapUrl.isEmpty() )
177 metadata += QStringLiteral( "<tr><td class=\"highlight\">" ) % tr( "Tilemap" ) % QStringLiteral( "</td><td><a href=\"%1\">%1</a>" ).arg( mTileMapUrl ) % QStringLiteral( "</td></tr>\n" );
178
179 return metadata;
180}
181
182bool QgsArcGisVectorTileServiceDataProvider::setupArcgisVectorTileServiceConnection()
183{
185
186 QgsDataSourceUri dsUri;
187 dsUri.setEncodedUri( dataSourceUri() );
188 QString tileServiceUri = dsUri.param( QStringLiteral( "url" ) );
189
190 QUrl url( tileServiceUri );
191 // some services don't default to json format, while others do... so let's explicitly request it!
192 // (refs https://github.com/qgis/QGIS/issues/4231)
193 QUrlQuery query;
194 query.addQueryItem( QStringLiteral( "f" ), QStringLiteral( "pjson" ) );
195 url.setQuery( query );
196
197 QNetworkRequest request = QNetworkRequest( url );
198
199 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsVectorTileLayer" ) )
200
201 QgsBlockingNetworkRequest networkRequest;
202 switch ( networkRequest.get( request ) )
203 {
205 break;
206
210 return false;
211 }
212
213 const QgsNetworkReplyContent content = networkRequest.reply();
214 const QByteArray raw = content.content();
215
216 // Parse data
217 QJsonParseError err;
218 const QJsonDocument doc = QJsonDocument::fromJson( raw, &err );
219 if ( doc.isNull() )
220 {
221 return false;
222 }
223
224 mArcgisLayerConfiguration = doc.object().toVariantMap();
225 if ( mArcgisLayerConfiguration.contains( QStringLiteral( "error" ) ) )
226 {
227 return false;
228 }
229
230 if ( !mArcgisLayerConfiguration.value( QStringLiteral( "tiles" ) ).isValid() )
231 {
232 // maybe url is pointing to a resources/styles/root.json type url, that's ok too!
233 const QString sourceUri = mArcgisLayerConfiguration.value( QStringLiteral( "sources" ) ).toMap().value( QStringLiteral( "esri" ) ).toMap().value( QStringLiteral( "url" ) ).toString();
234 if ( !sourceUri.isEmpty() )
235 {
236 QUrl url( sourceUri );
237 // some services don't default to json format, while others do... so let's explicitly request it!
238 // (refs https://github.com/qgis/QGIS/issues/4231)
239 QUrlQuery query;
240 query.addQueryItem( QStringLiteral( "f" ), QStringLiteral( "pjson" ) );
241 url.setQuery( query );
242
243 QNetworkRequest request = QNetworkRequest( url );
244
245 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsVectorTileLayer" ) )
246
247 QgsBlockingNetworkRequest networkRequest;
248 switch ( networkRequest.get( request ) )
249 {
251 break;
252
256 return false;
257 }
258
259 const QgsNetworkReplyContent content = networkRequest.reply();
260 const QByteArray raw = content.content();
261
262 // Parse data
263 QJsonParseError err;
264 const QJsonDocument doc = QJsonDocument::fromJson( raw, &err );
265 if ( doc.isNull() )
266 {
267 return false;
268 }
269
270 tileServiceUri = sourceUri;
271
272 // the resources/styles/root.json configuration is actually our style definition
273 mArcgisStyleConfiguration = mArcgisLayerConfiguration;
274 mArcgisLayerConfiguration = doc.object().toVariantMap();
275 if ( mArcgisLayerConfiguration.contains( QStringLiteral( "error" ) ) )
276 {
277 return false;
278 }
279 }
280 }
281
282 // read tileMap if available
283 QVariantMap tileMap;
284 const QString tileMapEndpoint = mArcgisLayerConfiguration.value( QStringLiteral( "tileMap" ) ).toString();
285 if ( !tileMapEndpoint.isEmpty() )
286 {
287 mTileMapUrl = tileServiceUri + '/' + tileMapEndpoint;
288 QUrl tilemapUrl( mTileMapUrl );
289 tilemapUrl.setQuery( query );
290
291 QNetworkRequest tileMapRequest = QNetworkRequest( tilemapUrl );
292 QgsSetRequestInitiatorClass( tileMapRequest, QStringLiteral( "QgsVectorTileLayer" ) )
293
294 QgsBlockingNetworkRequest tileMapNetworkRequest;
295 switch ( tileMapNetworkRequest.get( tileMapRequest ) )
296 {
298 break;
299
303 return false;
304 }
305
306 const QgsNetworkReplyContent tileMapContent = tileMapNetworkRequest.reply();
307 const QByteArray tileMapRaw = tileMapContent.content();
308
309 const QJsonDocument tileMapDoc = QJsonDocument::fromJson( tileMapRaw, &err );
310 if ( !tileMapDoc.isNull() )
311 {
312 tileMap = tileMapDoc.object().toVariantMap();
313 }
314 }
315
316 mSourcePath = tileServiceUri + '/' + mArcgisLayerConfiguration.value( QStringLiteral( "tiles" ) ).toList().value( 0 ).toString();
317 if ( !QgsVectorTileUtils::checkXYZUrlTemplate( mSourcePath ) )
318 {
319 QgsDebugError( QStringLiteral( "Invalid format of URL for XYZ source: " ) + tileServiceUri );
320 return false;
321 }
322
323 mArcgisLayerConfiguration.insert( QStringLiteral( "serviceUri" ), tileServiceUri );
324
325 mMatrixSet.fromEsriJson( mArcgisLayerConfiguration, tileMap );
326 mCrs = mMatrixSet.crs();
327
328 // if hardcoded zoom limits aren't specified, take them from the server
329 if ( dsUri.hasParam( QStringLiteral( "zmin" ) ) )
330 mMatrixSet.dropMatricesOutsideZoomRange( dsUri.param( QStringLiteral( "zmin" ) ).toInt(), 99 );
331
332 if ( dsUri.hasParam( QStringLiteral( "zmax" ) ) )
333 mMatrixSet.dropMatricesOutsideZoomRange( 0, dsUri.param( QStringLiteral( "zmax" ) ).toInt() );
334
335 const QVariantMap fullExtent = mArcgisLayerConfiguration.value( QStringLiteral( "fullExtent" ) ).toMap();
336 if ( !fullExtent.isEmpty() )
337 {
338 const QgsRectangle fullExtentRect(
339 fullExtent.value( QStringLiteral( "xmin" ) ).toDouble(),
340 fullExtent.value( QStringLiteral( "ymin" ) ).toDouble(),
341 fullExtent.value( QStringLiteral( "xmax" ) ).toDouble(),
342 fullExtent.value( QStringLiteral( "ymax" ) ).toDouble()
343 );
344
345 const QgsCoordinateReferenceSystem fullExtentCrs = QgsArcGisRestUtils::convertSpatialReference( fullExtent.value( QStringLiteral( "spatialReference" ) ).toMap() );
346 const QgsCoordinateTransform extentTransform( fullExtentCrs, mCrs, transformContext() );
347 try
348 {
349 mExtent = extentTransform.transformBoundingBox( fullExtentRect );
350 }
351 catch ( QgsCsException & )
352 {
353 QgsDebugError( QStringLiteral( "Could not transform layer fullExtent to layer CRS" ) );
354 }
355 }
356 else
357 {
358 // if no fullExtent specified in JSON, default to web mercator specs full extent
359 const QgsCoordinateTransform extentTransform( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3857" ) ), mCrs, transformContext() );
360 try
361 {
362 mExtent = extentTransform.transformBoundingBox( QgsRectangle( -20037508.3427892, -20037508.3427892, 20037508.3427892, 20037508.3427892 ) );
363 }
364 catch ( QgsCsException & )
365 {
366 QgsDebugError( QStringLiteral( "Could not transform layer extent to layer CRS" ) );
367 }
368 }
369
370 return true;
371}
372
373
374//
375// QgsArcGisVectorTileServiceDataProviderMetadata
376//
377
378QgsArcGisVectorTileServiceDataProviderMetadata::QgsArcGisVectorTileServiceDataProviderMetadata()
379 : QgsProviderMetadata( QgsArcGisVectorTileServiceDataProvider::ARCGIS_VT_SERVICE_DATA_PROVIDER_KEY,
380 QgsArcGisVectorTileServiceDataProvider::ARCGIS_VT_SERVICE_DATA_PROVIDER_DESCRIPTION )
381{
382}
383
384QIcon QgsArcGisVectorTileServiceDataProviderMetadata::icon() const
385{
386 return QgsApplication::getThemeIcon( QStringLiteral( "mIconVectorTileLayer.svg" ) );
387}
388
389QgsProviderMetadata::ProviderCapabilities QgsArcGisVectorTileServiceDataProviderMetadata::providerCapabilities() const
390{
392}
393
394QgsArcGisVectorTileServiceDataProvider *QgsArcGisVectorTileServiceDataProviderMetadata::createProvider( const QString &uri, const QgsDataProvider::ProviderOptions &options, Qgis::DataProviderReadFlags flags )
395{
396 return new QgsArcGisVectorTileServiceDataProvider( uri, options, flags );
397}
398
399QVariantMap QgsArcGisVectorTileServiceDataProviderMetadata::decodeUri( const QString &uri ) const
400{
401 QgsDataSourceUri dsUri;
402 dsUri.setEncodedUri( uri );
403
404 QVariantMap uriComponents;
405 uriComponents.insert( QStringLiteral( "type" ), QStringLiteral( "xyz" ) );
406 uriComponents.insert( QStringLiteral( "serviceType" ), QStringLiteral( "arcgis" ) );
407 uriComponents.insert( QStringLiteral( "url" ), dsUri.param( QStringLiteral( "url" ) ) );
408
409 if ( dsUri.hasParam( QStringLiteral( "zmin" ) ) )
410 uriComponents.insert( QStringLiteral( "zmin" ), dsUri.param( QStringLiteral( "zmin" ) ) );
411 if ( dsUri.hasParam( QStringLiteral( "zmax" ) ) )
412 uriComponents.insert( QStringLiteral( "zmax" ), dsUri.param( QStringLiteral( "zmax" ) ) );
413
414 dsUri.httpHeaders().updateMap( uriComponents );
415
416 if ( dsUri.hasParam( QStringLiteral( "styleUrl" ) ) )
417 uriComponents.insert( QStringLiteral( "styleUrl" ), dsUri.param( QStringLiteral( "styleUrl" ) ) );
418
419 const QString authcfg = dsUri.authConfigId();
420 if ( !authcfg.isEmpty() )
421 uriComponents.insert( QStringLiteral( "authcfg" ), authcfg );
422
423 return uriComponents;
424}
425
426QString QgsArcGisVectorTileServiceDataProviderMetadata::encodeUri( const QVariantMap &parts ) const
427{
428 QgsDataSourceUri dsUri;
429 dsUri.setParam( QStringLiteral( "type" ), QStringLiteral( "xyz" ) );
430 dsUri.setParam( QStringLiteral( "serviceType" ), QStringLiteral( "arcgis" ) );
431 dsUri.setParam( QStringLiteral( "url" ), parts.value( QStringLiteral( "url" ) ).toString() );
432
433 if ( parts.contains( QStringLiteral( "zmin" ) ) )
434 dsUri.setParam( QStringLiteral( "zmin" ), parts[ QStringLiteral( "zmin" ) ].toString() );
435 if ( parts.contains( QStringLiteral( "zmax" ) ) )
436 dsUri.setParam( QStringLiteral( "zmax" ), parts[ QStringLiteral( "zmax" ) ].toString() );
437
438 dsUri.httpHeaders().setFromMap( parts );
439
440 if ( parts.contains( QStringLiteral( "styleUrl" ) ) )
441 dsUri.setParam( QStringLiteral( "styleUrl" ), parts[ QStringLiteral( "styleUrl" ) ].toString() );
442
443 if ( parts.contains( QStringLiteral( "authcfg" ) ) )
444 dsUri.setAuthConfigId( parts[ QStringLiteral( "authcfg" ) ].toString() );
445
446 return dsUri.encodedUri();
447}
448
449QString QgsArcGisVectorTileServiceDataProviderMetadata::absoluteToRelativeUri( const QString &uri, const QgsReadWriteContext & ) const
450{
451 QgsDataSourceUri dsUri;
452 dsUri.setEncodedUri( uri );
453 return uri;
454}
455
456QString QgsArcGisVectorTileServiceDataProviderMetadata::relativeToAbsoluteUri( const QString &uri, const QgsReadWriteContext & ) const
457{
458 QgsDataSourceUri dsUri;
459 dsUri.setEncodedUri( uri );
460 return uri;
461}
462
463QList<Qgis::LayerType> QgsArcGisVectorTileServiceDataProviderMetadata::supportedLayerTypes() const
464{
466}
467
469
470
@ ReadLayerMetadata
Provider can read layer metadata from data store. See QgsDataProvider::layerMetadata().
Definition qgis.h:5589
@ AlwaysUseTileMatrixSetFromProvider
Vector tile layer must always use the tile matrix set from the data provider, and should never store,...
Definition qgis.h:5571
QFlags< DataProviderFlag > DataProviderFlags
Data provider flags.
Definition qgis.h:2315
@ FastExtent2D
Provider's 2D extent retrieval via QgsDataProvider::extent() is always guaranteed to be trivial/fast ...
Definition qgis.h:2310
QFlags< DataProviderReadFlag > DataProviderReadFlags
Flags which control data provider construction.
Definition qgis.h:486
QFlags< VectorTileProviderCapability > VectorTileProviderCapabilities
Vector tile data provider capabilities.
Definition qgis.h:5598
@ VectorTile
Vector tile layer. Added in QGIS 3.14.
Definition qgis.h:195
QFlags< VectorTileProviderFlag > VectorTileProviderFlags
Vector tile data provider flags.
Definition qgis.h:5580
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.
Represents a coordinate reference system (CRS).
Handles coordinate transforms between two coordinate systems.
Custom exception class for Coordinate Reference System related exceptions.
Stores the component parts of a 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
A container for the context for various read/write operations on objects.
A rectangle specified with double values.
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:57
#define QgsSetRequestInitiatorClass(request, _class)
#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS
Setting options for creating vector data providers.