QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
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() + '/' + mArcgisLayerConfiguration.value( u"defaultStyles"_s ).toString();
172}
173
174QString QgsArcGisVectorTileServiceDataProvider::htmlMetadata() const
175{
176 QString metadata;
177
178 if ( !mTileMapUrl.isEmpty() )
179 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;
180
181 return metadata;
182}
183
184bool QgsArcGisVectorTileServiceDataProvider::setupArcgisVectorTileServiceConnection()
185{
187
188 QgsDataSourceUri dsUri;
189 dsUri.setEncodedUri( dataSourceUri() );
190 QString tileServiceUri = dsUri.param( u"url"_s );
191
192 QUrl url( tileServiceUri );
193 // some services don't default to json format, while others do... so let's explicitly request it!
194 // (refs https://github.com/qgis/QGIS/issues/4231)
195 QUrlQuery query;
196 query.addQueryItem( u"f"_s, u"pjson"_s );
197 url.setQuery( query );
198
199 QNetworkRequest request = QNetworkRequest( url );
200
201 QgsSetRequestInitiatorClass( request, u"QgsVectorTileLayer"_s )
202
203 QgsBlockingNetworkRequest networkRequest;
204 switch ( networkRequest.get( request ) )
205 {
207 break;
208
212 return false;
213 }
214
215 const QgsNetworkReplyContent content = networkRequest.reply();
216 const QByteArray raw = content.content();
217
218 // Parse data
219 QJsonParseError err;
220 const QJsonDocument doc = QJsonDocument::fromJson( raw, &err );
221 if ( doc.isNull() )
222 {
223 return false;
224 }
225
226 mArcgisLayerConfiguration = doc.object().toVariantMap();
227 if ( mArcgisLayerConfiguration.contains( u"error"_s ) )
228 {
229 return false;
230 }
231
232 if ( !mArcgisLayerConfiguration.value( u"tiles"_s ).isValid() )
233 {
234 // maybe url is pointing to a resources/styles/root.json type url, that's ok too!
235 const QString sourceUri = mArcgisLayerConfiguration.value( u"sources"_s ).toMap().value( u"esri"_s ).toMap().value( u"url"_s ).toString();
236 if ( !sourceUri.isEmpty() )
237 {
238 QUrl url( sourceUri );
239 // some services don't default to json format, while others do... so let's explicitly request it!
240 // (refs https://github.com/qgis/QGIS/issues/4231)
241 QUrlQuery query;
242 query.addQueryItem( u"f"_s, u"pjson"_s );
243 url.setQuery( query );
244
245 QNetworkRequest request = QNetworkRequest( url );
246
247 QgsSetRequestInitiatorClass( request, u"QgsVectorTileLayer"_s )
248
249 QgsBlockingNetworkRequest networkRequest;
250 switch ( networkRequest.get( request ) )
251 {
253 break;
254
258 return false;
259 }
260
261 const QgsNetworkReplyContent content = networkRequest.reply();
262 const QByteArray raw = content.content();
263
264 // Parse data
265 QJsonParseError err;
266 const QJsonDocument doc = QJsonDocument::fromJson( raw, &err );
267 if ( doc.isNull() )
268 {
269 return false;
270 }
271
272 tileServiceUri = sourceUri;
273
274 // the resources/styles/root.json configuration is actually our style definition
275 mArcgisStyleConfiguration = mArcgisLayerConfiguration;
276 mArcgisLayerConfiguration = doc.object().toVariantMap();
277 if ( mArcgisLayerConfiguration.contains( u"error"_s ) )
278 {
279 return false;
280 }
281 }
282 }
283
284 // read tileMap if available
285 QVariantMap tileMap;
286 const QString tileMapEndpoint = mArcgisLayerConfiguration.value( u"tileMap"_s ).toString();
287 if ( !tileMapEndpoint.isEmpty() )
288 {
289 mTileMapUrl = tileServiceUri + '/' + tileMapEndpoint;
290 QUrl tilemapUrl( mTileMapUrl );
291 tilemapUrl.setQuery( query );
292
293 QNetworkRequest tileMapRequest = QNetworkRequest( tilemapUrl );
294 QgsSetRequestInitiatorClass( tileMapRequest, u"QgsVectorTileLayer"_s )
295
296 QgsBlockingNetworkRequest tileMapNetworkRequest;
297 switch ( tileMapNetworkRequest.get( tileMapRequest ) )
298 {
300 break;
301
305 return false;
306 }
307
308 const QgsNetworkReplyContent tileMapContent = tileMapNetworkRequest.reply();
309 const QByteArray tileMapRaw = tileMapContent.content();
310
311 const QJsonDocument tileMapDoc = QJsonDocument::fromJson( tileMapRaw, &err );
312 if ( !tileMapDoc.isNull() )
313 {
314 tileMap = tileMapDoc.object().toVariantMap();
315 }
316 }
317
318 mSourcePath = tileServiceUri + '/' + mArcgisLayerConfiguration.value( u"tiles"_s ).toList().value( 0 ).toString();
319 if ( !QgsVectorTileUtils::checkXYZUrlTemplate( mSourcePath ) )
320 {
321 QgsDebugError( u"Invalid format of URL for XYZ source: "_s + tileServiceUri );
322 return false;
323 }
324
325 mArcgisLayerConfiguration.insert( u"serviceUri"_s, tileServiceUri );
326
327 mMatrixSet.fromEsriJson( mArcgisLayerConfiguration, tileMap );
328 mCrs = mMatrixSet.crs();
329
330 // if hardcoded zoom limits aren't specified, take them from the server
331 if ( dsUri.hasParam( u"zmin"_s ) )
332 mMatrixSet.dropMatricesOutsideZoomRange( dsUri.param( u"zmin"_s ).toInt(), 99 );
333
334 if ( dsUri.hasParam( u"zmax"_s ) )
335 mMatrixSet.dropMatricesOutsideZoomRange( 0, dsUri.param( u"zmax"_s ).toInt() );
336
337 const QVariantMap fullExtent = mArcgisLayerConfiguration.value( u"fullExtent"_s ).toMap();
338 if ( !fullExtent.isEmpty() )
339 {
340 const QgsRectangle
341 fullExtentRect( fullExtent.value( u"xmin"_s ).toDouble(), fullExtent.value( u"ymin"_s ).toDouble(), fullExtent.value( u"xmax"_s ).toDouble(), fullExtent.value( u"ymax"_s ).toDouble() );
342
343 const QgsCoordinateReferenceSystem fullExtentCrs = QgsArcGisRestUtils::convertSpatialReference( fullExtent.value( u"spatialReference"_s ).toMap() );
344 const QgsCoordinateTransform extentTransform( fullExtentCrs, mCrs, transformContext() );
345 try
346 {
347 mExtent = extentTransform.transformBoundingBox( fullExtentRect );
348 }
349 catch ( QgsCsException & )
350 {
351 QgsDebugError( u"Could not transform layer fullExtent to layer CRS"_s );
352 }
353 }
354 else
355 {
356 // if no fullExtent specified in JSON, default to web mercator specs full extent
357 const QgsCoordinateTransform extentTransform( QgsCoordinateReferenceSystem( u"EPSG:3857"_s ), 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( u"Could not transform layer extent to layer CRS"_s );
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, QgsArcGisVectorTileServiceDataProvider::ARCGIS_VT_SERVICE_DATA_PROVIDER_DESCRIPTION )
378{}
379
380QIcon QgsArcGisVectorTileServiceDataProviderMetadata::icon() const
381{
382 return QgsApplication::getThemeIcon( u"mIconVectorTileLayer.svg"_s );
383}
384
385QgsProviderMetadata::ProviderCapabilities QgsArcGisVectorTileServiceDataProviderMetadata::providerCapabilities() const
386{
388}
389
390QgsArcGisVectorTileServiceDataProvider *QgsArcGisVectorTileServiceDataProviderMetadata::createProvider( const QString &uri, const QgsDataProvider::ProviderOptions &options, Qgis::DataProviderReadFlags flags )
391{
392 return new QgsArcGisVectorTileServiceDataProvider( uri, options, flags );
393}
394
395QVariantMap QgsArcGisVectorTileServiceDataProviderMetadata::decodeUri( const QString &uri ) const
396{
397 QgsDataSourceUri dsUri;
398 dsUri.setEncodedUri( uri );
399
400 QVariantMap uriComponents;
401 uriComponents.insert( u"type"_s, u"xyz"_s );
402 uriComponents.insert( u"serviceType"_s, u"arcgis"_s );
403 uriComponents.insert( u"url"_s, dsUri.param( u"url"_s ) );
404
405 if ( dsUri.hasParam( u"zmin"_s ) )
406 uriComponents.insert( u"zmin"_s, dsUri.param( u"zmin"_s ) );
407 if ( dsUri.hasParam( u"zmax"_s ) )
408 uriComponents.insert( u"zmax"_s, dsUri.param( u"zmax"_s ) );
409
410 dsUri.httpHeaders().updateMap( uriComponents );
411
412 if ( dsUri.hasParam( u"styleUrl"_s ) )
413 uriComponents.insert( u"styleUrl"_s, dsUri.param( u"styleUrl"_s ) );
414
415 const QString authcfg = dsUri.authConfigId();
416 if ( !authcfg.isEmpty() )
417 uriComponents.insert( u"authcfg"_s, authcfg );
418
419 return uriComponents;
420}
421
422QString QgsArcGisVectorTileServiceDataProviderMetadata::encodeUri( const QVariantMap &parts ) const
423{
424 QgsDataSourceUri dsUri;
425 dsUri.setParam( u"type"_s, u"xyz"_s );
426 dsUri.setParam( u"serviceType"_s, u"arcgis"_s );
427 dsUri.setParam( u"url"_s, parts.value( u"url"_s ).toString() );
428
429 if ( parts.contains( u"zmin"_s ) )
430 dsUri.setParam( u"zmin"_s, parts[u"zmin"_s].toString() );
431 if ( parts.contains( u"zmax"_s ) )
432 dsUri.setParam( u"zmax"_s, parts[u"zmax"_s].toString() );
433
434 dsUri.httpHeaders().setFromMap( parts );
435
436 if ( parts.contains( u"styleUrl"_s ) )
437 dsUri.setParam( u"styleUrl"_s, parts[u"styleUrl"_s].toString() );
438
439 if ( parts.contains( u"authcfg"_s ) )
440 dsUri.setAuthConfigId( parts[u"authcfg"_s].toString() );
441
442 return dsUri.encodedUri();
443}
444
445QString QgsArcGisVectorTileServiceDataProviderMetadata::absoluteToRelativeUri( const QString &uri, const QgsReadWriteContext & ) const
446{
447 QgsDataSourceUri dsUri;
448 dsUri.setEncodedUri( uri );
449 return uri;
450}
451
452QString QgsArcGisVectorTileServiceDataProviderMetadata::relativeToAbsoluteUri( const QString &uri, const QgsReadWriteContext & ) const
453{
454 QgsDataSourceUri dsUri;
455 dsUri.setEncodedUri( uri );
456 return uri;
457}
458
459QList<Qgis::LayerType> QgsArcGisVectorTileServiceDataProviderMetadata::supportedLayerTypes() const
460{
462}
463
@ ReadLayerMetadata
Provider can read layer metadata from data store. See QgsDataProvider::layerMetadata().
Definition qgis.h:5945
@ AlwaysUseTileMatrixSetFromProvider
Vector tile layer must always use the tile matrix set from the data provider, and should never store,...
Definition qgis.h:5927
QFlags< DataProviderFlag > DataProviderFlags
Data provider flags.
Definition qgis.h:2397
@ FastExtent2D
Provider's 2D extent retrieval via QgsDataProvider::extent() is always guaranteed to be trivial/fast ...
Definition qgis.h:2392
QFlags< DataProviderReadFlag > DataProviderReadFlags
Flags which control data provider construction.
Definition qgis.h:512
QFlags< VectorTileProviderCapability > VectorTileProviderCapabilities
Vector tile data provider capabilities.
Definition qgis.h:5954
@ VectorTile
Vector tile layer. Added in QGIS 3.14.
Definition qgis.h:211
QFlags< VectorTileProviderFlag > VectorTileProviderFlags
Vector tile data provider flags.
Definition qgis.h:5936
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.