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