QGIS API Documentation 3.99.0-Master (26c88405ac0)
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 g = g.clipped( tileExtent );
235
236 f.setGeometry( g );
237
238 addFeature( tileLayer, f );
239 }
240 while ( fit.nextFeature( f ) );
241
242 mKnownValues.clear();
243}
244
245void QgsVectorTileMVTEncoder::addFeature( vector_tile::Tile_Layer *tileLayer, const QgsFeature &f )
246{
247 QgsGeometry g = f.geometry();
248 const Qgis::GeometryType geomType = g.type();
249 const double onePixel = mTileExtent.width() / mResolution;
250
251 if ( geomType == Qgis::GeometryType::Line )
252 {
253 if ( g.length() < onePixel )
254 return; // too short
255 }
256 else if ( geomType == Qgis::GeometryType::Polygon )
257 {
258 if ( g.area() < onePixel * onePixel )
259 return; // too small
260 }
261
262 vector_tile::Tile_Feature *feature = tileLayer->add_features();
263
264 feature->set_id( static_cast<quint64>( f.id() ) );
265
266 //
267 // encode attributes
268 //
269
270 const QgsAttributes attrs = f.attributes();
271 for ( int i = 0; i < attrs.count(); ++i )
272 {
273 const QVariant v = attrs.at( i );
274 if ( QgsVariantUtils::isNull( v ) )
275 continue;
276
277 int valueIndex;
278 if ( mKnownValues.contains( v ) )
279 {
280 valueIndex = mKnownValues[v];
281 }
282 else
283 {
284 vector_tile::Tile_Value *value = tileLayer->add_values();
285 valueIndex = tileLayer->values_size() - 1;
286 mKnownValues[v] = valueIndex;
287
288 switch ( v.userType() )
289 {
290 case QMetaType::Type::Double:
291 value->set_double_value( v.toDouble() );
292 break;
293
294 case QMetaType::Type::Float:
295 value->set_float_value( v.toFloat() );
296 break;
297
298 case QMetaType::Type::Int:
299 case QMetaType::Type::Long:
300 case QMetaType::Type::LongLong:
301 value->set_int_value( v.toLongLong() );
302 break;
303
304 case QMetaType::Type::UInt:
305 case QMetaType::Type::ULong:
306 case QMetaType::Type::ULongLong:
307 value->set_uint_value( v.toULongLong() );
308 break;
309
310 case QMetaType::Type::Bool:
311 value->set_bool_value( v.toBool() );
312 break;
313
314 default:
315 value->set_string_value( v.toString().toUtf8().toStdString() );
316 break;
317 }
318
319 }
320
321 feature->add_tags( static_cast<quint32>( i ) );
322 feature->add_tags( static_cast<quint32>( valueIndex ) );
323 }
324
325 //
326 // encode geometry
327 //
328
329 vector_tile::Tile_GeomType mvtGeomType = vector_tile::Tile_GeomType_UNKNOWN;
330 if ( geomType == Qgis::GeometryType::Point )
331 mvtGeomType = vector_tile::Tile_GeomType_POINT;
332 else if ( geomType == Qgis::GeometryType::Line )
333 mvtGeomType = vector_tile::Tile_GeomType_LINESTRING;
334 else if ( geomType == Qgis::GeometryType::Polygon )
335 mvtGeomType = vector_tile::Tile_GeomType_POLYGON;
336 feature->set_type( mvtGeomType );
337
339 {
340 g = QgsGeometry( g.get()->segmentize() );
341 }
342
343 MVTGeometryWriter geomWriter( feature, mResolution, mTileExtent );
344
345 const QgsAbstractGeometry *geom = g.constGet();
346 switch ( QgsWkbTypes::flatType( g.wkbType() ) )
347 {
349 {
350 const QgsPoint *pt = static_cast<const QgsPoint *>( geom );
351 geomWriter.addMoveTo( 1 );
352 geomWriter.addPoint( *pt );
353 }
354 break;
355
357 {
358 encodeLineString( qgsgeometry_cast<const QgsLineString *>( geom ), true, false, geomWriter );
359 }
360 break;
361
363 {
364 encodePolygon( static_cast<const QgsPolygon *>( geom ), geomWriter );
365 }
366 break;
367
369 {
370 const QgsMultiPoint *mpt = static_cast<const QgsMultiPoint *>( geom );
371 geomWriter.addMoveTo( mpt->numGeometries() );
372 for ( int i = 0; i < mpt->numGeometries(); ++i )
373 geomWriter.addPoint( *mpt->pointN( i ) );
374 }
375 break;
376
378 {
379 const QgsMultiLineString *mls = qgsgeometry_cast<const QgsMultiLineString *>( geom );
380 for ( int i = 0; i < mls->numGeometries(); ++i )
381 {
382 encodeLineString( mls->lineStringN( i ), true, false, geomWriter );
383 }
384 }
385 break;
386
388 {
389 const QgsMultiPolygon *mp = qgsgeometry_cast<const QgsMultiPolygon *>( geom );
390 for ( int i = 0; i < mp->numGeometries(); ++i )
391 {
392 encodePolygon( mp->polygonN( i ), geomWriter );
393 }
394 }
395 break;
396
397 default:
398 break;
399 }
400}
401
402
404{
405 return QByteArray::fromStdString( tile.SerializeAsString() );
406}
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.
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)