QGIS API Documentation 3.99.0-Master (752b475928d)
Loading...
Searching...
No Matches
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
30{
31 vector_tile::Tile_Feature *feature = nullptr;
34 QPoint cursor;
35
36 MVTGeometryWriter( vector_tile::Tile_Feature *f, int res, const QgsRectangle &tileExtent )
37 : feature( f )
38 , resolution( res )
39 , tileXMin( tileExtent.xMinimum() )
40 , tileYMax( tileExtent.yMaximum() )
41 , tileDX( tileExtent.width() )
42 , tileDY( tileExtent.height() )
43 {
44 }
45
46 void addMoveTo( int count )
47 {
48 feature->add_geometry( 1 | ( count << 3 ) );
49 }
50 void addLineTo( int count )
51 {
52 feature->add_geometry( 2 | ( count << 3 ) );
53 }
55 {
56 feature->add_geometry( 7 | ( 1 << 3 ) );
57 }
58
59 void addPoint( const QgsPoint &pt )
60 {
61 addPoint( mapToTileCoordinates( pt.x(), pt.y() ) );
62 }
63
64 void addPoint( const QPoint &pt )
65 {
66 const qint32 vx = pt.x() - cursor.x();
67 const qint32 vy = pt.y() - cursor.y();
68
69 // (quint32)(-(qint32)((quint32)vx >> 31)) is a C/C++ compliant way
70 // of doing vx >> 31, which is undefined behavior since vx is signed
71 feature->add_geometry( ( ( quint32 )vx << 1 ) ^ ( ( quint32 )( -( qint32 )( ( quint32 )vx >> 31 ) ) ) );
72 feature->add_geometry( ( ( quint32 )vy << 1 ) ^ ( ( quint32 )( -( qint32 )( ( quint32 )vy >> 31 ) ) ) );
73
74 cursor = pt;
75 }
76
77 QPoint mapToTileCoordinates( double x, double y ) const
78 {
79 return QPoint( static_cast<int>( round( ( x - tileXMin ) * resolution / tileDX ) ),
80 static_cast<int>( round( ( tileYMax - y ) * resolution / tileDY ) ) );
81 }
82};
83
84
85static void encodeLineString( const QgsLineString *lineString, bool isRing, bool reversed, MVTGeometryWriter &geomWriter )
86{
87 int count = lineString->numPoints();
88 const double *xData = lineString->xData();
89 const double *yData = lineString->yData();
90
91 if ( isRing )
92 count--; // the last point in linear ring is repeated - but not in MVT
93
94 // de-duplicate points
95 QVector<QPoint> tilePoints;
96 QPoint last( -9999, -9999 );
97 tilePoints.reserve( count );
98 for ( int i = 0; i < count; ++i )
99 {
100 const QPoint pt = geomWriter.mapToTileCoordinates( xData[i], yData[i] );
101 if ( pt == last )
102 continue;
103
104 tilePoints << pt;
105 last = pt;
106 }
107 count = tilePoints.count();
108 if ( count == 0 )
109 return;
110
111 geomWriter.addMoveTo( 1 );
112 geomWriter.addPoint( tilePoints[0] );
113 geomWriter.addLineTo( count - 1 );
114 if ( reversed )
115 {
116 for ( int i = count - 1; i >= 1; --i )
117 geomWriter.addPoint( tilePoints[i] );
118 }
119 else
120 {
121 for ( int i = 1; i < count; ++i )
122 geomWriter.addPoint( tilePoints[i] );
123 }
124}
125
126static void encodePolygon( const QgsPolygon *polygon, MVTGeometryWriter &geomWriter )
127{
128 const QgsLineString *exteriorRing = qgsgeometry_cast<const QgsLineString *>( polygon->exteriorRing() );
129 encodeLineString( exteriorRing, true, !QgsVectorTileMVTUtils::isExteriorRing( exteriorRing ), geomWriter );
130 geomWriter.addClosePath();
131
132 for ( int i = 0; i < polygon->numInteriorRings(); ++i )
133 {
134 const QgsLineString *interiorRing = qgsgeometry_cast<const QgsLineString *>( polygon->interiorRing( i ) );
135 encodeLineString( interiorRing, true, QgsVectorTileMVTUtils::isExteriorRing( interiorRing ), geomWriter );
136 geomWriter.addClosePath();
137 }
138}
139
140
141//
142
143
145 : mTileID( tileID )
146{
147 const QgsTileMatrix tm = QgsTileMatrix::fromWebMercator( mTileID.zoomLevel() );
148 mTileExtent = tm.tileExtent( mTileID );
149 mCrs = tm.crs();
150}
151
153 : mTileID( tileID )
154{
155 mTileExtent = tileMatrix.tileExtent( mTileID );
156 mCrs = tileMatrix.crs();
157}
158
159void QgsVectorTileMVTEncoder::addLayer( QgsVectorLayer *layer, QgsFeedback *feedback, QString filterExpression, QString layerName )
160{
161 if ( feedback && feedback->isCanceled() )
162 return;
163
164 const QgsCoordinateTransform ct( layer->crs(), mCrs, mTransformContext );
165
166 QgsRectangle layerTileExtent = mTileExtent;
167 try
168 {
169 QgsCoordinateTransform extentTransform = ct;
170 extentTransform.setBallparkTransformsAreAppropriate( true );
171 layerTileExtent = extentTransform.transformBoundingBox( layerTileExtent, Qgis::TransformDirection::Reverse );
172 if ( !layerTileExtent.intersects( layer->extent() ) )
173 {
174 return; // tile is completely outside of the layer'e extent
175 }
176 }
177 catch ( const QgsCsException & )
178 {
179 QgsDebugError( "Failed to reproject tile extent to the layer" );
180 return;
181 }
182
183 if ( layerName.isEmpty() )
184 layerName = layer->name();
185
186 // add buffer to both filter extent in layer CRS (for feature request) and tile extent in target CRS (for clipping)
187 const double bufferRatio = static_cast<double>( mBuffer ) / mResolution;
188 QgsRectangle tileExtent = mTileExtent;
189 tileExtent.grow( bufferRatio * mTileExtent.width() );
190 layerTileExtent.grow( bufferRatio * std::max( layerTileExtent.width(), layerTileExtent.height() ) );
191
192 QgsFeatureRequest request;
193 request.setFilterRect( layerTileExtent );
194 if ( !filterExpression.isEmpty() )
195 request.setFilterExpression( filterExpression );
196 QgsFeatureIterator fit = layer->getFeatures( request );
197
198 QgsFeature f;
199 if ( !fit.nextFeature( f ) )
200 {
201 return; // nothing to write - do not add the layer at all
202 }
203
204 vector_tile::Tile_Layer *tileLayer = tile.add_layers();
205 tileLayer->set_name( layerName.toUtf8().constData() );
206 tileLayer->set_version( 2 ); // 2 means MVT spec version 2.1
207 tileLayer->set_extent( static_cast<::google::protobuf::uint32>( mResolution ) );
208
209 const QgsFields fields = layer->fields();
210 for ( int i = 0; i < fields.count(); ++i )
211 {
212 tileLayer->add_keys( fields[i].name().toUtf8().constData() );
213 }
214
215 do
216 {
217 if ( feedback && feedback->isCanceled() )
218 break;
219
220 QgsGeometry g = f.geometry();
221
222 // reproject
223 try
224 {
225 g.transform( ct );
226 }
227 catch ( const QgsCsException & )
228 {
229 QgsDebugError( "Failed to reproject geometry " + QString::number( f.id() ) );
230 continue;
231 }
232
233 // clip
234 const QgsGeometry clippedToTile = g.clipped( tileExtent );
235 if ( clippedToTile.isEmpty() )
236 {
237 // this can often happen -- the transformation of the tile's bounding box to the layer's bounding box
238 // may have expanded the extent that we are using to filter features by, so we may have retrieved
239 // features that do NOT intersect with the tile extent after reprojection. In this case we must
240 // skip these features as empty geometries are not valid for vector tiles.
241 continue;
242 }
243 f.setGeometry( clippedToTile );
244
245 addFeature( tileLayer, f );
246 }
247 while ( fit.nextFeature( f ) );
248
249 mKnownValues.clear();
250}
251
252void QgsVectorTileMVTEncoder::addFeature( vector_tile::Tile_Layer *tileLayer, const QgsFeature &f )
253{
254 QgsGeometry g = f.geometry();
255 const Qgis::GeometryType geomType = g.type();
256 const double onePixel = mTileExtent.width() / mResolution;
257
258 if ( geomType == Qgis::GeometryType::Line )
259 {
260 if ( g.length() < onePixel )
261 return; // too short
262 }
263 else if ( geomType == Qgis::GeometryType::Polygon )
264 {
265 if ( g.area() < onePixel * onePixel )
266 return; // too small
267 }
268
269 vector_tile::Tile_Feature *feature = tileLayer->add_features();
270
271 feature->set_id( static_cast<quint64>( f.id() ) );
272
273 //
274 // encode attributes
275 //
276
277 const QgsAttributes attrs = f.attributes();
278 for ( int i = 0; i < attrs.count(); ++i )
279 {
280 const QVariant v = attrs.at( i );
281 if ( QgsVariantUtils::isNull( v ) )
282 continue;
283
284 int valueIndex;
285 if ( mKnownValues.contains( v ) )
286 {
287 valueIndex = mKnownValues[v];
288 }
289 else
290 {
291 vector_tile::Tile_Value *value = tileLayer->add_values();
292 valueIndex = tileLayer->values_size() - 1;
293 mKnownValues[v] = valueIndex;
294
295 switch ( v.userType() )
296 {
297 case QMetaType::Type::Double:
298 value->set_double_value( v.toDouble() );
299 break;
300
301 case QMetaType::Type::Float:
302 value->set_float_value( v.toFloat() );
303 break;
304
305 case QMetaType::Type::Int:
306 case QMetaType::Type::Long:
307 case QMetaType::Type::LongLong:
308 value->set_int_value( v.toLongLong() );
309 break;
310
311 case QMetaType::Type::UInt:
312 case QMetaType::Type::ULong:
313 case QMetaType::Type::ULongLong:
314 value->set_uint_value( v.toULongLong() );
315 break;
316
317 case QMetaType::Type::Bool:
318 value->set_bool_value( v.toBool() );
319 break;
320
321 default:
322 value->set_string_value( v.toString().toUtf8().toStdString() );
323 break;
324 }
325
326 }
327
328 feature->add_tags( static_cast<quint32>( i ) );
329 feature->add_tags( static_cast<quint32>( valueIndex ) );
330 }
331
332 //
333 // encode geometry
334 //
335
336 vector_tile::Tile_GeomType mvtGeomType = vector_tile::Tile_GeomType_UNKNOWN;
337 if ( geomType == Qgis::GeometryType::Point )
338 mvtGeomType = vector_tile::Tile_GeomType_POINT;
339 else if ( geomType == Qgis::GeometryType::Line )
340 mvtGeomType = vector_tile::Tile_GeomType_LINESTRING;
341 else if ( geomType == Qgis::GeometryType::Polygon )
342 mvtGeomType = vector_tile::Tile_GeomType_POLYGON;
343 feature->set_type( mvtGeomType );
344
346 {
347 g = QgsGeometry( g.get()->segmentize() );
348 }
349
350 MVTGeometryWriter geomWriter( feature, mResolution, mTileExtent );
351
352 const QgsAbstractGeometry *geom = g.constGet();
353 switch ( QgsWkbTypes::flatType( g.wkbType() ) )
354 {
356 {
357 const QgsPoint *pt = static_cast<const QgsPoint *>( geom );
358 geomWriter.addMoveTo( 1 );
359 geomWriter.addPoint( *pt );
360 }
361 break;
362
364 {
365 encodeLineString( qgsgeometry_cast<const QgsLineString *>( geom ), true, false, geomWriter );
366 }
367 break;
368
370 {
371 encodePolygon( static_cast<const QgsPolygon *>( geom ), geomWriter );
372 }
373 break;
374
376 {
377 const QgsMultiPoint *mpt = static_cast<const QgsMultiPoint *>( geom );
378 geomWriter.addMoveTo( mpt->numGeometries() );
379 for ( int i = 0; i < mpt->numGeometries(); ++i )
380 geomWriter.addPoint( *mpt->pointN( i ) );
381 }
382 break;
383
385 {
386 const QgsMultiLineString *mls = qgsgeometry_cast<const QgsMultiLineString *>( geom );
387 for ( int i = 0; i < mls->numGeometries(); ++i )
388 {
389 encodeLineString( mls->lineStringN( i ), true, false, geomWriter );
390 }
391 }
392 break;
393
395 {
396 const QgsMultiPolygon *mp = qgsgeometry_cast<const QgsMultiPolygon *>( geom );
397 for ( int i = 0; i < mp->numGeometries(); ++i )
398 {
399 encodePolygon( mp->polygonN( i ), geomWriter );
400 }
401 }
402 break;
403
404 default:
405 break;
406 }
407}
408
409
411{
412 return QByteArray::fromStdString( tile.SerializeAsString() );
413}
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
Definition qgis.h:358
@ Point
Points.
Definition qgis.h:359
@ Line
Lines.
Definition qgis.h:360
@ Polygon
Polygons.
Definition qgis.h:361
@ Point
Point.
Definition qgis.h:279
@ LineString
LineString.
Definition qgis.h:280
@ MultiPoint
MultiPoint.
Definition qgis.h:283
@ Polygon
Polygon.
Definition qgis.h:281
@ MultiPolygon
MultiPolygon.
Definition qgis.h:285
@ MultiLineString
MultiLineString.
Definition qgis.h:284
@ Reverse
Reverse/inverse transform (from destination to source).
Definition qgis.h:2673
virtual QgsAbstractGeometry * segmentize(double tolerance=M_PI/180., SegmentationToleranceType toleranceType=MaximumAngle) const
Returns a version of the geometry without curves.
Handles coordinate transforms between two 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.
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.
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:58
QgsAttributes attributes
Definition qgsfeature.h:67
QgsFeatureId id
Definition qgsfeature.h:66
QgsGeometry geometry
Definition qgsfeature.h:69
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
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:46
int count
Definition qgsfields.h:50
void clear()
Removes all fields.
Definition qgsfields.cpp:61
int numGeometries() const
Returns the number of geometries within the collection.
A geometry is the spatial representation of a feature.
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
double area() const
Returns the planar, 2-dimensional area of the geometry.
bool isEmpty() const
Returns true if the geometry is empty (eg a linestring with no vertices, or a collection with no geom...
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.
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:84
QgsCoordinateReferenceSystem crs
Definition qgsmaplayer.h:87
QgsLineString * lineStringN(int index)
Returns the line string with the specified index.
QgsPoint * pointN(int index)
Returns the point with the specified index.
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
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.
bool intersects(const QgsRectangle &rect) const
Returns true when rectangle intersects with other rectangle.
void grow(double delta)
Grows the rectangle in place by the specified amount.
Defines a matrix of tiles for a single zoom level: it is defined by its size (width *.
Definition qgstiles.h:158
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:181
Stores coordinates of a tile in a tile matrix set.
Definition qgstiles.h:39
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 dataset.
QgsRectangle extent() const final
Returns the extent of the layer.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const final
Queries the layer for features specified in request.
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 Q_INVOKABLE bool isCurvedType(Qgis::WkbType type)
Returns true if the WKB type is a curved type or can contain curved geometries.
static Qgis::WkbType flatType(Qgis::WkbType type)
Returns the flat type for a WKB type.
T qgsgeometry_cast(QgsAbstractGeometry *geom)
#define QgsDebugError(str)
Definition qgslogger.h:57
Helper class for writing of geometry commands.
vector_tile::Tile_Feature * feature
void addPoint(const QgsPoint &pt)
QPoint mapToTileCoordinates(double x, double y) const
MVTGeometryWriter(vector_tile::Tile_Feature *f, int res, const QgsRectangle &tileExtent)
void addPoint(const QPoint &pt)