QGIS API Documentation  3.26.3-Buenos Aires (65e4edfdad)
qgsvectortilewriter.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsvectortilewriter.cpp
3  --------------------------------------
4  Date : April 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 "qgsvectortilewriter.h"
17 
18 #include "qgsdatasourceuri.h"
19 #include "qgsfeedback.h"
20 #include "qgsjsonutils.h"
21 #include "qgslogger.h"
22 #include "qgsmbtiles.h"
23 #include "qgstiles.h"
24 #include "qgsvectorlayer.h"
26 #include "qgsvectortileutils.h"
27 #include "qgsziputils.h"
28 
29 #include <nlohmann/json.hpp>
30 
31 #include <QDir>
32 #include <QFile>
33 #include <QFileInfo>
34 #include <QUrl>
35 
36 
38 {
40 }
41 
42 
44 {
45  if ( tileMatrix.isRootTileMatrix() )
46  {
47  mRootTileMatrix = tileMatrix;
48  return true;
49  }
50  return false;
51 }
52 
53 
55 {
56  if ( mMinZoom < 0 )
57  {
58  mErrorMessage = tr( "Invalid min. zoom level" );
59  return false;
60  }
61  if ( mMaxZoom > 24 )
62  {
63  mErrorMessage = tr( "Invalid max. zoom level" );
64  return false;
65  }
66 
67  std::unique_ptr<QgsMbTiles> mbtiles;
68 
69  QgsDataSourceUri dsUri;
70  dsUri.setEncodedUri( mDestinationUri );
71 
72  QString sourceType = dsUri.param( QStringLiteral( "type" ) );
73  QString sourcePath = dsUri.param( QStringLiteral( "url" ) );
74  if ( sourceType == QLatin1String( "xyz" ) )
75  {
76  // remove the initial file:// scheme
77  sourcePath = QUrl( sourcePath ).toLocalFile();
78 
79  if ( !QgsVectorTileUtils::checkXYZUrlTemplate( sourcePath ) )
80  {
81  mErrorMessage = tr( "Invalid template for XYZ: " ) + sourcePath;
82  return false;
83  }
84  }
85  else if ( sourceType == QLatin1String( "mbtiles" ) )
86  {
87  mbtiles.reset( new QgsMbTiles( sourcePath ) );
88  }
89  else
90  {
91  mErrorMessage = tr( "Unsupported source type for writing: " ) + sourceType;
92  return false;
93  }
94 
95  QgsRectangle outputExtent = mExtent;
96  if ( outputExtent.isEmpty() )
97  {
98  outputExtent = fullExtent();
99  if ( outputExtent.isEmpty() )
100  {
101  mErrorMessage = tr( "Failed to calculate output extent" );
102  return false;
103  }
104  }
105 
106  // figure out how many tiles we will need to do
107  int tilesToCreate = 0;
108  for ( int zoomLevel = mMinZoom; zoomLevel <= mMaxZoom; ++zoomLevel )
109  {
110  const QgsTileMatrix tileMatrix = QgsTileMatrix::fromTileMatrix( zoomLevel, mRootTileMatrix );
111 
112  QgsTileRange tileRange = tileMatrix.tileRangeFromExtent( outputExtent );
113  tilesToCreate += ( tileRange.endRow() - tileRange.startRow() + 1 ) *
114  ( tileRange.endColumn() - tileRange.startColumn() + 1 );
115  }
116 
117  if ( tilesToCreate == 0 )
118  {
119  mErrorMessage = tr( "No tiles to generate" );
120  return false;
121  }
122 
123  if ( mbtiles )
124  {
125  if ( !mbtiles->create() )
126  {
127  mErrorMessage = tr( "Failed to create MBTiles file: " ) + sourcePath;
128  return false;
129  }
130 
131  // required metadata
132  mbtiles->setMetadataValue( "format", "pbf" );
133  mbtiles->setMetadataValue( "json", mbtilesJsonSchema() );
134 
135  // metadata specified by the client
136  const QStringList metaKeys = mMetadata.keys();
137  for ( const QString &key : metaKeys )
138  {
139  mbtiles->setMetadataValue( key, mMetadata[key].toString() );
140  }
141 
142  // default metadata that we always write (if not written by the client)
143  if ( !mMetadata.contains( "name" ) )
144  mbtiles->setMetadataValue( "name", "unnamed" ); // required by the spec
145  if ( !mMetadata.contains( "minzoom" ) )
146  mbtiles->setMetadataValue( "minzoom", QString::number( mMinZoom ) );
147  if ( !mMetadata.contains( "maxzoom" ) )
148  mbtiles->setMetadataValue( "maxzoom", QString::number( mMaxZoom ) );
149  if ( !mMetadata.contains( "bounds" ) )
150  {
151  try
152  {
153  QgsCoordinateTransform ct( mRootTileMatrix.crs(), QgsCoordinateReferenceSystem( "EPSG:4326" ), mTransformContext );
155  QgsRectangle wgsExtent = ct.transform( outputExtent );
156  QString boundsStr = QString( "%1,%2,%3,%4" )
157  .arg( wgsExtent.xMinimum() ).arg( wgsExtent.yMinimum() )
158  .arg( wgsExtent.xMaximum() ).arg( wgsExtent.yMaximum() );
159  mbtiles->setMetadataValue( "bounds", boundsStr );
160  }
161  catch ( const QgsCsException & )
162  {
163  // bounds won't be written (not a problem - it is an optional value)
164  }
165  }
166  if ( !mMetadata.contains( "crs" ) )
167  mbtiles->setMetadataValue( "crs", mRootTileMatrix.crs().authid() );
168  }
169 
170  int tilesCreated = 0;
171  for ( int zoomLevel = mMinZoom; zoomLevel <= mMaxZoom; ++zoomLevel )
172  {
173  const QgsTileMatrix tileMatrix = QgsTileMatrix::fromTileMatrix( zoomLevel, mRootTileMatrix );
174 
175  QgsTileRange tileRange = tileMatrix.tileRangeFromExtent( outputExtent );
176  for ( int row = tileRange.startRow(); row <= tileRange.endRow(); ++row )
177  {
178  for ( int col = tileRange.startColumn(); col <= tileRange.endColumn(); ++col )
179  {
180  QgsTileXYZ tileID( col, row, zoomLevel );
181  QgsVectorTileMVTEncoder encoder( tileID );
182  encoder.setTransformContext( mTransformContext );
183 
184  for ( const Layer &layer : std::as_const( mLayers ) )
185  {
186  if ( ( layer.minZoom() >= 0 && zoomLevel < layer.minZoom() ) ||
187  ( layer.maxZoom() >= 0 && zoomLevel > layer.maxZoom() ) )
188  continue;
189 
190  encoder.addLayer( layer.layer(), feedback, layer.filterExpression(), layer.layerName() );
191  }
192 
193  if ( feedback && feedback->isCanceled() )
194  {
195  mErrorMessage = tr( "Operation has been canceled" );
196  return false;
197  }
198 
199  QByteArray tileData = encoder.encode();
200 
201  ++tilesCreated;
202  if ( feedback )
203  {
204  feedback->setProgress( static_cast<double>( tilesCreated ) / tilesToCreate * 100 );
205  }
206 
207  if ( tileData.isEmpty() )
208  {
209  // skipping empty tile - no need to write it
210  continue;
211  }
212 
213  if ( sourceType == QLatin1String( "xyz" ) )
214  {
215  if ( !writeTileFileXYZ( sourcePath, tileID, tileMatrix, tileData ) )
216  return false; // error message already set
217  }
218  else // mbtiles
219  {
220  QByteArray gzipTileData;
221  QgsZipUtils::encodeGzip( tileData, gzipTileData );
222  int rowTMS = pow( 2, tileID.zoomLevel() ) - tileID.row() - 1;
223  mbtiles->setTileData( tileID.zoomLevel(), tileID.column(), rowTMS, gzipTileData );
224  }
225  }
226  }
227  }
228 
229  return true;
230 }
231 
233 {
234  QgsRectangle extent;
235 
236  for ( const Layer &layer : mLayers )
237  {
238  QgsVectorLayer *vl = layer.layer();
239  QgsCoordinateTransform ct( vl->crs(), mRootTileMatrix.crs(), mTransformContext );
241  try
242  {
243  QgsRectangle r = ct.transformBoundingBox( vl->extent() );
244  extent.combineExtentWith( r );
245  }
246  catch ( const QgsCsException & )
247  {
248  QgsDebugMsg( "Failed to reproject layer extent to destination CRS" );
249  }
250  }
251  return extent;
252 }
253 
254 bool QgsVectorTileWriter::writeTileFileXYZ( const QString &sourcePath, QgsTileXYZ tileID, const QgsTileMatrix &tileMatrix, const QByteArray &tileData )
255 {
256  QString filePath = QgsVectorTileUtils::formatXYZUrlTemplate( sourcePath, tileID, tileMatrix );
257 
258  // make dirs if needed
259  QFileInfo fi( filePath );
260  QDir fileDir = fi.dir();
261  if ( !fileDir.exists() )
262  {
263  if ( !fileDir.mkpath( "." ) )
264  {
265  mErrorMessage = tr( "Cannot create directory " ) + fileDir.path();
266  return false;
267  }
268  }
269 
270  QFile f( filePath );
271  if ( !f.open( QIODevice::WriteOnly ) )
272  {
273  mErrorMessage = tr( "Cannot open file for writing " ) + filePath;
274  return false;
275  }
276 
277  f.write( tileData );
278  f.close();
279  return true;
280 }
281 
282 
283 QString QgsVectorTileWriter::mbtilesJsonSchema()
284 {
285  QVariantList arrayLayers;
286  for ( const Layer &layer : std::as_const( mLayers ) )
287  {
288  QgsVectorLayer *vl = layer.layer();
289  const QgsFields fields = vl->fields();
290 
291  QVariantMap fieldsObj;
292  for ( const QgsField &field : fields )
293  {
294  QString fieldTypeStr;
295  if ( field.type() == QVariant::Bool )
296  fieldTypeStr = QStringLiteral( "Boolean" );
297  else if ( field.type() == QVariant::Int || field.type() == QVariant::Double )
298  fieldTypeStr = QStringLiteral( "Number" );
299  else
300  fieldTypeStr = QStringLiteral( "String" );
301 
302  fieldsObj[field.name()] = fieldTypeStr;
303  }
304 
305  QVariantMap layerObj;
306  layerObj["id"] = vl->name();
307  layerObj["fields"] = fieldsObj;
308  arrayLayers.append( layerObj );
309  }
310 
311  QVariantMap rootObj;
312  rootObj["vector_layers"] = arrayLayers;
313  return QString::fromStdString( QgsJsonUtils::jsonFromVariant( rootObj ).dump() );
314 }
315 
316 
317 QByteArray QgsVectorTileWriter::writeSingleTile( QgsTileXYZ tileID, QgsFeedback *feedback, int buffer, int resolution ) const
318 {
319  int zoomLevel = tileID.zoomLevel();
320 
321  QgsVectorTileMVTEncoder encoder( tileID );
322  encoder.setTileBuffer( buffer );
323  encoder.setResolution( resolution );
324  encoder.setTransformContext( mTransformContext );
325 
326  for ( const QgsVectorTileWriter::Layer &layer : std::as_const( mLayers ) )
327  {
328  if ( ( layer.minZoom() >= 0 && zoomLevel < layer.minZoom() ) ||
329  ( layer.maxZoom() >= 0 && zoomLevel > layer.maxZoom() ) )
330  continue;
331 
332  encoder.addLayer( layer.layer(), feedback, layer.filterExpression(), layer.layerName() );
333  }
334 
335  return encoder.encode();
336 }
QgsMapLayer::crs
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:79
QgsFeedback::setProgress
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition: qgsfeedback.h:76
QgsDataSourceUri
Class for storing the component parts of a RDBMS data source URI (e.g. a Postgres data source).
Definition: qgsdatasourceuri.h:37
QgsVectorTileWriter::writeSingleTile
QByteArray writeSingleTile(QgsTileXYZ tileID, QgsFeedback *feedback=nullptr, int buffer=256, int resolution=4096) const
Encodes single MVT tile.
Definition: qgsvectortilewriter.cpp:317
qgsziputils.h
QgsRectangle::combineExtentWith
void combineExtentWith(const QgsRectangle &rect)
Expands the rectangle so that it covers both the original rectangle and the given rectangle.
Definition: qgsrectangle.h:391
QgsTileXYZ
Stores coordinates of a tile in a tile matrix set. Tile matrix is identified by the zoomLevel(),...
Definition: qgstiles.h:37
QgsTileRange
Range of tiles in a tile matrix to be rendered. The selection is rectangular, given by start/end row ...
Definition: qgstiles.h:70
QgsFields
Container of fields for a vector layer.
Definition: qgsfields.h:44
QgsRectangle::yMinimum
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:198
QgsCoordinateTransform::transformBoundingBox
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool handle180Crossover=false) const SIP_THROW(QgsCsException)
Transforms a rectangle from the source CRS to the destination CRS.
Definition: qgscoordinatetransform.cpp:560
QgsVectorTileMVTEncoder::encode
QByteArray encode() const
Encodes MVT using data stored previously with addLayer() calls.
Definition: qgsvectortilemvtencoder.cpp:379
QgsFeedback::isCanceled
bool isCanceled() const SIP_HOLDGIL
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:67
QgsVectorTileWriter::setRootTileMatrix
bool setRootTileMatrix(const QgsTileMatrix &tileMatrix)
Sets zoom level 0 tile matrix.
Definition: qgsvectortilewriter.cpp:43
QgsVectorTileMVTEncoder::addLayer
void addLayer(QgsVectorLayer *layer, QgsFeedback *feedback=nullptr, QString filterExpression=QString(), QString layerName=QString())
Fetches data from vector layer for the given tile, does reprojection and clipping.
Definition: qgsvectortilemvtencoder.cpp:158
field
const QgsField & field
Definition: qgsfield.h:463
QgsDebugMsg
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QgsField::name
QString name
Definition: qgsfield.h:60
QgsTileMatrix::fromTileMatrix
static QgsTileMatrix fromTileMatrix(int zoomLevel, const QgsTileMatrix &tileMatrix)
Returns a tile matrix based on another one.
Definition: qgstiles.cpp:61
QgsRectangle
A rectangle specified with double values.
Definition: qgsrectangle.h:41
QgsTileMatrix::crs
QgsCoordinateReferenceSystem crs() const
Returns the crs of the tile matrix.
Definition: qgstiles.h:131
qgsmbtiles.h
QgsTileMatrix::isRootTileMatrix
bool isRootTileMatrix() const
Returns the root status of the tile matrix (zoom level == 0)
Definition: qgstiles.h:193
QgsDataSourceUri::param
QString param(const QString &key) const
Returns a generic parameter value corresponding to the specified key.
Definition: qgsdatasourceuri.cpp:834
QgsVectorLayer::fields
QgsFields fields() const FINAL
Returns the list of fields of this layer.
Definition: qgsvectorlayer.cpp:3436
QgsRectangle::xMaximum
double xMaximum() const SIP_HOLDGIL
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:183
QgsTileRange::endRow
int endRow() const
Returns index of the last row in the range.
Definition: qgstiles.h:87
QgsTileMatrix
Defines a matrix of tiles for a single zoom level: it is defined by its size (width *.
Definition: qgstiles.h:107
QgsMbTiles
Utility class for reading and writing MBTiles files (which are SQLite3 databases).
Definition: qgsmbtiles.h:38
QgsCsException
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:65
QgsVectorTileUtils::checkXYZUrlTemplate
static bool checkXYZUrlTemplate(const QString &url)
Checks whether the URL template string is correct (contains {x}, {y} / {-y}, {z} placeholders)
Definition: qgsvectortileutils.cpp:141
QgsCoordinateTransform::setBallparkTransformsAreAppropriate
void setBallparkTransformsAreAppropriate(bool appropriate)
Sets whether approximate "ballpark" results are appropriate for this coordinate transform.
Definition: qgscoordinatetransform.cpp:939
qgsdatasourceuri.h
qgsvectortilemvtencoder.h
QgsVectorTileMVTEncoder::setResolution
void setResolution(int extent)
Sets the resolution of coordinates of geometries within the tile.
Definition: qgsvectortilemvtencoder.h:52
QgsFeedback
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition: qgsfeedback.h:44
QgsTileMatrix::tileRangeFromExtent
QgsTileRange tileRangeFromExtent(const QgsRectangle &mExtent) const
Returns tile range that fully covers the given extent.
Definition: qgstiles.cpp:97
QgsVectorTileMVTEncoder::setTransformContext
void setTransformContext(const QgsCoordinateTransformContext &transformContext)
Sets coordinate transform context for transforms between layers and tile matrix CRS.
Definition: qgsvectortilemvtencoder.h:60
QgsVectorTileWriter::QgsVectorTileWriter
QgsVectorTileWriter()
Definition: qgsvectortilewriter.cpp:37
QgsRectangle::xMinimum
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:188
QgsTileRange::endColumn
int endColumn() const
Returns index of the last column in the range.
Definition: qgstiles.h:83
QgsTileXYZ::zoomLevel
int zoomLevel() const
Returns tile's zoom level (Z)
Definition: qgstiles.h:51
QgsCoordinateReferenceSystem
This class represents a coordinate reference system (CRS).
Definition: qgscoordinatereferencesystem.h:211
QgsVectorLayer::extent
QgsRectangle extent() const FINAL
Returns the extent of the layer.
Definition: qgsvectorlayer.cpp:894
qgsvectorlayer.h
QgsVectorTileMVTEncoder::setTileBuffer
void setTileBuffer(int buffer)
Sets size of the buffer zone around tile edges in integer tile coordinates.
Definition: qgsvectortilemvtencoder.h:57
qgsvectortilewriter.h
QgsTileRange::startRow
int startRow() const
Returns index of the first row in the range.
Definition: qgstiles.h:85
QgsRectangle::yMaximum
double yMaximum() const SIP_HOLDGIL
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:193
QgsVectorTileUtils::formatXYZUrlTemplate
static QString formatXYZUrlTemplate(const QString &url, QgsTileXYZ tile, const QgsTileMatrix &tileMatrix)
Returns formatted tile URL string replacing {x}, {y}, {z} placeholders (or {-y} instead of {y} for TM...
Definition: qgsvectortileutils.cpp:124
qgstiles.h
QgsDataSourceUri::setEncodedUri
void setEncodedUri(const QByteArray &uri)
Sets the complete encoded uri.
Definition: qgsdatasourceuri.cpp:636
QgsTileXYZ::row
int row() const
Returns tile's row index (Y)
Definition: qgstiles.h:49
QgsVectorLayer
Represents a vector layer which manages a vector based data sets.
Definition: qgsvectorlayer.h:391
QgsVectorTileMVTEncoder
Handles conversion of vector features to Mapbox vector tiles encoding.
Definition: qgsvectortilemvtencoder.h:40
QgsVectorTileWriter::Layer
Configuration of a single input vector layer to be included in the output.
Definition: qgsvectortilewriter.h:86
QgsZipUtils::encodeGzip
CORE_EXPORT bool encodeGzip(const QByteArray &bytesIn, QByteArray &bytesOut)
Encodes gzip byte stream, returns true on success.
Definition: qgsziputils.cpp:255
QgsTileMatrix::fromWebMercator
static QgsTileMatrix fromWebMercator(int zoomLevel)
Returns a tile matrix for the usual web mercator.
Definition: qgstiles.cpp:23
QgsMapLayer::name
QString name
Definition: qgsmaplayer.h:76
QgsCoordinateReferenceSystem::authid
QString authid
Definition: qgscoordinatereferencesystem.h:217
QgsVectorTileWriter::writeTiles
bool writeTiles(QgsFeedback *feedback=nullptr)
Writes vector tiles according to the configuration.
Definition: qgsvectortilewriter.cpp:54
qgsjsonutils.h
qgslogger.h
QgsCoordinateTransform::transform
QgsPointXY transform(const QgsPointXY &point, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const SIP_THROW(QgsCsException)
Transform the point from the source CRS to the destination CRS.
Definition: qgscoordinatetransform.cpp:272
qgsvectortileutils.h
QgsCoordinateTransform
Class for doing transforms between two map coordinate systems.
Definition: qgscoordinatetransform.h:57
qgsfeedback.h
QgsRectangle::isEmpty
bool isEmpty() const
Returns true if the rectangle is empty.
Definition: qgsrectangle.h:469
QgsJsonUtils::jsonFromVariant
static json jsonFromVariant(const QVariant &v)
Converts a QVariant v to a json object.
Definition: qgsjsonutils.cpp:401
QgsField::type
QVariant::Type type
Definition: qgsfield.h:58
QgsTileRange::startColumn
int startColumn() const
Returns index of the first column in the range.
Definition: qgstiles.h:81
QgsVectorTileWriter::fullExtent
QgsRectangle fullExtent() const
Returns calculated extent that combines extent of all input layers.
Definition: qgsvectortilewriter.cpp:232
QgsTileXYZ::column
int column() const
Returns tile's column index (X)
Definition: qgstiles.h:47
QgsField
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:50