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