QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgsvectortilemvtencoder.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsvectortilemvtencoder.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
17
18#include "qgsfeedback.h"
19#include "qgslinestring.h"
20#include "qgslogger.h"
21#include "qgsmultilinestring.h"
22#include "qgsmultipoint.h"
23#include "qgsmultipolygon.h"
24#include "qgspolygon.h"
25#include "qgsvectorlayer.h"
27
28
31{
32 vector_tile::Tile_Feature *feature = nullptr;
35 QPoint cursor;
36
37 MVTGeometryWriter( vector_tile::Tile_Feature *f, int res, const QgsRectangle &tileExtent )
38 : feature( f )
39 , resolution( res )
40 , tileXMin( tileExtent.xMinimum() )
41 , tileYMax( tileExtent.yMaximum() )
42 , tileDX( tileExtent.width() )
43 , tileDY( tileExtent.height() )
44 {
45 }
46
47 void addMoveTo( int count )
48 {
49 feature->add_geometry( 1 | ( count << 3 ) );
50 }
51 void addLineTo( int count )
52 {
53 feature->add_geometry( 2 | ( count << 3 ) );
54 }
56 {
57 feature->add_geometry( 7 | ( 1 << 3 ) );
58 }
59
60 void addPoint( const QgsPoint &pt )
61 {
62 addPoint( mapToTileCoordinates( pt.x(), pt.y() ) );
63 }
64
65 void addPoint( const QPoint &pt )
66 {
67 const qint32 vx = pt.x() - cursor.x();
68 const qint32 vy = pt.y() - cursor.y();
69
70 // (quint32)(-(qint32)((quint32)vx >> 31)) is a C/C++ compliant way
71 // of doing vx >> 31, which is undefined behavior since vx is signed
72 feature->add_geometry( ( ( quint32 )vx << 1 ) ^ ( ( quint32 )( -( qint32 )( ( quint32 )vx >> 31 ) ) ) );
73 feature->add_geometry( ( ( quint32 )vy << 1 ) ^ ( ( quint32 )( -( qint32 )( ( quint32 )vy >> 31 ) ) ) );
74
75 cursor = pt;
76 }
77
78 QPoint mapToTileCoordinates( double x, double y )
79 {
80 return QPoint( static_cast<int>( round( ( x - tileXMin ) * resolution / tileDX ) ),
81 static_cast<int>( round( ( tileYMax - y ) * resolution / tileDY ) ) );
82 }
83};
84
85
86static void encodeLineString( const QgsLineString *lineString, bool isRing, bool reversed, MVTGeometryWriter &geomWriter )
87{
88 int count = lineString->numPoints();
89 const double *xData = lineString->xData();
90 const double *yData = lineString->yData();
91
92 if ( isRing )
93 count--; // the last point in linear ring is repeated - but not in MVT
94
95 // de-duplicate points
96 QVector<QPoint> tilePoints;
97 QPoint last( -9999, -9999 );
98 tilePoints.reserve( count );
99 for ( int i = 0; i < count; ++i )
100 {
101 const QPoint pt = geomWriter.mapToTileCoordinates( xData[i], yData[i] );
102 if ( pt == last )
103 continue;
104
105 tilePoints << pt;
106 last = pt;
107 }
108 count = tilePoints.count();
109 if ( count == 0 )
110 return;
111
112 geomWriter.addMoveTo( 1 );
113 geomWriter.addPoint( tilePoints[0] );
114 geomWriter.addLineTo( count - 1 );
115 if ( reversed )
116 {
117 for ( int i = count - 1; i >= 1; --i )
118 geomWriter.addPoint( tilePoints[i] );
119 }
120 else
121 {
122 for ( int i = 1; i < count; ++i )
123 geomWriter.addPoint( tilePoints[i] );
124 }
125}
126
127static void encodePolygon( const QgsPolygon *polygon, MVTGeometryWriter &geomWriter )
128{
129 const QgsLineString *exteriorRing = qgsgeometry_cast<const QgsLineString *>( polygon->exteriorRing() );
130 encodeLineString( exteriorRing, true, !QgsVectorTileMVTUtils::isExteriorRing( exteriorRing ), geomWriter );
131 geomWriter.addClosePath();
132
133 for ( int i = 0; i < polygon->numInteriorRings(); ++i )
134 {
135 const QgsLineString *interiorRing = qgsgeometry_cast<const QgsLineString *>( polygon->interiorRing( i ) );
136 encodeLineString( interiorRing, true, QgsVectorTileMVTUtils::isExteriorRing( interiorRing ), geomWriter );
137 geomWriter.addClosePath();
138 }
139}
140
141
142//
143
144
146 : mTileID( tileID )
147{
149 mTileExtent = tm.tileExtent( mTileID );
150 mCrs = tm.crs();
151}
152
154 : mTileID( tileID )
155{
156 mTileExtent = tileMatrix.tileExtent( mTileID );
157 mCrs = tileMatrix.crs();
158}
159
160void QgsVectorTileMVTEncoder::addLayer( QgsVectorLayer *layer, QgsFeedback *feedback, QString filterExpression, QString layerName )
161{
162 if ( feedback && feedback->isCanceled() )
163 return;
164
165 const QgsCoordinateTransform ct( layer->crs(), mCrs, mTransformContext );
166
167 QgsRectangle layerTileExtent = mTileExtent;
168 try
169 {
170 QgsCoordinateTransform extentTransform = ct;
171 extentTransform.setBallparkTransformsAreAppropriate( true );
172 layerTileExtent = extentTransform.transformBoundingBox( layerTileExtent, Qgis::TransformDirection::Reverse );
173 if ( !layerTileExtent.intersects( layer->extent() ) )
174 {
175 return; // tile is completely outside of the layer'e extent
176 }
177 }
178 catch ( const QgsCsException & )
179 {
180 QgsDebugError( "Failed to reproject tile extent to the layer" );
181 return;
182 }
183
184 if ( layerName.isEmpty() )
185 layerName = layer->name();
186
187 // add buffer to both filter extent in layer CRS (for feature request) and tile extent in target CRS (for clipping)
188 const double bufferRatio = static_cast<double>( mBuffer ) / mResolution;
189 QgsRectangle tileExtent = mTileExtent;
190 tileExtent.grow( bufferRatio * mTileExtent.width() );
191 layerTileExtent.grow( bufferRatio * std::max( layerTileExtent.width(), layerTileExtent.height() ) );
192
193 QgsFeatureRequest request;
194 request.setFilterRect( layerTileExtent );
195 if ( !filterExpression.isEmpty() )
196 request.setFilterExpression( filterExpression );
197 QgsFeatureIterator fit = layer->getFeatures( request );
198
199 QgsFeature f;
200 if ( !fit.nextFeature( f ) )
201 {
202 return; // nothing to write - do not add the layer at all
203 }
204
205 vector_tile::Tile_Layer *tileLayer = tile.add_layers();
206 tileLayer->set_name( layerName.toUtf8() );
207 tileLayer->set_version( 2 ); // 2 means MVT spec version 2.1
208 tileLayer->set_extent( static_cast<::google::protobuf::uint32>( mResolution ) );
209
210 const QgsFields fields = layer->fields();
211 for ( int i = 0; i < fields.count(); ++i )
212 {
213 tileLayer->add_keys( fields[i].name().toUtf8() );
214 }
215
216 do
217 {
218 if ( feedback && feedback->isCanceled() )
219 break;
220
221 QgsGeometry g = f.geometry();
222
223 // reproject
224 try
225 {
226 g.transform( ct );
227 }
228 catch ( const QgsCsException & )
229 {
230 QgsDebugError( "Failed to reproject geometry " + QString::number( f.id() ) );
231 continue;
232 }
233
234 // clip
235 g = g.clipped( tileExtent );
236
237 f.setGeometry( g );
238
239 addFeature( tileLayer, f );
240 }
241 while ( fit.nextFeature( f ) );
242
243 mKnownValues.clear();
244}
245
246void QgsVectorTileMVTEncoder::addFeature( vector_tile::Tile_Layer *tileLayer, const QgsFeature &f )
247{
248 QgsGeometry g = f.geometry();
249 const Qgis::GeometryType geomType = g.type();
250 const double onePixel = mTileExtent.width() / mResolution;
251
252 if ( geomType == Qgis::GeometryType::Line )
253 {
254 if ( g.length() < onePixel )
255 return; // too short
256 }
257 else if ( geomType == Qgis::GeometryType::Polygon )
258 {
259 if ( g.area() < onePixel * onePixel )
260 return; // too small
261 }
262
263 vector_tile::Tile_Feature *feature = tileLayer->add_features();
264
265 feature->set_id( static_cast<quint64>( f.id() ) );
266
267 //
268 // encode attributes
269 //
270
271 const QgsAttributes attrs = f.attributes();
272 for ( int i = 0; i < attrs.count(); ++i )
273 {
274 const QVariant v = attrs.at( i );
275 if ( QgsVariantUtils::isNull( v ) )
276 continue;
277
278 int valueIndex;
279 if ( mKnownValues.contains( v ) )
280 {
281 valueIndex = mKnownValues[v];
282 }
283 else
284 {
285 vector_tile::Tile_Value *value = tileLayer->add_values();
286 valueIndex = tileLayer->values_size() - 1;
287 mKnownValues[v] = valueIndex;
288
289 if ( v.type() == QVariant::Double )
290 value->set_double_value( v.toDouble() );
291 else if ( v.type() == QVariant::Int )
292 value->set_int_value( v.toInt() );
293 else if ( v.type() == QVariant::Bool )
294 value->set_bool_value( v.toBool() );
295 else
296 value->set_string_value( v.toString().toUtf8().toStdString() );
297 }
298
299 feature->add_tags( static_cast<quint32>( i ) );
300 feature->add_tags( static_cast<quint32>( valueIndex ) );
301 }
302
303 //
304 // encode geometry
305 //
306
307 vector_tile::Tile_GeomType mvtGeomType = vector_tile::Tile_GeomType_UNKNOWN;
308 if ( geomType == Qgis::GeometryType::Point )
309 mvtGeomType = vector_tile::Tile_GeomType_POINT;
310 else if ( geomType == Qgis::GeometryType::Line )
311 mvtGeomType = vector_tile::Tile_GeomType_LINESTRING;
312 else if ( geomType == Qgis::GeometryType::Polygon )
313 mvtGeomType = vector_tile::Tile_GeomType_POLYGON;
314 feature->set_type( mvtGeomType );
315
317 {
318 g = QgsGeometry( g.get()->segmentize() );
319 }
320
321 MVTGeometryWriter geomWriter( feature, mResolution, mTileExtent );
322
323 const QgsAbstractGeometry *geom = g.constGet();
324 switch ( QgsWkbTypes::flatType( g.wkbType() ) )
325 {
327 {
328 const QgsPoint *pt = static_cast<const QgsPoint *>( geom );
329 geomWriter.addMoveTo( 1 );
330 geomWriter.addPoint( *pt );
331 }
332 break;
333
335 {
336 encodeLineString( qgsgeometry_cast<const QgsLineString *>( geom ), true, false, geomWriter );
337 }
338 break;
339
341 {
342 encodePolygon( static_cast<const QgsPolygon *>( geom ), geomWriter );
343 }
344 break;
345
347 {
348 const QgsMultiPoint *mpt = static_cast<const QgsMultiPoint *>( geom );
349 geomWriter.addMoveTo( mpt->numGeometries() );
350 for ( int i = 0; i < mpt->numGeometries(); ++i )
351 geomWriter.addPoint( *mpt->pointN( i ) );
352 }
353 break;
354
356 {
357 const QgsMultiLineString *mls = qgsgeometry_cast<const QgsMultiLineString *>( geom );
358 for ( int i = 0; i < mls->numGeometries(); ++i )
359 {
360 encodeLineString( mls->lineStringN( i ), true, false, geomWriter );
361 }
362 }
363 break;
364
366 {
367 const QgsMultiPolygon *mp = qgsgeometry_cast<const QgsMultiPolygon *>( geom );
368 for ( int i = 0; i < mp->numGeometries(); ++i )
369 {
370 encodePolygon( mp->polygonN( i ), geomWriter );
371 }
372 }
373 break;
374
375 default:
376 break;
377 }
378}
379
380
382{
383 return QByteArray::fromStdString( tile.SerializeAsString() );
384}
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
Definition: qgis.h:255
@ Polygon
Polygons.
@ LineString
LineString.
@ MultiPoint
MultiPoint.
@ Polygon
Polygon.
@ MultiPolygon
MultiPolygon.
@ MultiLineString
MultiLineString.
@ Reverse
Reverse/inverse transform (from destination to source)
Abstract base class for all geometries.
virtual QgsAbstractGeometry * segmentize(double tolerance=M_PI/180., SegmentationToleranceType toleranceType=MaximumAngle) const
Returns a version of the geometry without curves.
A vector of attributes.
Definition: qgsattributes.h:59
Class for doing transforms between two map coordinate systems.
void setBallparkTransformsAreAppropriate(bool appropriate)
Sets whether approximate "ballpark" results are appropriate for this coordinate transform.
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool handle180Crossover=false) const
Transforms a rectangle from the source CRS to the destination CRS.
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:67
int numInteriorRings() const
Returns the number of interior rings contained with the curve polygon.
const QgsCurve * exteriorRing() const
Returns the curve polygon's exterior ring.
const QgsCurve * interiorRing(int i) const
Retrieves an interior ring from the curve polygon.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
This class wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setFilterExpression(const QString &expression)
Set the filter expression.
QgsFeatureRequest & setFilterRect(const QgsRectangle &rectangle)
Sets the rectangle from which features will be taken.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
QgsAttributes attributes
Definition: qgsfeature.h:65
QgsGeometry geometry
Definition: qgsfeature.h:67
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
Definition: qgsfeature.cpp:167
Q_GADGET QgsFeatureId id
Definition: qgsfeature.h:64
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition: qgsfeedback.h:44
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:53
Container of fields for a vector layer.
Definition: qgsfields.h:45
int count() const
Returns number of items.
Definition: qgsfields.cpp:133
int numGeometries() const
Returns the number of geometries within the collection.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:162
QgsGeometry clipped(const QgsRectangle &rectangle)
Clips the geometry using the specified rectangle.
double length() const
Returns the planar, 2-dimensional length of geometry.
Qgis::GeometryOperationResult transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool transformZ=false)
Transforms this geometry as described by the coordinate transform ct.
QgsAbstractGeometry * get()
Returns a modifiable (non-const) reference to the underlying abstract geometry primitive.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
Qgis::GeometryType type
Definition: qgsgeometry.h:165
double area() const
Returns the planar, 2-dimensional area of the geometry.
Qgis::WkbType wkbType() const
Returns type of the geometry as a WKB type (point / linestring / polygon etc.)
Line string geometry type, with support for z-dimension and m-values.
Definition: qgslinestring.h:45
const double * yData() const
Returns a const pointer to the y vertex data.
const double * xData() const
Returns a const pointer to the x vertex data.
int numPoints() const override
Returns the number of points in the curve.
QString name
Definition: qgsmaplayer.h:78
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:81
Multi line string geometry collection.
QgsLineString * lineStringN(int index)
Returns the line string with the specified index.
Multi point geometry collection.
Definition: qgsmultipoint.h:29
QgsPoint * pointN(int index)
Returns the point with the specified index.
Multi polygon geometry collection.
QgsPolygon * polygonN(int index)
Returns the polygon with the specified index.
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:49
Q_GADGET double x
Definition: qgspoint.h:52
double y
Definition: qgspoint.h:53
Polygon geometry type.
Definition: qgspolygon.h:33
A rectangle specified with double values.
Definition: qgsrectangle.h:42
bool intersects(const QgsRectangle &rect) const
Returns true when rectangle intersects with other rectangle.
Definition: qgsrectangle.h:371
double width() const
Returns the width of the rectangle.
Definition: qgsrectangle.h:236
void grow(double delta)
Grows the rectangle in place by the specified amount.
Definition: qgsrectangle.h:307
double height() const
Returns the height of the rectangle.
Definition: qgsrectangle.h:243
Defines a matrix of tiles for a single zoom level: it is defined by its size (width *.
Definition: qgstiles.h:134
QgsRectangle tileExtent(QgsTileXYZ id) const
Returns extent of the given tile in this matrix.
Definition: qgstiles.cpp:81
static QgsTileMatrix fromWebMercator(int zoomLevel)
Returns a tile matrix for the usual web mercator.
Definition: qgstiles.cpp:23
QgsCoordinateReferenceSystem crs() const
Returns the crs of the tile matrix.
Definition: qgstiles.h:157
Stores coordinates of a tile in a tile matrix set.
Definition: qgstiles.h:38
int zoomLevel() const
Returns tile's zoom level (Z)
Definition: qgstiles.h:51
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
Represents a vector layer which manages a vector based data sets.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
QgsRectangle extent() const FINAL
Returns the extent of the layer.
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.
QByteArray encode() const
Encodes MVT using data stored previously with addLayer() calls.
QgsVectorTileMVTEncoder(QgsTileXYZ tileID)
Creates MVT encoder for the given tile coordinates for Web Mercator.
static bool isExteriorRing(const QgsLineString *lineString)
Returns whether this linear ring forms an exterior ring according to MVT spec (depending on the orien...
static bool isCurvedType(Qgis::WkbType type)
Returns true if the WKB type is a curved type or can contain curved geometries.
Definition: qgswkbtypes.h:806
static Qgis::WkbType flatType(Qgis::WkbType type)
Returns the flat type for a WKB type.
Definition: qgswkbtypes.h:628
#define QgsDebugError(str)
Definition: qgslogger.h:38
Helper class for writing of geometry commands.
QPoint mapToTileCoordinates(double x, double y)
vector_tile::Tile_Feature * feature
void addPoint(const QgsPoint &pt)
MVTGeometryWriter(vector_tile::Tile_Feature *f, int res, const QgsRectangle &tileExtent)
void addPoint(const QPoint &pt)