QGIS API Documentation 3.34.0-Prizren (ffbdd678812)
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, ReadFlags 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::VectorTileProviderFlags QgsArcGisVectorTileServiceDataProvider::providerFlags() const
76{
77 return QgsXyzVectorTileDataProviderBase::providerFlags() | Qgis::VectorTileProviderFlag::AlwaysUseTileMatrixSetFromProvider;
78}
79
80Qgis::VectorTileProviderCapabilities QgsArcGisVectorTileServiceDataProvider::providerCapabilities() const
81{
83}
84
85QString QgsArcGisVectorTileServiceDataProvider::name() const
86{
88
89 return ARCGIS_VT_SERVICE_DATA_PROVIDER_KEY;
90}
91
92QString QgsArcGisVectorTileServiceDataProvider::description() const
93{
95
96 return ARCGIS_VT_SERVICE_DATA_PROVIDER_DESCRIPTION;
97}
98
99QgsVectorTileDataProvider *QgsArcGisVectorTileServiceDataProvider::clone() const
100{
102
103 return new QgsArcGisVectorTileServiceDataProvider( *this );
104}
105
106QString QgsArcGisVectorTileServiceDataProvider::sourcePath() const
107{
109
110 return mSourcePath;
111}
112
113bool QgsArcGisVectorTileServiceDataProvider::isValid() const
114{
116
117 return mIsValid;
118}
119
120QgsRectangle QgsArcGisVectorTileServiceDataProvider::extent() const
121{
123
124 return mExtent;
125}
126
127const QgsVectorTileMatrixSet &QgsArcGisVectorTileServiceDataProvider::tileMatrixSet() const
128{
130
131 return mMatrixSet;
132}
133
134QgsCoordinateReferenceSystem QgsArcGisVectorTileServiceDataProvider::crs() const
135{
137
138 return mCrs;
139}
140
141QgsLayerMetadata QgsArcGisVectorTileServiceDataProvider::layerMetadata() const
142{
144
145 return mLayerMetadata;
146}
147
148QVariantMap QgsArcGisVectorTileServiceDataProvider::styleDefinition() const
149{
151
152 return mArcgisStyleConfiguration;
153}
154
155QString QgsArcGisVectorTileServiceDataProvider::styleUrl() const
156{
158
159 // for ArcMap VectorTileServices we default to the defaultStyles URL from the layer configuration
160 return mArcgisLayerConfiguration.value( QStringLiteral( "serviceUri" ) ).toString()
161 + '/' + mArcgisLayerConfiguration.value( QStringLiteral( "defaultStyles" ) ).toString();
162}
163
164QString QgsArcGisVectorTileServiceDataProvider::htmlMetadata() const
165{
166 QString metadata;
167
168 if ( !mTileMapUrl.isEmpty() )
169 metadata += QStringLiteral( "<tr><td class=\"highlight\">" ) % tr( "Tilemap" ) % QStringLiteral( "</td><td><a href=\"%1\">%1</a>" ).arg( mTileMapUrl ) % QStringLiteral( "</td></tr>\n" );
170
171 return metadata;
172}
173
174bool QgsArcGisVectorTileServiceDataProvider::setupArcgisVectorTileServiceConnection()
175{
177
178 QgsDataSourceUri dsUri;
179 dsUri.setEncodedUri( dataSourceUri() );
180 QString tileServiceUri = dsUri.param( QStringLiteral( "url" ) );
181
182 QUrl url( tileServiceUri );
183 // some services don't default to json format, while others do... so let's explicitly request it!
184 // (refs https://github.com/qgis/QGIS/issues/4231)
185 QUrlQuery query;
186 query.addQueryItem( QStringLiteral( "f" ), QStringLiteral( "pjson" ) );
187 url.setQuery( query );
188
189 QNetworkRequest request = QNetworkRequest( url );
190
191 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsVectorTileLayer" ) )
192
193 QgsBlockingNetworkRequest networkRequest;
194 switch ( networkRequest.get( request ) )
195 {
197 break;
198
202 return false;
203 }
204
205 const QgsNetworkReplyContent content = networkRequest.reply();
206 const QByteArray raw = content.content();
207
208 // Parse data
209 QJsonParseError err;
210 const QJsonDocument doc = QJsonDocument::fromJson( raw, &err );
211 if ( doc.isNull() )
212 {
213 return false;
214 }
215
216 mArcgisLayerConfiguration = doc.object().toVariantMap();
217 if ( mArcgisLayerConfiguration.contains( QStringLiteral( "error" ) ) )
218 {
219 return false;
220 }
221
222 if ( !mArcgisLayerConfiguration.value( QStringLiteral( "tiles" ) ).isValid() )
223 {
224 // maybe url is pointing to a resources/styles/root.json type url, that's ok too!
225 const QString sourceUri = mArcgisLayerConfiguration.value( QStringLiteral( "sources" ) ).toMap().value( QStringLiteral( "esri" ) ).toMap().value( QStringLiteral( "url" ) ).toString();
226 if ( !sourceUri.isEmpty() )
227 {
228 QUrl url( sourceUri );
229 // some services don't default to json format, while others do... so let's explicitly request it!
230 // (refs https://github.com/qgis/QGIS/issues/4231)
231 QUrlQuery query;
232 query.addQueryItem( QStringLiteral( "f" ), QStringLiteral( "pjson" ) );
233 url.setQuery( query );
234
235 QNetworkRequest request = QNetworkRequest( url );
236
237 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsVectorTileLayer" ) )
238
239 QgsBlockingNetworkRequest networkRequest;
240 switch ( networkRequest.get( request ) )
241 {
243 break;
244
248 return false;
249 }
250
251 const QgsNetworkReplyContent content = networkRequest.reply();
252 const QByteArray raw = content.content();
253
254 // Parse data
255 QJsonParseError err;
256 const QJsonDocument doc = QJsonDocument::fromJson( raw, &err );
257 if ( doc.isNull() )
258 {
259 return false;
260 }
261
262 tileServiceUri = sourceUri;
263
264 // the resources/styles/root.json configuration is actually our style definition
265 mArcgisStyleConfiguration = mArcgisLayerConfiguration;
266 mArcgisLayerConfiguration = doc.object().toVariantMap();
267 if ( mArcgisLayerConfiguration.contains( QStringLiteral( "error" ) ) )
268 {
269 return false;
270 }
271 }
272 }
273
274 // read tileMap if available
275 QVariantMap tileMap;
276 const QString tileMapEndpoint = mArcgisLayerConfiguration.value( QStringLiteral( "tileMap" ) ).toString();
277 if ( !tileMapEndpoint.isEmpty() )
278 {
279 mTileMapUrl = tileServiceUri + '/' + tileMapEndpoint;
280 QUrl tilemapUrl( mTileMapUrl );
281 tilemapUrl.setQuery( query );
282
283 QNetworkRequest tileMapRequest = QNetworkRequest( tilemapUrl );
284 QgsSetRequestInitiatorClass( tileMapRequest, QStringLiteral( "QgsVectorTileLayer" ) )
285
286 QgsBlockingNetworkRequest tileMapNetworkRequest;
287 switch ( tileMapNetworkRequest.get( tileMapRequest ) )
288 {
290 break;
291
295 return false;
296 }
297
298 const QgsNetworkReplyContent tileMapContent = tileMapNetworkRequest.reply();
299 const QByteArray tileMapRaw = tileMapContent.content();
300
301 const QJsonDocument tileMapDoc = QJsonDocument::fromJson( tileMapRaw, &err );
302 if ( !tileMapDoc.isNull() )
303 {
304 tileMap = tileMapDoc.object().toVariantMap();
305 }
306 }
307
308 mSourcePath = tileServiceUri + '/' + mArcgisLayerConfiguration.value( QStringLiteral( "tiles" ) ).toList().value( 0 ).toString();
309 if ( !QgsVectorTileUtils::checkXYZUrlTemplate( mSourcePath ) )
310 {
311 QgsDebugError( QStringLiteral( "Invalid format of URL for XYZ source: " ) + tileServiceUri );
312 return false;
313 }
314
315 mArcgisLayerConfiguration.insert( QStringLiteral( "serviceUri" ), tileServiceUri );
316
317 mMatrixSet.fromEsriJson( mArcgisLayerConfiguration, tileMap );
318 mCrs = mMatrixSet.crs();
319
320 // if hardcoded zoom limits aren't specified, take them from the server
321 if ( dsUri.hasParam( QStringLiteral( "zmin" ) ) )
322 mMatrixSet.dropMatricesOutsideZoomRange( dsUri.param( QStringLiteral( "zmin" ) ).toInt(), 99 );
323
324 if ( dsUri.hasParam( QStringLiteral( "zmax" ) ) )
325 mMatrixSet.dropMatricesOutsideZoomRange( 0, dsUri.param( QStringLiteral( "zmax" ) ).toInt() );
326
327 const QVariantMap fullExtent = mArcgisLayerConfiguration.value( QStringLiteral( "fullExtent" ) ).toMap();
328 if ( !fullExtent.isEmpty() )
329 {
330 const QgsRectangle fullExtentRect(
331 fullExtent.value( QStringLiteral( "xmin" ) ).toDouble(),
332 fullExtent.value( QStringLiteral( "ymin" ) ).toDouble(),
333 fullExtent.value( QStringLiteral( "xmax" ) ).toDouble(),
334 fullExtent.value( QStringLiteral( "ymax" ) ).toDouble()
335 );
336
337 const QgsCoordinateReferenceSystem fullExtentCrs = QgsArcGisRestUtils::convertSpatialReference( fullExtent.value( QStringLiteral( "spatialReference" ) ).toMap() );
338 const QgsCoordinateTransform extentTransform( fullExtentCrs, mCrs, transformContext() );
339 try
340 {
341 mExtent = extentTransform.transformBoundingBox( fullExtentRect );
342 }
343 catch ( QgsCsException & )
344 {
345 QgsDebugError( QStringLiteral( "Could not transform layer fullExtent to layer CRS" ) );
346 }
347 }
348 else
349 {
350 // if no fullExtent specified in JSON, default to web mercator specs full extent
351 const QgsCoordinateTransform extentTransform( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3857" ) ), mCrs, transformContext() );
352 try
353 {
354 mExtent = extentTransform.transformBoundingBox( QgsRectangle( -20037508.3427892, -20037508.3427892, 20037508.3427892, 20037508.3427892 ) );
355 }
356 catch ( QgsCsException & )
357 {
358 QgsDebugError( QStringLiteral( "Could not transform layer extent to layer CRS" ) );
359 }
360 }
361
362 return true;
363}
364
365
366//
367// QgsArcGisVectorTileServiceDataProviderMetadata
368//
369
370QgsArcGisVectorTileServiceDataProviderMetadata::QgsArcGisVectorTileServiceDataProviderMetadata()
371 : QgsProviderMetadata( QgsArcGisVectorTileServiceDataProvider::ARCGIS_VT_SERVICE_DATA_PROVIDER_KEY,
372 QgsArcGisVectorTileServiceDataProvider::ARCGIS_VT_SERVICE_DATA_PROVIDER_DESCRIPTION )
373{
374}
375
376QIcon QgsArcGisVectorTileServiceDataProviderMetadata::icon() const
377{
378 return QgsApplication::getThemeIcon( QStringLiteral( "mIconVectorTileLayer.svg" ) );
379}
380
381QgsProviderMetadata::ProviderCapabilities QgsArcGisVectorTileServiceDataProviderMetadata::providerCapabilities() const
382{
383 return QgsProviderMetadata::ProviderCapabilities();
384}
385
386QgsArcGisVectorTileServiceDataProvider *QgsArcGisVectorTileServiceDataProviderMetadata::createProvider( const QString &uri, const QgsDataProvider::ProviderOptions &options, QgsDataProvider::ReadFlags flags )
387{
388 return new QgsArcGisVectorTileServiceDataProvider( uri, options, flags );
389}
390
391QVariantMap QgsArcGisVectorTileServiceDataProviderMetadata::decodeUri( const QString &uri ) const
392{
393 QgsDataSourceUri dsUri;
394 dsUri.setEncodedUri( uri );
395
396 QVariantMap uriComponents;
397 uriComponents.insert( QStringLiteral( "type" ), QStringLiteral( "xyz" ) );
398 uriComponents.insert( QStringLiteral( "serviceType" ), QStringLiteral( "arcgis" ) );
399 uriComponents.insert( QStringLiteral( "url" ), dsUri.param( QStringLiteral( "url" ) ) );
400
401 if ( dsUri.hasParam( QStringLiteral( "zmin" ) ) )
402 uriComponents.insert( QStringLiteral( "zmin" ), dsUri.param( QStringLiteral( "zmin" ) ) );
403 if ( dsUri.hasParam( QStringLiteral( "zmax" ) ) )
404 uriComponents.insert( QStringLiteral( "zmax" ), dsUri.param( QStringLiteral( "zmax" ) ) );
405
406 dsUri.httpHeaders().updateMap( uriComponents );
407
408 if ( dsUri.hasParam( QStringLiteral( "styleUrl" ) ) )
409 uriComponents.insert( QStringLiteral( "styleUrl" ), dsUri.param( QStringLiteral( "styleUrl" ) ) );
410
411 const QString authcfg = dsUri.authConfigId();
412 if ( !authcfg.isEmpty() )
413 uriComponents.insert( QStringLiteral( "authcfg" ), authcfg );
414
415 return uriComponents;
416}
417
418QString QgsArcGisVectorTileServiceDataProviderMetadata::encodeUri( const QVariantMap &parts ) const
419{
420 QgsDataSourceUri dsUri;
421 dsUri.setParam( QStringLiteral( "type" ), QStringLiteral( "xyz" ) );
422 dsUri.setParam( QStringLiteral( "serviceType" ), QStringLiteral( "arcgis" ) );
423 dsUri.setParam( QStringLiteral( "url" ), parts.value( QStringLiteral( "url" ) ).toString() );
424
425 if ( parts.contains( QStringLiteral( "zmin" ) ) )
426 dsUri.setParam( QStringLiteral( "zmin" ), parts[ QStringLiteral( "zmin" ) ].toString() );
427 if ( parts.contains( QStringLiteral( "zmax" ) ) )
428 dsUri.setParam( QStringLiteral( "zmax" ), parts[ QStringLiteral( "zmax" ) ].toString() );
429
430 dsUri.httpHeaders().setFromMap( parts );
431
432 if ( parts.contains( QStringLiteral( "styleUrl" ) ) )
433 dsUri.setParam( QStringLiteral( "styleUrl" ), parts[ QStringLiteral( "styleUrl" ) ].toString() );
434
435 if ( parts.contains( QStringLiteral( "authcfg" ) ) )
436 dsUri.setAuthConfigId( parts[ QStringLiteral( "authcfg" ) ].toString() );
437
438 return dsUri.encodedUri();
439}
440
441QString QgsArcGisVectorTileServiceDataProviderMetadata::absoluteToRelativeUri( const QString &uri, const QgsReadWriteContext & ) const
442{
443 QgsDataSourceUri dsUri;
444 dsUri.setEncodedUri( uri );
445 return uri;
446}
447
448QString QgsArcGisVectorTileServiceDataProviderMetadata::relativeToAbsoluteUri( const QString &uri, const QgsReadWriteContext & ) const
449{
450 QgsDataSourceUri dsUri;
451 dsUri.setEncodedUri( uri );
452 return uri;
453}
454
455QList<Qgis::LayerType> QgsArcGisVectorTileServiceDataProviderMetadata::supportedLayerTypes() const
456{
458}
459
461
462
@ 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,...
@ VectorTile
Vector tile layer. Added in QGIS 3.14.
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...
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.