QGIS API Documentation 3.30.0-'s-Hertogenbosch (f186b8efe0)
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
110 geomWriter.addMoveTo( 1 );
111 geomWriter.addPoint( tilePoints[0] );
112 geomWriter.addLineTo( count - 1 );
113 if ( reversed )
114 {
115 for ( int i = count - 1; i >= 1; --i )
116 geomWriter.addPoint( tilePoints[i] );
117 }
118 else
119 {
120 for ( int i = 1; i < count; ++i )
121 geomWriter.addPoint( tilePoints[i] );
122 }
123}
124
125static void encodePolygon( const QgsPolygon *polygon, MVTGeometryWriter &geomWriter )
126{
127 const QgsLineString *exteriorRing = qgsgeometry_cast<const QgsLineString *>( polygon->exteriorRing() );
128 encodeLineString( exteriorRing, true, !QgsVectorTileMVTUtils::isExteriorRing( exteriorRing ), geomWriter );
129 geomWriter.addClosePath();
130
131 for ( int i = 0; i < polygon->numInteriorRings(); ++i )
132 {
133 const QgsLineString *interiorRing = qgsgeometry_cast<const QgsLineString *>( polygon->interiorRing( i ) );
134 encodeLineString( interiorRing, true, QgsVectorTileMVTUtils::isExteriorRing( interiorRing ), geomWriter );
135 geomWriter.addClosePath();
136 }
137}
138
139
140//
141
142
144 : mTileID( tileID )
145{
147 mTileExtent = tm.tileExtent( mTileID );
148 mCrs = tm.crs();
149}
150
152 : mTileID( tileID )
153{
154 mTileExtent = tileMatrix.tileExtent( mTileID );
155 mCrs = tileMatrix.crs();
156}
157
158void QgsVectorTileMVTEncoder::addLayer( QgsVectorLayer *layer, QgsFeedback *feedback, QString filterExpression, QString layerName )
159{
160 if ( feedback && feedback->isCanceled() )
161 return;
162
163 const QgsCoordinateTransform ct( layer->crs(), mCrs, mTransformContext );
164
165 QgsRectangle layerTileExtent = mTileExtent;
166 try
167 {
168 QgsCoordinateTransform extentTransform = ct;
169 extentTransform.setBallparkTransformsAreAppropriate( true );
170 layerTileExtent = extentTransform.transformBoundingBox( layerTileExtent, Qgis::TransformDirection::Reverse );
171 if ( !layerTileExtent.intersects( layer->extent() ) )
172 {
173 return; // tile is completely outside of the layer'e extent
174 }
175 }
176 catch ( const QgsCsException & )
177 {
178 QgsDebugMsg( "Failed to reproject tile extent to the layer" );
179 return;
180 }
181
182 if ( layerName.isEmpty() )
183 layerName = layer->name();
184
185 // add buffer to both filter extent in layer CRS (for feature request) and tile extent in target CRS (for clipping)
186 const double bufferRatio = static_cast<double>( mBuffer ) / mResolution;
187 QgsRectangle tileExtent = mTileExtent;
188 tileExtent.grow( bufferRatio * mTileExtent.width() );
189 layerTileExtent.grow( bufferRatio * std::max( layerTileExtent.width(), layerTileExtent.height() ) );
190
191 QgsFeatureRequest request;
192 request.setFilterRect( layerTileExtent );
193 if ( !filterExpression.isEmpty() )
194 request.setFilterExpression( filterExpression );
195 QgsFeatureIterator fit = layer->getFeatures( request );
196
197 QgsFeature f;
198 if ( !fit.nextFeature( f ) )
199 {
200 return; // nothing to write - do not add the layer at all
201 }
202
203 vector_tile::Tile_Layer *tileLayer = tile.add_layers();
204 tileLayer->set_name( layerName.toUtf8() );
205 tileLayer->set_version( 2 ); // 2 means MVT spec version 2.1
206 tileLayer->set_extent( static_cast<::google::protobuf::uint32>( mResolution ) );
207
208 const QgsFields fields = layer->fields();
209 for ( int i = 0; i < fields.count(); ++i )
210 {
211 tileLayer->add_keys( fields[i].name().toUtf8() );
212 }
213
214 do
215 {
216 if ( feedback && feedback->isCanceled() )
217 break;
218
219 QgsGeometry g = f.geometry();
220
221 // reproject
222 try
223 {
224 g.transform( ct );
225 }
226 catch ( const QgsCsException & )
227 {
228 QgsDebugMsg( "Failed to reproject geometry " + QString::number( f.id() ) );
229 continue;
230 }
231
232 // clip
233 g = g.clipped( tileExtent );
234
235 f.setGeometry( g );
236
237 addFeature( tileLayer, f );
238 }
239 while ( fit.nextFeature( f ) );
240
241 mKnownValues.clear();
242}
243
244void QgsVectorTileMVTEncoder::addFeature( vector_tile::Tile_Layer *tileLayer, const QgsFeature &f )
245{
246 QgsGeometry g = f.geometry();
247 const Qgis::GeometryType geomType = g.type();
248 const double onePixel = mTileExtent.width() / mResolution;
249
250 if ( geomType == Qgis::GeometryType::Line )
251 {
252 if ( g.length() < onePixel )
253 return; // too short
254 }
255 else if ( geomType == Qgis::GeometryType::Polygon )
256 {
257 if ( g.area() < onePixel * onePixel )
258 return; // too small
259 }
260
261 vector_tile::Tile_Feature *feature = tileLayer->add_features();
262
263 feature->set_id( static_cast<quint64>( f.id() ) );
264
265 //
266 // encode attributes
267 //
268
269 const QgsAttributes attrs = f.attributes();
270 for ( int i = 0; i < attrs.count(); ++i )
271 {
272 const QVariant v = attrs.at( i );
273 if ( QgsVariantUtils::isNull( v ) )
274 continue;
275
276 int valueIndex;
277 if ( mKnownValues.contains( v ) )
278 {
279 valueIndex = mKnownValues[v];
280 }
281 else
282 {
283 vector_tile::Tile_Value *value = tileLayer->add_values();
284 valueIndex = tileLayer->values_size() - 1;
285 mKnownValues[v] = valueIndex;
286
287 if ( v.type() == QVariant::Double )
288 value->set_double_value( v.toDouble() );
289 else if ( v.type() == QVariant::Int )
290 value->set_int_value( v.toInt() );
291 else if ( v.type() == QVariant::Bool )
292 value->set_bool_value( v.toBool() );
293 else
294 value->set_string_value( v.toString().toUtf8().toStdString() );
295 }
296
297 feature->add_tags( static_cast<quint32>( i ) );
298 feature->add_tags( static_cast<quint32>( valueIndex ) );
299 }
300
301 //
302 // encode geometry
303 //
304
305 vector_tile::Tile_GeomType mvtGeomType = vector_tile::Tile_GeomType_UNKNOWN;
306 if ( geomType == Qgis::GeometryType::Point )
307 mvtGeomType = vector_tile::Tile_GeomType_POINT;
308 else if ( geomType == Qgis::GeometryType::Line )
309 mvtGeomType = vector_tile::Tile_GeomType_LINESTRING;
310 else if ( geomType == Qgis::GeometryType::Polygon )
311 mvtGeomType = vector_tile::Tile_GeomType_POLYGON;
312 feature->set_type( mvtGeomType );
313
315 {
316 g = QgsGeometry( g.get()->segmentize() );
317 }
318
319 MVTGeometryWriter geomWriter( feature, mResolution, mTileExtent );
320
321 const QgsAbstractGeometry *geom = g.constGet();
322 switch ( QgsWkbTypes::flatType( g.wkbType() ) )
323 {
325 {
326 const QgsPoint *pt = static_cast<const QgsPoint *>( geom );
327 geomWriter.addMoveTo( 1 );
328 geomWriter.addPoint( *pt );
329 }
330 break;
331
333 {
334 encodeLineString( qgsgeometry_cast<const QgsLineString *>( geom ), true, false, geomWriter );
335 }
336 break;
337
339 {
340 encodePolygon( static_cast<const QgsPolygon *>( geom ), geomWriter );
341 }
342 break;
343
345 {
346 const QgsMultiPoint *mpt = static_cast<const QgsMultiPoint *>( geom );
347 geomWriter.addMoveTo( mpt->numGeometries() );
348 for ( int i = 0; i < mpt->numGeometries(); ++i )
349 geomWriter.addPoint( *mpt->pointN( i ) );
350 }
351 break;
352
354 {
355 const QgsMultiLineString *mls = qgsgeometry_cast<const QgsMultiLineString *>( geom );
356 for ( int i = 0; i < mls->numGeometries(); ++i )
357 {
358 encodeLineString( mls->lineStringN( i ), true, false, geomWriter );
359 }
360 }
361 break;
362
364 {
365 const QgsMultiPolygon *mp = qgsgeometry_cast<const QgsMultiPolygon *>( geom );
366 for ( int i = 0; i < mp->numGeometries(); ++i )
367 {
368 encodePolygon( mp->polygonN( i ), geomWriter );
369 }
370 }
371 break;
372
373 default:
374 break;
375 }
376}
377
378
380{
381 return QByteArray::fromStdString( tile.SerializeAsString() );
382}
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
Definition: qgis.h:228
@ LineString
LineString.
@ MultiPoint
MultiPoint.
@ Polygon
Polygon.
@ MultiPolygon
MultiPolygon.
@ MultiLineString
MultiLineString.
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 SIP_THROW(QgsCsException)
Transforms a rectangle from the source CRS to the destination CRS.
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:66
const QgsCurve * interiorRing(int i) const SIP_HOLDGIL
Retrieves an interior ring from the curve polygon.
const QgsCurve * exteriorRing() const SIP_HOLDGIL
Returns the curve polygon's exterior ring.
int numInteriorRings() const SIP_HOLDGIL
Returns the number of interior rings contained with the curve polygon.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
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:170
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:45
bool isCanceled() const SIP_HOLDGIL
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:54
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 SIP_HOLDGIL
Returns the number of geometries within the collection.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:164
QgsGeometry clipped(const QgsRectangle &rectangle)
Clips the geometry using the specified rectangle.
const QgsAbstractGeometry * constGet() const SIP_HOLDGIL
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
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) SIP_THROW(QgsCsException)
Transforms this geometry as described by the coordinate transform ct.
Qgis::WkbType wkbType() const SIP_HOLDGIL
Returns type of the geometry as a WKB type (point / linestring / polygon etc.)
QgsAbstractGeometry * get()
Returns a modifiable (non-const) reference to the underlying abstract geometry primitive.
Qgis::GeometryType type
Definition: qgsgeometry.h:167
double area() const
Returns the planar, 2-dimensional area of the geometry.
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 SIP_HOLDGIL
Returns the number of points in the curve.
QString name
Definition: qgsmaplayer.h:76
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:79
Multi line string geometry collection.
QgsLineString * lineStringN(int index)
Returns the line string with the specified index.
Multi point geometry collection.
Definition: qgsmultipoint.h:30
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:34
A rectangle specified with double values.
Definition: qgsrectangle.h:42
bool intersects(const QgsRectangle &rect) const SIP_HOLDGIL
Returns true when rectangle intersects with other rectangle.
Definition: qgsrectangle.h:349
double height() const SIP_HOLDGIL
Returns the height of the rectangle.
Definition: qgsrectangle.h:230
void grow(double delta)
Grows the rectangle in place by the specified amount.
Definition: qgsrectangle.h:296
double width() const SIP_HOLDGIL
Returns the width of the rectangle.
Definition: qgsrectangle.h:223
Defines a matrix of tiles for a single zoom level: it is defined by its size (width *.
Definition: qgstiles.h:108
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:131
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)
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) SIP_HOLDGIL
Returns true if the WKB type is a curved type or can contain curved geometries.
Definition: qgswkbtypes.h:808
static Qgis::WkbType flatType(Qgis::WkbType type) SIP_HOLDGIL
Returns the flat type for a WKB type.
Definition: qgswkbtypes.h:629
#define QgsDebugMsg(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)