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