QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
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
31 SetStylePostProcessor( QDomDocument &doc )
32 : mDocument( doc )
33 {}
34
36 {
37 if ( QgsVectorTileLayer *tileLayer = qobject_cast< QgsVectorTileLayer * >( layer ) )
38 {
39 QString errorMsg;
40 tileLayer->importNamedStyle( mDocument, errorMsg );
41 tileLayer->triggerRepaint();
42 }
43 }
44
45 private:
46
47 QDomDocument mDocument;
48};
49
50QString QgsDownloadVectorTilesAlgorithm::name() const
51{
52 return QStringLiteral( "downloadvectortiles" );
53}
54
55QString QgsDownloadVectorTilesAlgorithm::displayName() const
56{
57 return QObject::tr( "Download vector tiles" );
58}
59
60QStringList QgsDownloadVectorTilesAlgorithm::tags() const
61{
62 return QObject::tr( "vector,split,field,unique" ).split( ',' );
63}
64
65QString QgsDownloadVectorTilesAlgorithm::group() const
66{
67 return QObject::tr( "Vector tiles" );
68}
69
70QString QgsDownloadVectorTilesAlgorithm::groupId() const
71{
72 return QStringLiteral( "vectortiles" );
73}
74
75QString QgsDownloadVectorTilesAlgorithm::shortHelpString() const
76{
77 return QObject::tr( "Downloads vector tiles of the input vector tile layer and saves them in the local vector tile file." );
78}
79
80QgsDownloadVectorTilesAlgorithm *QgsDownloadVectorTilesAlgorithm::createInstance() const
81{
82 return new QgsDownloadVectorTilesAlgorithm();
83}
84
85void QgsDownloadVectorTilesAlgorithm::initAlgorithm( const QVariantMap & )
86{
87 addParameter( new QgsProcessingParameterMapLayer( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ), QVariant(), false, QList<int>() << static_cast< int >( Qgis::ProcessingSourceType::VectorTile ) ) );
88 addParameter( new QgsProcessingParameterExtent( QStringLiteral( "EXTENT" ), QObject::tr( "Extent" ) ) );
89 addParameter( new QgsProcessingParameterNumber( QStringLiteral( "MAX_ZOOM" ), QObject::tr( "Maximum zoom level to download" ), Qgis::ProcessingNumberParameterType::Integer, 10, false, 0 ) );
90 addParameter( new QgsProcessingParameterNumber( QStringLiteral( "TILE_LIMIT" ), QObject::tr( "Tile limit" ), Qgis::ProcessingNumberParameterType::Integer, 100, false, 0 ) );
91 addParameter( new QgsProcessingParameterVectorTileDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Output" ) ) );
92}
93
94bool QgsDownloadVectorTilesAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
95{
96 QgsMapLayer *layer = parameterAsLayer( parameters, QStringLiteral( "INPUT" ), context );
97 if ( !layer )
98 throw QgsProcessingException( QObject::tr( "Invalid input layer" ) );
99
100 QgsVectorTileLayer *vtLayer = qobject_cast< QgsVectorTileLayer * >( layer );
101 mProvider.reset( qgis::down_cast< const QgsVectorTileDataProvider * >( vtLayer->dataProvider() )->clone() );
102 mTileMatrixSet = vtLayer->tileMatrixSet();
103 mSourceMinZoom = vtLayer->sourceMinZoom();
104 mLayerName = vtLayer->name();
105
106 mExtent = parameterAsExtent( parameters, QStringLiteral( "EXTENT" ), context, layer->crs() );
107
108 mMaxZoom = parameterAsInt( parameters, QStringLiteral( "MAX_ZOOM" ), context );
109 if ( mMaxZoom > vtLayer->sourceMaxZoom() )
110 {
111 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() ) );
112 }
113
114 mTileLimit = static_cast< long long >( parameterAsInt( parameters, QStringLiteral( "TILE_LIMIT" ), context ) );
115
116 mStyleDocument = QDomDocument( QStringLiteral( "qgis" ) );
117 QString errorMsg;
118 vtLayer->exportNamedStyle( mStyleDocument, errorMsg );
119 if ( !errorMsg.isEmpty() )
120 {
121 feedback->pushWarning( QObject::tr( "Failed to get layer style: %1" ).arg( errorMsg ) );
122 }
123
124 return true;
125}
126
127QVariantMap QgsDownloadVectorTilesAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
128{
129 const QString outputFile = parameterAsOutputLayer( parameters, QStringLiteral( "OUTPUT" ), context );
130
131 // count total number of tiles in the requested extent and zoom levels to see if it exceeds the tile limit
132 long long tileCount = 0;
133 QMap<int, QgsTileRange> tileRanges;
134 for ( int i = 0; i <= mMaxZoom; i++ )
135 {
136 QgsTileMatrix tileMatrix = mTileMatrixSet.tileMatrix( i );
137 QgsTileRange tileRange = tileMatrix.tileRangeFromExtent( mExtent );
138 tileRanges.insert( i, tileRange );
139 tileCount += static_cast< long long >( tileRange.endColumn() - tileRange.startColumn() + 1 ) * ( tileRange.endRow() - tileRange.startRow() + 1 );
140 }
141 if ( tileCount > mTileLimit )
142 {
143 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 ) );
144 }
145
146 std::unique_ptr<QgsMbTiles> writer = std::make_unique<QgsMbTiles>( outputFile );
147 if ( !writer->create() )
148 {
149 throw QgsProcessingException( QObject::tr( "Failed to create MBTiles file %1" ).arg( outputFile ) );
150 }
151 writer->setMetadataValue( "format", "pbf" );
152 writer->setMetadataValue( "name", mLayerName );
153 writer->setMetadataValue( "minzoom", QString::number( mSourceMinZoom ) );
154 writer->setMetadataValue( "maxzoom", QString::number( mMaxZoom ) );
155 writer->setMetadataValue( "crs", mTileMatrixSet.rootMatrix().crs().authid() );
156 try
157 {
158 QgsCoordinateTransform ct( mTileMatrixSet.rootMatrix().crs(), QgsCoordinateReferenceSystem( "EPSG:4326" ), context.transformContext() );
159 ct.setBallparkTransformsAreAppropriate( true );
160 QgsRectangle wgsExtent = ct.transformBoundingBox( mExtent );
161 QString boundsStr = QString( "%1,%2,%3,%4" )
162 .arg( wgsExtent.xMinimum() ).arg( wgsExtent.yMinimum() )
163 .arg( wgsExtent.xMaximum() ).arg( wgsExtent.yMaximum() );
164 writer->setMetadataValue( "bounds", boundsStr );
165 }
166 catch ( const QgsCsException & )
167 {
168 // bounds won't be written (not a problem - it is an optional value)
169 }
170
171 QgsProcessingMultiStepFeedback multiStepFeedback( mMaxZoom + 1, feedback );
172
173 std::unique_ptr<QgsVectorTileLoader> loader;
174 QList<QgsVectorTileRawData> rawTiles;
175
176 QMap<int, QgsTileRange>::const_iterator it = tileRanges.constBegin();
177 while ( it != tileRanges.constEnd() )
178 {
179 if ( feedback->isCanceled() )
180 break;
181
182 multiStepFeedback.setCurrentStep( it.key() );
183
184 QgsTileMatrix tileMatrix = mTileMatrixSet.tileMatrix( it.key() );
185 tileCount = static_cast< long long >( it.value().endColumn() - it.value().startColumn() + 1 ) * ( it.value().endRow() - it.value().startRow() + 1 );
186
187 const QPointF viewCenter = tileMatrix.mapToTileCoordinates( mExtent.center() );
188
189 long long tileNumber = 0;
190 rawTiles = QgsVectorTileLoader::blockingFetchTileRawData( mProvider.get(), mTileMatrixSet, viewCenter, it.value(), it.key(), &multiStepFeedback, Qgis::RendererUsage::Export );
191 for ( const QgsVectorTileRawData &rawTile : std::as_const( rawTiles ) )
192 {
193 if ( feedback->isCanceled() )
194 break;
195
196 if ( !rawTile.data.isEmpty() )
197 {
198 QByteArray gzipTileData;
199 QgsZipUtils::encodeGzip( rawTile.data, gzipTileData );
200 int rowTMS = pow( 2, rawTile.id.zoomLevel() ) - rawTile.id.row() - 1;
201 writer->setTileData( rawTile.id.zoomLevel(), rawTile.id.column(), rowTMS, gzipTileData );
202 }
203
204 multiStepFeedback.setProgress( 100.0 * ( tileNumber++ ) / tileCount );
205 }
206
207 ++it;
208 }
209
210 if ( context.willLoadLayerOnCompletion( outputFile ) )
211 {
212 context.layerToLoadOnCompletionDetails( outputFile ).setPostProcessor( new SetStylePostProcessor( mStyleDocument ) );
213 }
214
215 QVariantMap results;
216 results.insert( QStringLiteral( "OUTPUT" ), outputFile );
217 return results;
218}
219
@ VectorTile
Vector tile layers.
@ Export
Renderer used for printing or exporting to a file.
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
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:75
QString name
Definition: qgsmaplayer.h:78
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:81
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.
Definition: qgsexception.h:83
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.
Definition: qgsrectangle.h:42
double xMinimum() const
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:201
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:211
double xMaximum() const
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:196
double yMaximum() const
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:206
Defines a matrix of tiles for a single zoom level: it is defined by its size (width *.
Definition: qgstiles.h:134
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
Range of tiles in a tile matrix to be rendered.
Definition: qgstiles.h:97
int endColumn() const
Returns index of the last column in the range.
Definition: qgstiles.h:109
int endRow() const
Returns index of the last row in the range.
Definition: qgstiles.h:113
int startRow() const
Returns index of the first row in the range.
Definition: qgstiles.h:111
int startColumn() const
Returns index of the first column in the range.
Definition: qgstiles.h:107
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 that need to be decoded.
CORE_EXPORT bool encodeGzip(const QByteArray &bytesIn, QByteArray &bytesOut)
Encodes gzip byte stream, returns true on success.