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