QGIS API Documentation 4.3.0-Master (35968c35f7e)
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 sourceUrl = url.resolved( sourceUri );
239 if ( sourceUrl.toString().endsWith( '/' ) )
240 {
241 const QString sourceUrlString = sourceUrl.toString();
242 sourceUrl = QUrl( sourceUrlString.left( sourceUrlString.size() - 1 ) );
243 }
244 // some services don't default to json format, while others do... so let's explicitly request it!
245 // (refs https://github.com/qgis/QGIS/issues/4231)
246 QUrlQuery query;
247 query.addQueryItem( u"f"_s, u"pjson"_s );
248 sourceUrl.setQuery( query );
249
250 QNetworkRequest request = QNetworkRequest( sourceUrl );
251
252 QgsSetRequestInitiatorClass( request, u"QgsVectorTileLayer"_s )
253
254 QgsBlockingNetworkRequest networkRequest;
255 switch ( networkRequest.get( request ) )
256 {
258 break;
259
263 return false;
264 }
265
266 const QgsNetworkReplyContent content = networkRequest.reply();
267 const QByteArray raw = content.content();
268
269 // Parse data
270 QJsonParseError err;
271 const QJsonDocument doc = QJsonDocument::fromJson( raw, &err );
272 if ( doc.isNull() )
273 {
274 return false;
275 }
276
277 sourceUrl.setQuery( QUrlQuery() );
278 tileServiceUri = sourceUrl.toString();
279
280 // the resources/styles/root.json configuration is actually our style definition
281 mArcgisStyleConfiguration = mArcgisLayerConfiguration;
282 mArcgisLayerConfiguration = doc.object().toVariantMap();
283 if ( mArcgisLayerConfiguration.contains( u"error"_s ) )
284 {
285 return false;
286 }
287 }
288 }
289
290 // read tileMap if available
291 QVariantMap tileMap;
292 const QString tileMapEndpoint = mArcgisLayerConfiguration.value( u"tileMap"_s ).toString();
293 if ( !tileMapEndpoint.isEmpty() )
294 {
295 mTileMapUrl = tileServiceUri + '/' + tileMapEndpoint;
296 QUrl tilemapUrl( mTileMapUrl );
297 tilemapUrl.setQuery( query );
298
299 QNetworkRequest tileMapRequest = QNetworkRequest( tilemapUrl );
300 QgsSetRequestInitiatorClass( tileMapRequest, u"QgsVectorTileLayer"_s )
301
302 QgsBlockingNetworkRequest tileMapNetworkRequest;
303 switch ( tileMapNetworkRequest.get( tileMapRequest ) )
304 {
306 break;
307
311 return false;
312 }
313
314 const QgsNetworkReplyContent tileMapContent = tileMapNetworkRequest.reply();
315 const QByteArray tileMapRaw = tileMapContent.content();
316
317 const QJsonDocument tileMapDoc = QJsonDocument::fromJson( tileMapRaw, &err );
318 if ( !tileMapDoc.isNull() )
319 {
320 tileMap = tileMapDoc.object().toVariantMap();
321 }
322 }
323
324 mSourcePath = tileServiceUri + '/' + mArcgisLayerConfiguration.value( u"tiles"_s ).toList().value( 0 ).toString();
325 if ( !QgsVectorTileUtils::checkXYZUrlTemplate( mSourcePath ) )
326 {
327 QgsDebugError( u"Invalid format of URL for XYZ source: "_s + tileServiceUri );
328 return false;
329 }
330
331 mArcgisLayerConfiguration.insert( u"serviceUri"_s, tileServiceUri );
332
333 mMatrixSet.fromEsriJson( mArcgisLayerConfiguration, tileMap );
334 mCrs = mMatrixSet.crs();
335
336 // if hardcoded zoom limits aren't specified, take them from the server
337 if ( dsUri.hasParam( u"zmin"_s ) )
338 mMatrixSet.dropMatricesOutsideZoomRange( dsUri.param( u"zmin"_s ).toInt(), 99 );
339
340 if ( dsUri.hasParam( u"zmax"_s ) )
341 mMatrixSet.dropMatricesOutsideZoomRange( 0, dsUri.param( u"zmax"_s ).toInt() );
342
343 const QVariantMap fullExtent = mArcgisLayerConfiguration.value( u"fullExtent"_s ).toMap();
344 if ( !fullExtent.isEmpty() )
345 {
346 const QgsRectangle
347 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() );
348
349 const QgsCoordinateReferenceSystem fullExtentCrs = QgsArcGisRestUtils::convertSpatialReference( fullExtent.value( u"spatialReference"_s ).toMap() );
350 const QgsCoordinateTransform extentTransform( fullExtentCrs, mCrs, transformContext() );
351 try
352 {
353 mExtent = extentTransform.transformBoundingBox( fullExtentRect );
354 }
355 catch ( QgsCsException & )
356 {
357 QgsDebugError( u"Could not transform layer fullExtent to layer CRS"_s );
358 }
359 }
360 else
361 {
362 // if no fullExtent specified in JSON, default to web mercator specs full extent
363 const QgsCoordinateTransform extentTransform( QgsCoordinateReferenceSystem( u"EPSG:3857"_s ), mCrs, transformContext() );
364 try
365 {
366 mExtent = extentTransform.transformBoundingBox( QgsRectangle( -20037508.3427892, -20037508.3427892, 20037508.3427892, 20037508.3427892 ) );
367 }
368 catch ( QgsCsException & )
369 {
370 QgsDebugError( u"Could not transform layer extent to layer CRS"_s );
371 }
372 }
373
374 return true;
375}
376
377
378//
379// QgsArcGisVectorTileServiceDataProviderMetadata
380//
381
382QgsArcGisVectorTileServiceDataProviderMetadata::QgsArcGisVectorTileServiceDataProviderMetadata()
383 : QgsProviderMetadata( QgsArcGisVectorTileServiceDataProvider::ARCGIS_VT_SERVICE_DATA_PROVIDER_KEY, QgsArcGisVectorTileServiceDataProvider::ARCGIS_VT_SERVICE_DATA_PROVIDER_DESCRIPTION )
384{}
385
386QIcon QgsArcGisVectorTileServiceDataProviderMetadata::icon() const
387{
388 return QgsApplication::getThemeIcon( u"mIconVectorTileLayer.svg"_s );
389}
390
391QgsProviderMetadata::ProviderCapabilities QgsArcGisVectorTileServiceDataProviderMetadata::providerCapabilities() const
392{
394}
395
396QgsArcGisVectorTileServiceDataProvider *QgsArcGisVectorTileServiceDataProviderMetadata::createProvider( const QString &uri, const QgsDataProvider::ProviderOptions &options, Qgis::DataProviderReadFlags flags )
397{
398 return new QgsArcGisVectorTileServiceDataProvider( uri, options, flags );
399}
400
401QVariantMap QgsArcGisVectorTileServiceDataProviderMetadata::decodeUri( const QString &uri ) const
402{
403 QgsDataSourceUri dsUri;
404 dsUri.setEncodedUri( uri );
405
406 QVariantMap uriComponents;
407 uriComponents.insert( u"type"_s, u"xyz"_s );
408 uriComponents.insert( u"serviceType"_s, u"arcgis"_s );
409 uriComponents.insert( u"url"_s, dsUri.param( u"url"_s ) );
410
411 if ( dsUri.hasParam( u"zmin"_s ) )
412 uriComponents.insert( u"zmin"_s, dsUri.param( u"zmin"_s ) );
413 if ( dsUri.hasParam( u"zmax"_s ) )
414 uriComponents.insert( u"zmax"_s, dsUri.param( u"zmax"_s ) );
415
416 dsUri.httpHeaders().updateMap( uriComponents );
417
418 if ( dsUri.hasParam( u"styleUrl"_s ) )
419 uriComponents.insert( u"styleUrl"_s, dsUri.param( u"styleUrl"_s ) );
420
421 const QString authcfg = dsUri.authConfigId();
422 if ( !authcfg.isEmpty() )
423 uriComponents.insert( u"authcfg"_s, authcfg );
424
425 return uriComponents;
426}
427
428QString QgsArcGisVectorTileServiceDataProviderMetadata::encodeUri( const QVariantMap &parts ) const
429{
430 QgsDataSourceUri dsUri;
431 dsUri.setParam( u"type"_s, u"xyz"_s );
432 dsUri.setParam( u"serviceType"_s, u"arcgis"_s );
433 dsUri.setParam( u"url"_s, parts.value( u"url"_s ).toString() );
434
435 if ( parts.contains( u"zmin"_s ) )
436 dsUri.setParam( u"zmin"_s, parts[u"zmin"_s].toString() );
437 if ( parts.contains( u"zmax"_s ) )
438 dsUri.setParam( u"zmax"_s, parts[u"zmax"_s].toString() );
439
440 dsUri.httpHeaders().setFromMap( parts );
441
442 if ( parts.contains( u"styleUrl"_s ) )
443 dsUri.setParam( u"styleUrl"_s, parts[u"styleUrl"_s].toString() );
444
445 if ( parts.contains( u"authcfg"_s ) )
446 dsUri.setAuthConfigId( parts[u"authcfg"_s].toString() );
447
448 return dsUri.encodedUri();
449}
450
451QString QgsArcGisVectorTileServiceDataProviderMetadata::absoluteToRelativeUri( const QString &uri, const QgsReadWriteContext & ) const
452{
453 QgsDataSourceUri dsUri;
454 dsUri.setEncodedUri( uri );
455 return uri;
456}
457
458QString QgsArcGisVectorTileServiceDataProviderMetadata::relativeToAbsoluteUri( const QString &uri, const QgsReadWriteContext & ) const
459{
460 QgsDataSourceUri dsUri;
461 dsUri.setEncodedUri( uri );
462 return uri;
463}
464
465QList<Qgis::LayerType> QgsArcGisVectorTileServiceDataProviderMetadata::supportedLayerTypes() const
466{
468}
469
@ ReadLayerMetadata
Provider can read layer metadata from data store. See QgsDataProvider::layerMetadata().
Definition qgis.h:6409
@ AlwaysUseTileMatrixSetFromProvider
Vector tile layer must always use the tile matrix set from the data provider, and should never store,...
Definition qgis.h:6391
QFlags< DataProviderFlag > DataProviderFlags
Data provider flags.
Definition qgis.h:2501
@ FastExtent2D
Provider's 2D extent retrieval via QgsDataProvider::extent() is always guaranteed to be trivial/fast ...
Definition qgis.h:2496
QFlags< DataProviderReadFlag > DataProviderReadFlags
Flags which control data provider construction.
Definition qgis.h:532
QFlags< VectorTileProviderCapability > VectorTileProviderCapabilities
Vector tile data provider capabilities.
Definition qgis.h:6418
@ VectorTile
Vector tile layer. Added in QGIS 3.14.
Definition qgis.h:211
QFlags< VectorTileProviderFlag > VectorTileProviderFlags
Vector tile data provider flags.
Definition qgis.h:6400
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:71
#define QgsSetRequestInitiatorClass(request, _class)
#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS
Setting options for creating vector data providers.