QGIS API Documentation 3.43.0-Master (e01d6d7c4c0)
qgsalgorithmdownloadvectortiles.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsalgorithmdownloadvectortiles.cpp
3 ---------------------
4 begin : May 2023
5 copyright : (C) 2023 by Alexander Bruy
6 email : alexander dot bruy at gmail dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
19
20#include "qgsmbtiles.h"
21#include "qgsvectortileloader.h"
22#include "qgsvectortilelayer.h"
23#include "qgsziputils.h"
24
26
27class SetStylePostProcessor : public QgsProcessingLayerPostProcessorInterface
28{
29 public:
30 SetStylePostProcessor( QDomDocument &doc )
31 : mDocument( doc )
32 {}
33
35 {
36 if ( QgsVectorTileLayer *tileLayer = qobject_cast<QgsVectorTileLayer *>( layer ) )
37 {
38 QString errorMsg;
39 tileLayer->importNamedStyle( mDocument, errorMsg );
40 tileLayer->triggerRepaint();
41 }
42 }
43
44 private:
45 QDomDocument mDocument;
46};
47
48QString QgsDownloadVectorTilesAlgorithm::name() const
49{
50 return QStringLiteral( "downloadvectortiles" );
51}
52
53QString QgsDownloadVectorTilesAlgorithm::displayName() const
54{
55 return QObject::tr( "Download vector tiles" );
56}
57
58QStringList QgsDownloadVectorTilesAlgorithm::tags() const
59{
60 return QObject::tr( "vector,split,field,unique" ).split( ',' );
61}
62
63QString QgsDownloadVectorTilesAlgorithm::group() const
64{
65 return QObject::tr( "Vector tiles" );
66}
67
68QString QgsDownloadVectorTilesAlgorithm::groupId() const
69{
70 return QStringLiteral( "vectortiles" );
71}
72
73QString QgsDownloadVectorTilesAlgorithm::shortHelpString() const
74{
75 return QObject::tr( "This algorithm downloads vector tiles of the input vector tile layer and saves them in the local vector tile file." );
76}
77
78QString QgsDownloadVectorTilesAlgorithm::shortDescription() const
79{
80 return QObject::tr( "Downloads vector tiles of the input vector tile layer and saves them in the local vector tile file." );
81}
82
83QgsDownloadVectorTilesAlgorithm *QgsDownloadVectorTilesAlgorithm::createInstance() const
84{
85 return new QgsDownloadVectorTilesAlgorithm();
86}
87
88void QgsDownloadVectorTilesAlgorithm::initAlgorithm( const QVariantMap & )
89{
90 addParameter( new QgsProcessingParameterMapLayer( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ), QVariant(), false, QList<int>() << static_cast<int>( Qgis::ProcessingSourceType::VectorTile ) ) );
91 addParameter( new QgsProcessingParameterExtent( QStringLiteral( "EXTENT" ), QObject::tr( "Extent" ) ) );
92 addParameter( new QgsProcessingParameterNumber( QStringLiteral( "MAX_ZOOM" ), QObject::tr( "Maximum zoom level to download" ), Qgis::ProcessingNumberParameterType::Integer, 10, false, 0 ) );
93 addParameter( new QgsProcessingParameterNumber( QStringLiteral( "TILE_LIMIT" ), QObject::tr( "Tile limit" ), Qgis::ProcessingNumberParameterType::Integer, 100, false, 0 ) );
94 addParameter( new QgsProcessingParameterVectorTileDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Output" ) ) );
95}
96
97bool QgsDownloadVectorTilesAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
98{
99 QgsMapLayer *layer = parameterAsLayer( parameters, QStringLiteral( "INPUT" ), context );
100 if ( !layer )
101 throw QgsProcessingException( QObject::tr( "Invalid input layer" ) );
102
103 QgsVectorTileLayer *vtLayer = qobject_cast<QgsVectorTileLayer *>( layer );
104 mProvider.reset( qgis::down_cast<const QgsVectorTileDataProvider *>( vtLayer->dataProvider() )->clone() );
105 mTileMatrixSet = vtLayer->tileMatrixSet();
106 mSourceMinZoom = vtLayer->sourceMinZoom();
107 mLayerName = vtLayer->name();
108
109 mExtent = parameterAsExtent( parameters, QStringLiteral( "EXTENT" ), context, layer->crs() );
110
111 mMaxZoom = parameterAsInt( parameters, QStringLiteral( "MAX_ZOOM" ), context );
112 if ( mMaxZoom > vtLayer->sourceMaxZoom() )
113 {
114 throw QgsProcessingException( QObject::tr( "Requested maximum zoom level is bigger than available zoom level in the source layer. Please, select zoom level lower or equal to %1." ).arg( vtLayer->sourceMaxZoom() ) );
115 }
116
117 mTileLimit = static_cast<long long>( parameterAsInt( parameters, QStringLiteral( "TILE_LIMIT" ), context ) );
118
119 mStyleDocument = QDomDocument( QStringLiteral( "qgis" ) );
120 QString errorMsg;
121 vtLayer->exportNamedStyle( mStyleDocument, errorMsg );
122 if ( !errorMsg.isEmpty() )
123 {
124 feedback->pushWarning( QObject::tr( "Failed to get layer style: %1" ).arg( errorMsg ) );
125 }
126
127 return true;
128}
129
130QVariantMap QgsDownloadVectorTilesAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
131{
132 const QString outputFile = parameterAsOutputLayer( parameters, QStringLiteral( "OUTPUT" ), context );
133
134 // count total number of tiles in the requested extent and zoom levels to see if it exceeds the tile limit
135 long long tileCount = 0;
136 QMap<int, QgsTileRange> tileRanges;
137 for ( int i = 0; i <= mMaxZoom; i++ )
138 {
139 QgsTileMatrix tileMatrix = mTileMatrixSet.tileMatrix( i );
140 QgsTileRange tileRange = tileMatrix.tileRangeFromExtent( mExtent );
141 tileRanges.insert( i, tileRange );
142 tileCount += static_cast<long long>( tileRange.endColumn() - tileRange.startColumn() + 1 ) * ( tileRange.endRow() - tileRange.startRow() + 1 );
143 }
144 if ( tileCount > mTileLimit )
145 {
146 throw QgsProcessingException( QObject::tr( "Requested number of tiles %1 exceeds limit of %2 tiles. Please, select a smaller extent, reduce maximum zoom level or increase tile limit." ).arg( tileCount ).arg( mTileLimit ) );
147 }
148
149 auto writer = std::make_unique<QgsMbTiles>( outputFile );
150 if ( !writer->create() )
151 {
152 throw QgsProcessingException( QObject::tr( "Failed to create MBTiles file %1" ).arg( outputFile ) );
153 }
154 writer->setMetadataValue( "format", "pbf" );
155 writer->setMetadataValue( "name", mLayerName );
156 writer->setMetadataValue( "minzoom", QString::number( mSourceMinZoom ) );
157 writer->setMetadataValue( "maxzoom", QString::number( mMaxZoom ) );
158 writer->setMetadataValue( "crs", mTileMatrixSet.rootMatrix().crs().authid() );
159 try
160 {
161 QgsCoordinateTransform ct( mTileMatrixSet.rootMatrix().crs(), QgsCoordinateReferenceSystem( "EPSG:4326" ), context.transformContext() );
162 ct.setBallparkTransformsAreAppropriate( true );
163 QgsRectangle wgsExtent = ct.transformBoundingBox( mExtent );
164 QString boundsStr = QString( "%1,%2,%3,%4" )
165 .arg( wgsExtent.xMinimum() )
166 .arg( wgsExtent.yMinimum() )
167 .arg( wgsExtent.xMaximum() )
168 .arg( wgsExtent.yMaximum() );
169 writer->setMetadataValue( "bounds", boundsStr );
170 }
171 catch ( const QgsCsException & )
172 {
173 // bounds won't be written (not a problem - it is an optional value)
174 }
175
176 QgsProcessingMultiStepFeedback multiStepFeedback( mMaxZoom + 1, feedback );
177
178 std::unique_ptr<QgsVectorTileLoader> loader;
179 QList<QgsVectorTileRawData> rawTiles;
180
181 QMap<int, QgsTileRange>::const_iterator it = tileRanges.constBegin();
182 while ( it != tileRanges.constEnd() )
183 {
184 if ( feedback->isCanceled() )
185 break;
186
187 multiStepFeedback.setCurrentStep( it.key() );
188
189 QgsTileMatrix tileMatrix = mTileMatrixSet.tileMatrix( it.key() );
190 tileCount = static_cast<long long>( it.value().endColumn() - it.value().startColumn() + 1 ) * ( it.value().endRow() - it.value().startRow() + 1 );
191
192 const QPointF viewCenter = tileMatrix.mapToTileCoordinates( mExtent.center() );
193
194 long long tileNumber = 0;
195 rawTiles = QgsVectorTileLoader::blockingFetchTileRawData( mProvider.get(), mTileMatrixSet, viewCenter, it.value(), it.key(), &multiStepFeedback, Qgis::RendererUsage::Export );
196 for ( const QgsVectorTileRawData &rawTile : std::as_const( rawTiles ) )
197 {
198 if ( feedback->isCanceled() )
199 break;
200
201 // TODO: at the moment, it handles single source only of tiles
202 // takes the first one
203 const QByteArray data = rawTile.data.first();
204
205 if ( !data.isEmpty() )
206 {
207 QByteArray gzipTileData;
208 QgsZipUtils::encodeGzip( data, gzipTileData );
209 int rowTMS = pow( 2, rawTile.id.zoomLevel() ) - rawTile.id.row() - 1;
210 writer->setTileData( rawTile.id.zoomLevel(), rawTile.id.column(), rowTMS, gzipTileData );
211 }
212
213 multiStepFeedback.setProgress( 100.0 * ( tileNumber++ ) / tileCount );
214 }
215
216 ++it;
217 }
218
219 if ( context.willLoadLayerOnCompletion( outputFile ) )
220 {
221 context.layerToLoadOnCompletionDetails( outputFile ).setPostProcessor( new SetStylePostProcessor( mStyleDocument ) );
222 }
223
224 QVariantMap results;
225 results.insert( QStringLiteral( "OUTPUT" ), outputFile );
226 return results;
227}
228
@ VectorTile
Vector tile layers.
@ Export
Renderer used for printing or exporting to a file.
Represents a coordinate reference system (CRS).
Handles coordinate transforms between two coordinate systems.
Custom exception class for Coordinate Reference System related exceptions.
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:53
Base class for all map layer types.
Definition qgsmaplayer.h:77
QString name
Definition qgsmaplayer.h:81
virtual void exportNamedStyle(QDomDocument &doc, QString &errorMsg, const QgsReadWriteContext &context=QgsReadWriteContext(), QgsMapLayer::StyleCategories categories=QgsMapLayer::AllStyleCategories) const
Export the properties of this layer as named style in a QDomDocument.
QgsCoordinateReferenceSystem crs
Definition qgsmaplayer.h:84
void setPostProcessor(QgsProcessingLayerPostProcessorInterface *processor)
Sets the layer post-processor.
Contains information about the context in which a processing algorithm is executed.
QgsProcessingContext::LayerDetails & layerToLoadOnCompletionDetails(const QString &layer)
Returns a reference to the details for a given layer which is loaded on completion of the algorithm o...
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context.
bool willLoadLayerOnCompletion(const QString &layer) const
Returns true if the given layer (by ID or datasource) will be loaded into the current project upon co...
Custom exception class for processing related exceptions.
Base class for providing feedback from a processing algorithm.
virtual void pushWarning(const QString &warning)
Pushes a warning informational message from the algorithm.
An interface for layer post-processing handlers for execution following a processing algorithm operat...
virtual void postProcessLayer(QgsMapLayer *layer, QgsProcessingContext &context, QgsProcessingFeedback *feedback)=0
Post-processes the specified layer, following successful execution of a processing algorithm.
Processing feedback object for multi-step operations.
A rectangular map extent parameter for processing algorithms.
A map layer parameter for processing algorithms.
A numeric parameter for processing algorithms.
A vector tile layer destination parameter, for specifying the destination path for a vector tile laye...
A rectangle specified with double values.
double xMinimum
double yMinimum
double xMaximum
double yMaximum
Defines a matrix of tiles for a single zoom level: it is defined by its size (width *.
Definition qgstiles.h:142
QPointF mapToTileCoordinates(const QgsPointXY &mapPoint) const
Returns row/column coordinates (floating point number) from the given point in map coordinates.
Definition qgstiles.cpp:121
QgsTileRange tileRangeFromExtent(const QgsRectangle &mExtent) const
Returns tile range that fully covers the given extent.
Definition qgstiles.cpp:97
A range of tiles in a tile matrix.
Definition qgstiles.h:98
int endColumn() const
Returns index of the last column in the range.
Definition qgstiles.h:110
int endRow() const
Returns index of the last row in the range.
Definition qgstiles.h:114
int startRow() const
Returns index of the first row in the range.
Definition qgstiles.h:112
int startColumn() const
Returns index of the first column in the range.
Definition qgstiles.h:108
Implements a map layer that is dedicated to rendering of vector tiles.
int sourceMinZoom() const
Returns minimum zoom level at which source has any valid tiles (negative = unconstrained)
int sourceMaxZoom() const
Returns maximum zoom level at which source has any valid tiles (negative = unconstrained)
QgsDataProvider * dataProvider() override
Returns the layer's data provider, it may be nullptr.
QgsVectorTileMatrixSet & tileMatrixSet()
Returns the vector tile matrix set.
static QList< QgsVectorTileRawData > blockingFetchTileRawData(const QgsVectorTileDataProvider *provider, const QgsTileMatrixSet &tileMatrixSet, const QPointF &viewCenter, const QgsTileRange &range, int zoomLevel, QgsFeedback *feedback=nullptr, Qgis::RendererUsage usage=Qgis::RendererUsage::Unknown)
Returns raw tile data for the specified range of tiles. Blocks the caller until all tiles are fetched...
Keeps track of raw tile data from one or more sources that need to be decoded.
static bool encodeGzip(const QByteArray &bytesIn, QByteArray &bytesOut)
Encodes gzip byte stream, returns true on success.