1 /***************************************************************************
2 qgsvectortilelayer.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
16 #include "qgsvectortilelayer.h"
17
18 #include "qgslogger.h"
19 #include "qgsvectortilelayerrenderer.h"
20 #include "qgsmbtiles.h"
21 #include "qgsvectortilebasiclabeling.h"
22 #include "qgsvectortilebasicrenderer.h"
23 #include "qgsvectortilelabeling.h"
24 #include "qgsvectortileloader.h"
25 #include "qgsvectortileutils.h"
26 #include "qgsnetworkaccessmanager.h"
27
28 #include "qgsdatasourceuri.h"
29 #include "qgslayermetadataformatter.h"
30 #include "qgsblockingnetworkrequest.h"
31 #include "qgsmapboxglstyleconverter.h"
32 #include "qgsjsonutils.h"
33 #include "qgspainting.h"
34 #include "qgsmaplayerfactory.h"
35
36 #include <QUrl>
37 #include <QUrlQuery>
38
QgsVectorTileLayer(const QString & uri,const QString & baseName,const LayerOptions & options)39 QgsVectorTileLayer::QgsVectorTileLayer( const QString &uri, const QString &baseName, const LayerOptions &options )
40 : QgsMapLayer( QgsMapLayerType::VectorTileLayer, baseName )
41 , mTransformContext( options.transformContext )
42 {
43 mDataSource = uri;
44
45 setValid( loadDataSource() );
46
47 // set a default renderer
48 QgsVectorTileBasicRenderer *renderer = new QgsVectorTileBasicRenderer;
49 renderer->setStyles( QgsVectorTileBasicRenderer::simpleStyleWithRandomColors() );
50 setRenderer( renderer );
51 }
52
setDataSourcePrivate(const QString & dataSource,const QString & baseName,const QString &,const QgsDataProvider::ProviderOptions &,QgsDataProvider::ReadFlags)53 void QgsVectorTileLayer::setDataSourcePrivate( const QString &dataSource, const QString &baseName, const QString &, const QgsDataProvider::ProviderOptions &, QgsDataProvider::ReadFlags )
54 {
55 mDataSource = dataSource;
56 mLayerName = baseName;
57 mDataProvider.reset();
58
59 setValid( loadDataSource() );
60 }
61
loadDataSource()62 bool QgsVectorTileLayer::loadDataSource()
63 {
64 QgsDataSourceUri dsUri;
65 dsUri.setEncodedUri( mDataSource );
66
67 mSourceType = dsUri.param( QStringLiteral( "type" ) );
68 mSourcePath = dsUri.param( QStringLiteral( "url" ) );
69 if ( mSourceType == QLatin1String( "xyz" ) && dsUri.param( QStringLiteral( "serviceType" ) ) == QLatin1String( "arcgis" ) )
70 {
71 if ( !setupArcgisVectorTileServiceConnection( mSourcePath, dsUri ) )
72 return false;
73 }
74 else if ( mSourceType == QLatin1String( "xyz" ) )
75 {
76 if ( !QgsVectorTileUtils::checkXYZUrlTemplate( mSourcePath ) )
77 {
78 QgsDebugMsg( QStringLiteral( "Invalid format of URL for XYZ source: " ) + mSourcePath );
79 return false;
80 }
81
82 // online tiles
83 mSourceMinZoom = 0;
84 mSourceMaxZoom = 14;
85
86 if ( dsUri.hasParam( QStringLiteral( "zmin" ) ) )
87 mSourceMinZoom = dsUri.param( QStringLiteral( "zmin" ) ).toInt();
88 if ( dsUri.hasParam( QStringLiteral( "zmax" ) ) )
89 mSourceMaxZoom = dsUri.param( QStringLiteral( "zmax" ) ).toInt();
90
91 setExtent( QgsRectangle( -20037508.3427892, -20037508.3427892, 20037508.3427892, 20037508.3427892 ) );
92 }
93 else if ( mSourceType == QLatin1String( "mbtiles" ) )
94 {
95 QgsMbTiles reader( mSourcePath );
96 if ( !reader.open() )
97 {
98 QgsDebugMsg( QStringLiteral( "failed to open MBTiles file: " ) + mSourcePath );
99 return false;
100 }
101
102 const QString format = reader.metadataValue( QStringLiteral( "format" ) );
103 if ( format != QLatin1String( "pbf" ) )
104 {
105 QgsDebugMsg( QStringLiteral( "Cannot open MBTiles for vector tiles. Format = " ) + format );
106 return false;
107 }
108
109 QgsDebugMsgLevel( QStringLiteral( "name: " ) + reader.metadataValue( QStringLiteral( "name" ) ), 2 );
110 bool minZoomOk, maxZoomOk;
111 const int minZoom = reader.metadataValue( QStringLiteral( "minzoom" ) ).toInt( &minZoomOk );
112 const int maxZoom = reader.metadataValue( QStringLiteral( "maxzoom" ) ).toInt( &maxZoomOk );
113 if ( minZoomOk )
114 mSourceMinZoom = minZoom;
115 if ( maxZoomOk )
116 mSourceMaxZoom = maxZoom;
117 QgsDebugMsgLevel( QStringLiteral( "zoom range: %1 - %2" ).arg( mSourceMinZoom ).arg( mSourceMaxZoom ), 2 );
118
119 QgsRectangle r = reader.extent();
120 const QgsCoordinateTransform ct( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ),
121 QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3857" ) ), transformContext() );
122 r = ct.transformBoundingBox( r );
123 setExtent( r );
124 }
125 else
126 {
127 QgsDebugMsg( QStringLiteral( "Unknown source type: " ) + mSourceType );
128 return false;
129 }
130
131 setCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3857" ) ) );
132
133 const QgsDataProvider::ProviderOptions providerOptions { mTransformContext };
134 const QgsDataProvider::ReadFlags flags;
135 mDataProvider.reset( new QgsVectorTileDataProvider( providerOptions, flags ) );
136 mProviderKey = mDataProvider->name();
137
138 return true;
139 }
140
setupArcgisVectorTileServiceConnection(const QString & uri,const QgsDataSourceUri & dataSourceUri)141 bool QgsVectorTileLayer::setupArcgisVectorTileServiceConnection( const QString &uri, const QgsDataSourceUri &dataSourceUri )
142 {
143 QUrl url( uri );
144 // some services don't default to json format, while others do... so let's explicitly request it!
145 // (refs https://github.com/qgis/QGIS/issues/4231)
146 QUrlQuery query;
147 query.addQueryItem( QStringLiteral( "f" ), QStringLiteral( "pjson" ) );
148 url.setQuery( query );
149
150 QNetworkRequest request = QNetworkRequest( url );
151
152 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsVectorTileLayer" ) )
153
154 QgsBlockingNetworkRequest networkRequest;
155 switch ( networkRequest.get( request ) )
156 {
157 case QgsBlockingNetworkRequest::NoError:
158 break;
159
160 case QgsBlockingNetworkRequest::NetworkError:
161 case QgsBlockingNetworkRequest::TimeoutError:
162 case QgsBlockingNetworkRequest::ServerExceptionError:
163 return false;
164 }
165
166 const QgsNetworkReplyContent content = networkRequest.reply();
167 const QByteArray raw = content.content();
168
169 // Parse data
170 QJsonParseError err;
171 const QJsonDocument doc = QJsonDocument::fromJson( raw, &err );
172 if ( doc.isNull() )
173 {
174 return false;
175 }
176 mArcgisLayerConfiguration = doc.object().toVariantMap();
177 if ( mArcgisLayerConfiguration.contains( QStringLiteral( "error" ) ) )
178 {
179 return false;
180 }
181
182 mArcgisLayerConfiguration.insert( QStringLiteral( "serviceUri" ), uri );
183 mSourcePath = uri + '/' + mArcgisLayerConfiguration.value( QStringLiteral( "tiles" ) ).toList().value( 0 ).toString();
184 if ( !QgsVectorTileUtils::checkXYZUrlTemplate( mSourcePath ) )
185 {
186 QgsDebugMsg( QStringLiteral( "Invalid format of URL for XYZ source: " ) + mSourcePath );
187 return false;
188 }
189
190 // if hardcoded zoom limits aren't specified, take them from the server
191 if ( !dataSourceUri.hasParam( QStringLiteral( "zmin" ) ) )
192 mSourceMinZoom = 0;
193 else
194 mSourceMinZoom = dataSourceUri.param( QStringLiteral( "zmin" ) ).toInt();
195
196 if ( !dataSourceUri.hasParam( QStringLiteral( "zmax" ) ) )
197 mSourceMaxZoom = mArcgisLayerConfiguration.value( QStringLiteral( "maxzoom" ) ).toInt();
198 else
199 mSourceMaxZoom = dataSourceUri.param( QStringLiteral( "zmax" ) ).toInt();
200
201 setExtent( QgsRectangle( -20037508.3427892, -20037508.3427892, 20037508.3427892, 20037508.3427892 ) );
202
203 return true;
204 }
205
206 QgsVectorTileLayer::~QgsVectorTileLayer() = default;
207
208
clone() const209 QgsVectorTileLayer *QgsVectorTileLayer::clone() const
210 {
211 const QgsVectorTileLayer::LayerOptions options( mTransformContext );
212 QgsVectorTileLayer *layer = new QgsVectorTileLayer( source(), name(), options );
213 layer->setRenderer( renderer() ? renderer()->clone() : nullptr );
214 return layer;
215 }
216
dataProvider()217 QgsDataProvider *QgsVectorTileLayer::dataProvider()
218 {
219 return mDataProvider.get();
220 }
221
dataProvider() const222 const QgsDataProvider *QgsVectorTileLayer::dataProvider() const
223 {
224 return mDataProvider.get();
225 }
226
createMapRenderer(QgsRenderContext & rendererContext)227 QgsMapLayerRenderer *QgsVectorTileLayer::createMapRenderer( QgsRenderContext &rendererContext )
228 {
229 return new QgsVectorTileLayerRenderer( this, rendererContext );
230 }
231
readXml(const QDomNode & layerNode,QgsReadWriteContext & context)232 bool QgsVectorTileLayer::readXml( const QDomNode &layerNode, QgsReadWriteContext &context )
233 {
234 setValid( loadDataSource() );
235
236 QString errorMsg;
237 if ( !readSymbology( layerNode, errorMsg, context ) )
238 return false;
239
240 readStyleManager( layerNode );
241 return true;
242 }
243
writeXml(QDomNode & layerNode,QDomDocument & doc,const QgsReadWriteContext & context) const244 bool QgsVectorTileLayer::writeXml( QDomNode &layerNode, QDomDocument &doc, const QgsReadWriteContext &context ) const
245 {
246 QDomElement mapLayerNode = layerNode.toElement();
247 mapLayerNode.setAttribute( QStringLiteral( "type" ), QgsMapLayerFactory::typeToString( QgsMapLayerType::VectorTileLayer ) );
248
249 // add provider node
250 if ( mDataProvider )
251 {
252 QDomElement provider = doc.createElement( QStringLiteral( "provider" ) );
253 const QDomText providerText = doc.createTextNode( providerType() );
254 provider.appendChild( providerText );
255 mapLayerNode.appendChild( provider );
256 }
257
258 writeStyleManager( layerNode, doc );
259
260 QString errorMsg;
261 return writeSymbology( layerNode, doc, errorMsg, context );
262 }
263
readSymbology(const QDomNode & node,QString & errorMessage,QgsReadWriteContext & context,QgsMapLayer::StyleCategories categories)264 bool QgsVectorTileLayer::readSymbology( const QDomNode &node, QString &errorMessage, QgsReadWriteContext &context, QgsMapLayer::StyleCategories categories )
265 {
266 const QDomElement elem = node.toElement();
267
268 readCommonStyle( elem, context, categories );
269
270 const QDomElement elemRenderer = elem.firstChildElement( QStringLiteral( "renderer" ) );
271 if ( elemRenderer.isNull() )
272 {
273 errorMessage = tr( "Missing <renderer> tag" );
274 return false;
275 }
276 const QString rendererType = elemRenderer.attribute( QStringLiteral( "type" ) );
277
278 if ( categories.testFlag( Symbology ) )
279 {
280 QgsVectorTileRenderer *r = nullptr;
281 if ( rendererType == QLatin1String( "basic" ) )
282 r = new QgsVectorTileBasicRenderer;
283 else
284 {
285 errorMessage = tr( "Unknown renderer type: " ) + rendererType;
286 return false;
287 }
288
289 r->readXml( elemRenderer, context );
290 setRenderer( r );
291 }
292
293 if ( categories.testFlag( Labeling ) )
294 {
295 setLabeling( nullptr );
296 const QDomElement elemLabeling = elem.firstChildElement( QStringLiteral( "labeling" ) );
297 if ( !elemLabeling.isNull() )
298 {
299 const QString labelingType = elemLabeling.attribute( QStringLiteral( "type" ) );
300 QgsVectorTileLabeling *labeling = nullptr;
301 if ( labelingType == QLatin1String( "basic" ) )
302 labeling = new QgsVectorTileBasicLabeling;
303 else
304 {
305 errorMessage = tr( "Unknown labeling type: " ) + rendererType;
306 }
307
308 if ( labeling )
309 {
310 labeling->readXml( elemLabeling, context );
311 setLabeling( labeling );
312 }
313 }
314 }
315
316 if ( categories.testFlag( Symbology ) )
317 {
318 // get and set the blend mode if it exists
319 const QDomNode blendModeNode = node.namedItem( QStringLiteral( "blendMode" ) );
320 if ( !blendModeNode.isNull() )
321 {
322 const QDomElement e = blendModeNode.toElement();
323 setBlendMode( QgsPainting::getCompositionMode( static_cast< QgsPainting::BlendMode >( e.text().toInt() ) ) );
324 }
325 }
326
327 // get and set the layer transparency
328 if ( categories.testFlag( Rendering ) )
329 {
330 const QDomNode layerOpacityNode = node.namedItem( QStringLiteral( "layerOpacity" ) );
331 if ( !layerOpacityNode.isNull() )
332 {
333 const QDomElement e = layerOpacityNode.toElement();
334 setOpacity( e.text().toDouble() );
335 }
336 }
337
338 return true;
339 }
340
writeSymbology(QDomNode & node,QDomDocument & doc,QString & errorMessage,const QgsReadWriteContext & context,QgsMapLayer::StyleCategories categories) const341 bool QgsVectorTileLayer::writeSymbology( QDomNode &node, QDomDocument &doc, QString &errorMessage, const QgsReadWriteContext &context, QgsMapLayer::StyleCategories categories ) const
342 {
343 Q_UNUSED( errorMessage )
344 QDomElement elem = node.toElement();
345
346 writeCommonStyle( elem, doc, context, categories );
347
348 if ( mRenderer )
349 {
350 QDomElement elemRenderer = doc.createElement( QStringLiteral( "renderer" ) );
351 elemRenderer.setAttribute( QStringLiteral( "type" ), mRenderer->type() );
352 if ( categories.testFlag( Symbology ) )
353 {
354 mRenderer->writeXml( elemRenderer, context );
355 }
356 elem.appendChild( elemRenderer );
357 }
358
359 if ( mLabeling && categories.testFlag( Labeling ) )
360 {
361 QDomElement elemLabeling = doc.createElement( QStringLiteral( "labeling" ) );
362 elemLabeling.setAttribute( QStringLiteral( "type" ), mLabeling->type() );
363 mLabeling->writeXml( elemLabeling, context );
364 elem.appendChild( elemLabeling );
365 }
366
367 if ( categories.testFlag( Symbology ) )
368 {
369 // add the blend mode field
370 QDomElement blendModeElem = doc.createElement( QStringLiteral( "blendMode" ) );
371 const QDomText blendModeText = doc.createTextNode( QString::number( QgsPainting::getBlendModeEnum( blendMode() ) ) );
372 blendModeElem.appendChild( blendModeText );
373 node.appendChild( blendModeElem );
374 }
375
376 // add the layer opacity
377 if ( categories.testFlag( Rendering ) )
378 {
379 QDomElement layerOpacityElem = doc.createElement( QStringLiteral( "layerOpacity" ) );
380 const QDomText layerOpacityText = doc.createTextNode( QString::number( opacity() ) );
381 layerOpacityElem.appendChild( layerOpacityText );
382 node.appendChild( layerOpacityElem );
383 }
384
385 return true;
386 }
387
setTransformContext(const QgsCoordinateTransformContext & transformContext)388 void QgsVectorTileLayer::setTransformContext( const QgsCoordinateTransformContext &transformContext )
389 {
390 if ( mDataProvider )
391 mDataProvider->setTransformContext( transformContext );
392
393 mTransformContext = transformContext;
394 invalidateWgs84Extent();
395 }
396
loadDefaultStyle(bool & resultFlag)397 QString QgsVectorTileLayer::loadDefaultStyle( bool &resultFlag )
398 {
399 QString error;
400 QStringList warnings;
401 resultFlag = loadDefaultStyle( error, warnings );
402 return error;
403 }
404
loadDefaultStyle(QString & error,QStringList & warnings)405 bool QgsVectorTileLayer::loadDefaultStyle( QString &error, QStringList &warnings )
406 {
407 QgsDataSourceUri dsUri;
408 dsUri.setEncodedUri( mDataSource );
409
410 QString styleUrl;
411 if ( !dsUri.param( QStringLiteral( "styleUrl" ) ).isEmpty() )
412 {
413 styleUrl = dsUri.param( QStringLiteral( "styleUrl" ) );
414 }
415 else if ( mSourceType == QLatin1String( "xyz" ) && dsUri.param( QStringLiteral( "serviceType" ) ) == QLatin1String( "arcgis" ) )
416 {
417 // for ArcMap VectorTileServices we default to the defaultStyles URL from the layer configuration
418 styleUrl = mArcgisLayerConfiguration.value( QStringLiteral( "serviceUri" ) ).toString()
419 + '/' + mArcgisLayerConfiguration.value( QStringLiteral( "defaultStyles" ) ).toString();
420 }
421
422 if ( !styleUrl.isEmpty() )
423 {
424 QNetworkRequest request = QNetworkRequest( QUrl( styleUrl ) );
425
426 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsVectorTileLayer" ) );
427
428 QgsBlockingNetworkRequest networkRequest;
429 switch ( networkRequest.get( request ) )
430 {
431 case QgsBlockingNetworkRequest::NoError:
432 break;
433
434 case QgsBlockingNetworkRequest::NetworkError:
435 case QgsBlockingNetworkRequest::TimeoutError:
436 case QgsBlockingNetworkRequest::ServerExceptionError:
437 error = QObject::tr( "Error retrieving default style" );
438 return false;
439 }
440
441 const QgsNetworkReplyContent content = networkRequest.reply();
442 const QVariantMap styleDefinition = QgsJsonUtils::parseJson( content.content() ).toMap();
443
444 QgsMapBoxGlStyleConversionContext context;
445 // convert automatically from pixel sizes to millimeters, because pixel sizes
446 // are a VERY edge case in QGIS and don't play nice with hidpi map renders or print layouts
447 context.setTargetUnit( QgsUnitTypes::RenderMillimeters );
448 //assume source uses 96 dpi
449 context.setPixelSizeConversionFactor( 25.4 / 96.0 );
450
451 if ( styleDefinition.contains( QStringLiteral( "sprite" ) ) )
452 {
453 // retrieve sprite definition
454 QString spriteUriBase;
455 if ( styleDefinition.value( QStringLiteral( "sprite" ) ).toString().startsWith( QLatin1String( "http" ) ) )
456 {
457 spriteUriBase = styleDefinition.value( QStringLiteral( "sprite" ) ).toString();
458 }
459 else
460 {
461 spriteUriBase = styleUrl + '/' + styleDefinition.value( QStringLiteral( "sprite" ) ).toString();
462 }
463
464 for ( int resolution = 2; resolution > 0; resolution-- )
465 {
466 QUrl spriteUrl = QUrl( spriteUriBase );
467 spriteUrl.setPath( spriteUrl.path() + QStringLiteral( "%1.json" ).arg( resolution > 1 ? QStringLiteral( "@%1x" ).arg( resolution ) : QString() ) );
468 QNetworkRequest request = QNetworkRequest( spriteUrl );
469 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsVectorTileLayer" ) )
470 QgsBlockingNetworkRequest networkRequest;
471 switch ( networkRequest.get( request ) )
472 {
473 case QgsBlockingNetworkRequest::NoError:
474 {
475 const QgsNetworkReplyContent content = networkRequest.reply();
476 const QVariantMap spriteDefinition = QgsJsonUtils::parseJson( content.content() ).toMap();
477
478 // retrieve sprite images
479 QUrl spriteUrl = QUrl( spriteUriBase );
480 spriteUrl.setPath( spriteUrl.path() + QStringLiteral( "%1.png" ).arg( resolution > 1 ? QStringLiteral( "@%1x" ).arg( resolution ) : QString() ) );
481 QNetworkRequest request = QNetworkRequest( spriteUrl );
482 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsVectorTileLayer" ) )
483 QgsBlockingNetworkRequest networkRequest;
484 switch ( networkRequest.get( request ) )
485 {
486 case QgsBlockingNetworkRequest::NoError:
487 {
488 const QgsNetworkReplyContent imageContent = networkRequest.reply();
489 const QImage spriteImage( QImage::fromData( imageContent.content() ) );
490 context.setSprites( spriteImage, spriteDefinition );
491 break;
492 }
493
494 case QgsBlockingNetworkRequest::NetworkError:
495 case QgsBlockingNetworkRequest::TimeoutError:
496 case QgsBlockingNetworkRequest::ServerExceptionError:
497 break;
498 }
499
500 break;
501 }
502
503 case QgsBlockingNetworkRequest::NetworkError:
504 case QgsBlockingNetworkRequest::TimeoutError:
505 case QgsBlockingNetworkRequest::ServerExceptionError:
506 break;
507 }
508
509 if ( !context.spriteDefinitions().isEmpty() )
510 break;
511 }
512 }
513
514 QgsMapBoxGlStyleConverter converter;
515 if ( converter.convert( styleDefinition, &context ) != QgsMapBoxGlStyleConverter::Success )
516 {
517 warnings = converter.warnings();
518 error = converter.errorMessage();
519 return false;
520 }
521
522 setRenderer( converter.renderer() );
523 setLabeling( converter.labeling() );
524 warnings = converter.warnings();
525 return true;
526 }
527 else
528 {
529 bool resultFlag = false;
530 error = QgsMapLayer::loadDefaultStyle( resultFlag );
531 return resultFlag;
532 }
533 }
534
loadDefaultMetadata(bool & resultFlag)535 QString QgsVectorTileLayer::loadDefaultMetadata( bool &resultFlag )
536 {
537 QgsDataSourceUri dsUri;
538 dsUri.setEncodedUri( mDataSource );
539 if ( mSourceType == QLatin1String( "xyz" ) && dsUri.param( QStringLiteral( "serviceType" ) ) == QLatin1String( "arcgis" ) )
540 {
541 // populate default metadata
542 QgsLayerMetadata metadata;
543 metadata.setIdentifier( mArcgisLayerConfiguration.value( QStringLiteral( "serviceUri" ) ).toString() );
544 const QString parentIdentifier = mArcgisLayerConfiguration.value( QStringLiteral( "serviceItemId" ) ).toString();
545 if ( !parentIdentifier.isEmpty() )
546 {
547 metadata.setParentIdentifier( parentIdentifier );
548 }
549 metadata.setType( QStringLiteral( "dataset" ) );
550 metadata.setTitle( mArcgisLayerConfiguration.value( QStringLiteral( "name" ) ).toString() );
551 const QString copyright = mArcgisLayerConfiguration.value( QStringLiteral( "copyrightText" ) ).toString();
552 if ( !copyright.isEmpty() )
553 metadata.setRights( QStringList() << copyright );
554 metadata.addLink( QgsAbstractMetadataBase::Link( tr( "Source" ), QStringLiteral( "WWW:LINK" ), mArcgisLayerConfiguration.value( QStringLiteral( "serviceUri" ) ).toString() ) );
555
556 setMetadata( metadata );
557
558 resultFlag = true;
559 return QString();
560 }
561 else
562 {
563 QgsMapLayer::loadDefaultMetadata( resultFlag );
564 resultFlag = true;
565 return QString();
566 }
567 }
568
encodedSource(const QString & source,const QgsReadWriteContext & context) const569 QString QgsVectorTileLayer::encodedSource( const QString &source, const QgsReadWriteContext &context ) const
570 {
571 QgsDataSourceUri dsUri;
572 dsUri.setEncodedUri( source );
573
574 const QString sourceType = dsUri.param( QStringLiteral( "type" ) );
575 QString sourcePath = dsUri.param( QStringLiteral( "url" ) );
576 if ( sourceType == QLatin1String( "xyz" ) )
577 {
578 const QUrl sourceUrl( sourcePath );
579 if ( sourceUrl.isLocalFile() )
580 {
581 // relative path will become "file:./x.txt"
582 const QString relSrcUrl = context.pathResolver().writePath( sourceUrl.toLocalFile() );
583 dsUri.removeParam( QStringLiteral( "url" ) ); // needed because setParam() would insert second "url" key
584 dsUri.setParam( QStringLiteral( "url" ), QUrl::fromLocalFile( relSrcUrl ).toString() );
585 return dsUri.encodedUri();
586 }
587 }
588 else if ( sourceType == QLatin1String( "mbtiles" ) )
589 {
590 sourcePath = context.pathResolver().writePath( sourcePath );
591 dsUri.removeParam( QStringLiteral( "url" ) ); // needed because setParam() would insert second "url" key
592 dsUri.setParam( QStringLiteral( "url" ), sourcePath );
593 return dsUri.encodedUri();
594 }
595
596 return source;
597 }
598
decodedSource(const QString & source,const QString & provider,const QgsReadWriteContext & context) const599 QString QgsVectorTileLayer::decodedSource( const QString &source, const QString &provider, const QgsReadWriteContext &context ) const
600 {
601 Q_UNUSED( provider )
602
603 QgsDataSourceUri dsUri;
604 dsUri.setEncodedUri( source );
605
606 const QString sourceType = dsUri.param( QStringLiteral( "type" ) );
607 QString sourcePath = dsUri.param( QStringLiteral( "url" ) );
608 if ( sourceType == QLatin1String( "xyz" ) )
609 {
610 const QUrl sourceUrl( sourcePath );
611 if ( sourceUrl.isLocalFile() ) // file-based URL? convert to relative path
612 {
613 const QString absSrcUrl = context.pathResolver().readPath( sourceUrl.toLocalFile() );
614 dsUri.removeParam( QStringLiteral( "url" ) ); // needed because setParam() would insert second "url" key
615 dsUri.setParam( QStringLiteral( "url" ), QUrl::fromLocalFile( absSrcUrl ).toString() );
616 return dsUri.encodedUri();
617 }
618 }
619 else if ( sourceType == QLatin1String( "mbtiles" ) )
620 {
621 sourcePath = context.pathResolver().readPath( sourcePath );
622 dsUri.removeParam( QStringLiteral( "url" ) ); // needed because setParam() would insert second "url" key
623 dsUri.setParam( QStringLiteral( "url" ), sourcePath );
624 return dsUri.encodedUri();
625 }
626
627 return source;
628 }
629
htmlMetadata() const630 QString QgsVectorTileLayer::htmlMetadata() const
631 {
632 const QgsLayerMetadataFormatter htmlFormatter( metadata() );
633
634 QString info = QStringLiteral( "<html><head></head>\n<body>\n" );
635
636 info += generalHtmlMetadata();
637
638 info += QStringLiteral( "<h1>" ) + tr( "Information from provider" ) + QStringLiteral( "</h1>\n<hr>\n" ) %
639 QStringLiteral( "<table class=\"list-view\">\n" );
640
641 info += QStringLiteral( "<tr><td class=\"highlight\">" ) % tr( "Source type" ) % QStringLiteral( "</td><td>" ) % sourceType() % QStringLiteral( "</td></tr>\n" );
642
643 info += QStringLiteral( "<tr><td class=\"highlight\">" ) % tr( "Zoom levels" ) % QStringLiteral( "</td><td>" ) % QStringLiteral( "%1 - %2" ).arg( sourceMinZoom() ).arg( sourceMaxZoom() ) % QStringLiteral( "</td></tr>\n" );
644
645 info += QLatin1String( "</table>\n<br>" );
646
647 // CRS
648 info += crsHtmlMetadata();
649
650 // Identification section
651 info += QStringLiteral( "<h1>" ) % tr( "Identification" ) % QStringLiteral( "</h1>\n<hr>\n" ) %
652 htmlFormatter.identificationSectionHtml() %
653 QStringLiteral( "<br>\n" ) %
654
655 // extent section
656 QStringLiteral( "<h1>" ) % tr( "Extent" ) % QStringLiteral( "</h1>\n<hr>\n" ) %
657 htmlFormatter.extentSectionHtml( ) %
658 QStringLiteral( "<br>\n" ) %
659
660 // Start the Access section
661 QStringLiteral( "<h1>" ) % tr( "Access" ) % QStringLiteral( "</h1>\n<hr>\n" ) %
662 htmlFormatter.accessSectionHtml( ) %
663 QStringLiteral( "<br>\n" ) %
664
665
666 // Start the contacts section
667 QStringLiteral( "<h1>" ) % tr( "Contacts" ) % QStringLiteral( "</h1>\n<hr>\n" ) %
668 htmlFormatter.contactsSectionHtml( ) %
669 QStringLiteral( "<br><br>\n" ) %
670
671 // Start the links section
672 QStringLiteral( "<h1>" ) % tr( "References" ) % QStringLiteral( "</h1>\n<hr>\n" ) %
673 htmlFormatter.linksSectionHtml( ) %
674 QStringLiteral( "<br>\n" ) %
675
676 // Start the history section
677 QStringLiteral( "<h1>" ) % tr( "History" ) % QStringLiteral( "</h1>\n<hr>\n" ) %
678 htmlFormatter.historySectionHtml( ) %
679 QStringLiteral( "<br>\n" ) %
680
681 QStringLiteral( "\n</body>\n</html>\n" );
682
683 return info;
684 }
685
getRawTile(QgsTileXYZ tileID)686 QByteArray QgsVectorTileLayer::getRawTile( QgsTileXYZ tileID )
687 {
688 const QgsTileMatrix tileMatrix = QgsTileMatrix::fromWebMercator( tileID.zoomLevel() );
689 const QgsTileRange tileRange( tileID.column(), tileID.column(), tileID.row(), tileID.row() );
690
691 QgsDataSourceUri dsUri;
692 dsUri.setEncodedUri( mDataSource );
693 const QString authcfg = dsUri.authConfigId();
694 const QString referer = dsUri.param( QStringLiteral( "referer" ) );
695
696 QList<QgsVectorTileRawData> rawTiles = QgsVectorTileLoader::blockingFetchTileRawData( mSourceType, mSourcePath, tileMatrix, QPointF(), tileRange, authcfg, referer );
697 if ( rawTiles.isEmpty() )
698 return QByteArray();
699 return rawTiles.first().data;
700 }
701
setRenderer(QgsVectorTileRenderer * r)702 void QgsVectorTileLayer::setRenderer( QgsVectorTileRenderer *r )
703 {
704 mRenderer.reset( r );
705 triggerRepaint();
706 }
707
renderer() const708 QgsVectorTileRenderer *QgsVectorTileLayer::renderer() const
709 {
710 return mRenderer.get();
711 }
712
setLabeling(QgsVectorTileLabeling * labeling)713 void QgsVectorTileLayer::setLabeling( QgsVectorTileLabeling *labeling )
714 {
715 mLabeling.reset( labeling );
716 triggerRepaint();
717 }
718
labeling() const719 QgsVectorTileLabeling *QgsVectorTileLayer::labeling() const
720 {
721 return mLabeling.get();
722 }
723
724
725
726 //
727 // QgsVectorTileDataProvider
728 //
729 ///@cond PRIVATE
QgsVectorTileDataProvider(const ProviderOptions & options,QgsDataProvider::ReadFlags flags)730 QgsVectorTileDataProvider::QgsVectorTileDataProvider(
731 const ProviderOptions &options,
732 QgsDataProvider::ReadFlags flags )
733 : QgsDataProvider( QString(), options, flags )
734 {}
735
crs() const736 QgsCoordinateReferenceSystem QgsVectorTileDataProvider::crs() const
737 {
738 return QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3857" ) );
739 }
740
name() const741 QString QgsVectorTileDataProvider::name() const
742 {
743 return QStringLiteral( "vectortile" );
744 }
745
description() const746 QString QgsVectorTileDataProvider::description() const
747 {
748 return QString();
749 }
750
extent() const751 QgsRectangle QgsVectorTileDataProvider::extent() const
752 {
753 return QgsRectangle();
754 }
755
isValid() const756 bool QgsVectorTileDataProvider::isValid() const
757 {
758 return true;
759 }
760 ///@endcond
761