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