QGIS API Documentation 3.31.0-Master (9f23a2c1dc)
qgsmbtilesvectortiledataprovider.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsmbtilesvectortiledataprovider.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 "qgsmbtiles.h"
19#include "qgstiles.h"
20#include "qgsvectortileloader.h"
21#include "qgsziputils.h"
22#include "qgslogger.h"
23#include "qgsapplication.h"
25#include "qgsproviderutils.h"
27
28#include <QIcon>
29#include <QFileInfo>
30
32
33QString QgsMbTilesVectorTileDataProvider::MB_TILES_VECTOR_TILE_DATA_PROVIDER_KEY = QStringLiteral( "mbtilesvectortiles" );
34QString QgsMbTilesVectorTileDataProvider::MB_TILES_VECTOR_TILE_DATA_PROVIDER_DESCRIPTION = QObject::tr( "MBTile Vector Tiles data provider" );
35
36QgsMbTilesVectorTileDataProvider::QgsMbTilesVectorTileDataProvider( const QString &uri, const ProviderOptions &providerOptions, ReadFlags flags )
37 : QgsVectorTileDataProvider( uri, providerOptions, flags )
38{
39 QgsDataSourceUri dsUri;
40 dsUri.setEncodedUri( uri );
41 const QString sourcePath = dsUri.param( QStringLiteral( "url" ) );
42
43 QgsMbTiles reader( sourcePath );
44 if ( !reader.open() )
45 {
46 QgsDebugError( QStringLiteral( "failed to open MBTiles file: " ) + sourcePath );
47 mIsValid = false;
48 return;
49 }
50
51 const QString format = reader.metadataValue( QStringLiteral( "format" ) );
52 if ( format != QLatin1String( "pbf" ) )
53 {
54 QgsDebugError( QStringLiteral( "Cannot open MBTiles for vector tiles. Format = " ) + format );
55 mIsValid = false;
56 return;
57 }
58
59 QgsDebugMsgLevel( QStringLiteral( "name: " ) + reader.metadataValue( QStringLiteral( "name" ) ), 2 );
60
62
63 bool minZoomOk, maxZoomOk;
64 const int minZoom = reader.metadataValue( QStringLiteral( "minzoom" ) ).toInt( &minZoomOk );
65 const int maxZoom = reader.metadataValue( QStringLiteral( "maxzoom" ) ).toInt( &maxZoomOk );
66 if ( minZoomOk )
67 mMatrixSet.dropMatricesOutsideZoomRange( minZoom, 99 );
68 if ( maxZoomOk )
69 mMatrixSet.dropMatricesOutsideZoomRange( 0, maxZoom );
70 QgsDebugMsgLevel( QStringLiteral( "zoom range: %1 - %2" ).arg( mMatrixSet.minimumZoom() ).arg( mMatrixSet.maximumZoom() ), 2 );
71
72 QgsRectangle r = reader.extent();
73 QgsCoordinateTransform ct( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ),
74 QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3857" ) ), transformContext() );
75 ct.setBallparkTransformsAreAppropriate( true );
76 try
77 {
78 mExtent = ct.transformBoundingBox( r );
79 }
80 catch ( QgsCsException & )
81 {
82 QgsDebugError( QStringLiteral( "Could not transform layer extent to layer CRS" ) );
83 }
84
85 mIsValid = true;
86}
87
88QgsMbTilesVectorTileDataProvider::QgsMbTilesVectorTileDataProvider( const QgsMbTilesVectorTileDataProvider &other )
90{
91 mIsValid = other.mIsValid;
92 mExtent = other.mExtent;
93 mMatrixSet = other.mMatrixSet;
94}
95
96QString QgsMbTilesVectorTileDataProvider::name() const
97{
99
100 return MB_TILES_VECTOR_TILE_DATA_PROVIDER_KEY;
101}
102
103QString QgsMbTilesVectorTileDataProvider::description() const
104{
106
107 return MB_TILES_VECTOR_TILE_DATA_PROVIDER_DESCRIPTION;
108}
109
110QgsVectorTileDataProvider *QgsMbTilesVectorTileDataProvider::clone() const
111{
113 return new QgsMbTilesVectorTileDataProvider( *this );
114}
115
116QString QgsMbTilesVectorTileDataProvider::sourcePath() const
117{
119
120 QgsDataSourceUri dsUri;
121 dsUri.setEncodedUri( dataSourceUri() );
122 return dsUri.param( QStringLiteral( "url" ) );
123}
124
125bool QgsMbTilesVectorTileDataProvider::isValid() const
126{
128
129 return mIsValid;
130}
131
132QgsRectangle QgsMbTilesVectorTileDataProvider::extent() const
133{
135
136 return mExtent;
137}
138
140{
142
143 return QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3857" ) );
144}
145
146const QgsVectorTileMatrixSet &QgsMbTilesVectorTileDataProvider::tileMatrixSet() const
147{
149
150 return mMatrixSet;
151}
152
153QgsVectorTileRawData QgsMbTilesVectorTileDataProvider::readTile( const QgsTileMatrixSet &, const QgsTileXYZ &id, QgsFeedback *feedback ) const
154{
156
157 QgsDataSourceUri dsUri;
158 dsUri.setEncodedUri( dataSourceUri() );
159
160 QgsMbTiles mbReader( dsUri.param( QStringLiteral( "url" ) ) );
161 mbReader.open();
162 return QgsVectorTileRawData( id, loadFromMBTiles( mbReader, id, feedback ) );
163}
164
165QList<QgsVectorTileRawData> QgsMbTilesVectorTileDataProvider::readTiles( const QgsTileMatrixSet &, const QVector<QgsTileXYZ> &tiles, QgsFeedback *feedback ) const
166{
168
169 QgsDataSourceUri dsUri;
170 dsUri.setEncodedUri( dataSourceUri() );
171
172 QgsMbTiles mbReader( dsUri.param( QStringLiteral( "url" ) ) );
173 mbReader.open();
174
175 QList<QgsVectorTileRawData> rawTiles;
176 rawTiles.reserve( tiles.size() );
177 for ( QgsTileXYZ id : std::as_const( tiles ) )
178 {
179 if ( feedback && feedback->isCanceled() )
180 break;
181
182 const QByteArray rawData = loadFromMBTiles( mbReader, id, feedback );
183 if ( !rawData.isEmpty() )
184 {
185 rawTiles.append( QgsVectorTileRawData( id, rawData ) );
186 }
187 }
188 return rawTiles;
189}
190
191QByteArray QgsMbTilesVectorTileDataProvider::loadFromMBTiles( QgsMbTiles &mbTileReader, const QgsTileXYZ &id, QgsFeedback *feedback )
192{
193 // MBTiles uses TMS specs with Y starting at the bottom while XYZ uses Y starting at the top
194 const int rowTMS = static_cast<int>( pow( 2, id.zoomLevel() ) - id.row() - 1 );
195 QByteArray gzippedTileData = mbTileReader.tileData( id.zoomLevel(), id.column(), rowTMS );
196 if ( gzippedTileData.isEmpty() )
197 {
198 return QByteArray();
199 }
200
201 if ( feedback && feedback->isCanceled() )
202 return QByteArray();
203
204 QByteArray data;
205 if ( !QgsZipUtils::decodeGzip( gzippedTileData, data ) )
206 {
207 QgsDebugError( QStringLiteral( "Failed to decompress tile " ) + id.toString() );
208 return QByteArray();
209 }
210
211 QgsDebugMsgLevel( QStringLiteral( "Tile blob size %1 -> uncompressed size %2" ).arg( gzippedTileData.size() ).arg( data.size() ), 2 );
212 return data;
213}
214
215
216//
217// QgsMbTilesVectorTileDataProviderMetadata
218//
219
220QgsMbTilesVectorTileDataProviderMetadata::QgsMbTilesVectorTileDataProviderMetadata()
221 : QgsProviderMetadata( QgsMbTilesVectorTileDataProvider::MB_TILES_VECTOR_TILE_DATA_PROVIDER_KEY,
222 QgsMbTilesVectorTileDataProvider::MB_TILES_VECTOR_TILE_DATA_PROVIDER_DESCRIPTION )
223{
224}
225
226QgsProviderMetadata::ProviderMetadataCapabilities QgsMbTilesVectorTileDataProviderMetadata::capabilities() const
227{
228 return ProviderMetadataCapability::LayerTypesForUri
229 | ProviderMetadataCapability::PriorityForUri
230 | ProviderMetadataCapability::QuerySublayers;
231}
232
233QgsMbTilesVectorTileDataProvider *QgsMbTilesVectorTileDataProviderMetadata::createProvider( const QString &uri, const QgsDataProvider::ProviderOptions &options, QgsDataProvider::ReadFlags flags )
234{
235 return new QgsMbTilesVectorTileDataProvider( uri, options, flags );
236}
237
238QIcon QgsMbTilesVectorTileDataProviderMetadata::icon() const
239{
240 return QgsApplication::getThemeIcon( QStringLiteral( "mIconVectorTileLayer.svg" ) );
241}
242
243QgsProviderMetadata::ProviderCapabilities QgsMbTilesVectorTileDataProviderMetadata::providerCapabilities() const
244{
245 return FileBasedUris;
246}
247
248QString QgsMbTilesVectorTileDataProviderMetadata::filters( Qgis::FileFilterType type )
249{
250 switch ( type )
251 {
252 case Qgis::FileFilterType::Vector:
253 case Qgis::FileFilterType::Raster:
254 case Qgis::FileFilterType::Mesh:
255 case Qgis::FileFilterType::MeshDataset:
256 case Qgis::FileFilterType::PointCloud:
257 return QString();
258
260 return QObject::tr( "Mbtiles Vector Tiles" ) + QStringLiteral( " (*.mbtiles *.MBTILES)" );
261 }
262 return QString();
263}
264
265QList<QgsProviderSublayerDetails> QgsMbTilesVectorTileDataProviderMetadata::querySublayers( const QString &uri, Qgis::SublayerQueryFlags flags, QgsFeedback * ) const
266{
267 QString fileName;
268 const QFileInfo fi( uri );
269 if ( fi.isFile() )
270 {
271 fileName = uri;
272 }
273 else
274 {
275 const QVariantMap parts = decodeUri( uri );
276 fileName = parts.value( QStringLiteral( "path" ) ).toString();
277 }
278
279 if ( fileName.isEmpty() )
280 return {};
281
282 if ( QFileInfo( fileName ).suffix().compare( QLatin1String( "mbtiles" ), Qt::CaseInsensitive ) == 0 )
283 {
284 QVariantMap parts;
285 parts.insert( QStringLiteral( "path" ), fileName );
286
288 {
289 // fast scan -- assume vector tile are available
291 details.setUri( encodeUri( parts ) );
292 details.setProviderKey( key() );
293 details.setType( Qgis::LayerType::VectorTile );
294 details.setSkippedContainerScan( true );
296 return {details};
297 }
298 else
299 {
300 // slower scan, check actual mbtiles format
301 QgsMbTiles reader( fileName );
302 if ( reader.open() )
303 {
304 if ( reader.metadataValue( "format" ) == QLatin1String( "pbf" ) )
305 {
307 details.setUri( encodeUri( parts ) );
308 details.setProviderKey( key() );
309 details.setType( Qgis::LayerType::VectorTile );
311 return {details};
312 }
313 }
314 }
315 }
316 return {};
317}
318
319int QgsMbTilesVectorTileDataProviderMetadata::priorityForUri( const QString &uri ) const
320{
321 if ( validLayerTypesForUri( uri ).contains( Qgis::LayerType::VectorTile ) )
322 return 100;
323
324 return 0;
325}
326
327QList<Qgis::LayerType> QgsMbTilesVectorTileDataProviderMetadata::validLayerTypesForUri( const QString &uri ) const
328{
329 const QFileInfo fi( uri );
330 if ( fi.isFile() && fi.suffix().compare( QLatin1String( "mbtiles" ), Qt::CaseInsensitive ) == 0 )
331 {
332 return { Qgis::LayerType::VectorTile };
333 }
334
335 const QVariantMap parts = decodeUri( uri );
336 if ( parts.value( QStringLiteral( "path" ) ).toString().endsWith( ".mbtiles", Qt::CaseSensitivity::CaseInsensitive ) )
337 return { Qgis::LayerType::VectorTile };
338
339 return {};
340}
341
342QVariantMap QgsMbTilesVectorTileDataProviderMetadata::decodeUri( const QString &uri ) const
343{
344 QgsDataSourceUri dsUri;
345 dsUri.setEncodedUri( uri );
346
347 QVariantMap uriComponents;
348 uriComponents.insert( QStringLiteral( "type" ), QStringLiteral( "mbtiles" ) );
349 uriComponents.insert( QStringLiteral( "path" ), dsUri.param( QStringLiteral( "url" ) ) );
350
351 return uriComponents;
352}
353
354QString QgsMbTilesVectorTileDataProviderMetadata::encodeUri( const QVariantMap &parts ) const
355{
356 QgsDataSourceUri dsUri;
357 dsUri.setParam( QStringLiteral( "type" ), QStringLiteral( "mbtiles" ) );
358 dsUri.setParam( QStringLiteral( "url" ), parts.value( parts.contains( QStringLiteral( "path" ) ) ? QStringLiteral( "path" ) : QStringLiteral( "url" ) ).toString() );
359 return dsUri.encodedUri();
360}
361
362QString QgsMbTilesVectorTileDataProviderMetadata::absoluteToRelativeUri( const QString &uri, const QgsReadWriteContext &context ) const
363{
364 QVariantMap parts = decodeUri( uri );
365
366 const QString originalPath = parts.value( QStringLiteral( "path" ) ).toString();
367 parts.insert( QStringLiteral( "path" ), context.pathResolver().writePath( originalPath ) );
368
369 return encodeUri( parts );
370}
371
372QString QgsMbTilesVectorTileDataProviderMetadata::relativeToAbsoluteUri( const QString &uri, const QgsReadWriteContext &context ) const
373{
374 QVariantMap parts = decodeUri( uri );
375
376 const QString originalPath = parts.value( QStringLiteral( "path" ) ).toString();
377 parts.insert( QStringLiteral( "path" ), context.pathResolver().readPath( originalPath ) );
378
379 return encodeUri( parts );
380}
381
382QList<Qgis::LayerType> QgsMbTilesVectorTileDataProviderMetadata::supportedLayerTypes() const
383{
384 return { Qgis::LayerType::VectorTile };
385}
386
387
389
390
FileFilterType
Type of file filters.
Definition: qgis.h:929
@ VectorTile
Vector tile layers (since QGIS 3.32)
@ FastScan
Indicates that the provider must scan for sublayers using the fastest possible approach – e....
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
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.
Definition: qgsexception.h:67
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.
void setEncodedUri(const QByteArray &uri)
Sets the complete encoded uri.
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.
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition: qgsfeedback.h:45
bool isCanceled() const SIP_HOLDGIL
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:54
Utility class for reading and writing MBTiles files (which are SQLite3 databases).
Definition: qgsmbtiles.h:39
QByteArray tileData(int z, int x, int y) const
Returns raw tile data for given tile.
Definition: qgsmbtiles.cpp:143
QString writePath(const QString &filename) const
Prepare a filename to save it to the project file.
QString readPath(const QString &filename) const
Turn filename read from the project file to an absolute path.
Holds data provider key, description, and associated shared library file or function pointer informat...
Contains details about a sub layer available from a dataset.
void setUri(const QString &uri)
Sets the layer's uri.
void setType(Qgis::LayerType type)
Sets the layer type.
void setName(const QString &name)
Sets the layer's name.
void setProviderKey(const QString &key)
Sets the associated data provider key.
void setSkippedContainerScan(bool skipped)
Set to true if the layer is a potential dataset container and an in-depth scan of its contents was sk...
static QString suggestLayerNameFromFilePath(const QString &path)
Suggests a suitable layer name given only a file path.
The class is used as a container of context for various read/write operations on other objects.
const QgsPathResolver & pathResolver() const
Returns path resolver for conversion between relative and absolute paths.
A rectangle specified with double values.
Definition: qgsrectangle.h:42
Defines a set of tile matrices for multiple zoom levels.
Definition: qgstiles.h:250
Stores coordinates of a tile in a tile matrix set.
Definition: qgstiles.h:38
Base class for vector tile layer data providers.
Encapsulates properties of a vector tile matrix set, including tile origins and scaling information.
static QgsVectorTileMatrixSet fromWebMercator(int minimumZoom=0, int maximumZoom=14)
Returns a vector tile structure corresponding to the standard web mercator/GoogleCRS84Quad setup.
Keeps track of raw tile data that need to be decoded.
CORE_EXPORT bool decodeGzip(const QByteArray &bytesIn, QByteArray &bytesOut)
Decodes gzip byte stream, returns true on success.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugError(str)
Definition: qgslogger.h:38
#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS
const QgsCoordinateReferenceSystem & crs
Setting options for creating vector data providers.