QGIS API Documentation  3.25.0-Master (349eb7cb1e)
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"
26 #include "qgsvectortilemvtutils.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  }
55  void addClosePath()
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 
86 static 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 
125 static 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 
158 void 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 
244 void QgsVectorTileMVTEncoder::addFeature( vector_tile::Tile_Layer *tileLayer, const QgsFeature &f )
245 {
246  QgsGeometry g = f.geometry();
247  const QgsWkbTypes::GeometryType geomType = g.type();
248  const double onePixel = mTileExtent.width() / mResolution;
249 
250  if ( geomType == QgsWkbTypes::LineGeometry )
251  {
252  if ( g.length() < onePixel )
253  return; // too short
254  }
255  else if ( geomType == QgsWkbTypes::PolygonGeometry )
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 ( !v.isValid() || v.isNull() )
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 == QgsWkbTypes::PointGeometry )
307  mvtGeomType = vector_tile::Tile_GeomType_POINT;
308  else if ( geomType == QgsWkbTypes::LineGeometry )
309  mvtGeomType = vector_tile::Tile_GeomType_LINESTRING;
310  else if ( geomType == QgsWkbTypes::PolygonGeometry )
311  mvtGeomType = vector_tile::Tile_GeomType_POLYGON;
312  feature->set_type( mvtGeomType );
313 
314  if ( QgsWkbTypes::isCurvedType( g.wkbType() ) )
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  {
324  case QgsWkbTypes::Point:
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 }
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:58
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:163
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:125
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.
QgsWkbTypes::Type wkbType() const SIP_HOLDGIL
Returns type of the geometry as a WKB type (point / linestring / polygon etc.)
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.
QgsAbstractGeometry * get()
Returns a modifiable (non-const) reference to the underlying abstract geometry primitive.
QgsWkbTypes::GeometryType type
Definition: qgsgeometry.h:128
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:44
const double * yData() const
Returns a const pointer to the y vertex data.
int numPoints() const override SIP_HOLDGIL
Returns the number of points in the curve.
const double * xData() const
Returns a const pointer to the x vertex data.
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
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...
GeometryType
The geometry types are used to group QgsWkbTypes::Type in a coarse way.
Definition: qgswkbtypes.h:141
static bool isCurvedType(Type type) SIP_HOLDGIL
Returns true if the WKB type is a curved type or can contain curved geometries.
Definition: qgswkbtypes.h:911
static Type flatType(Type type) SIP_HOLDGIL
Returns the flat type for a WKB type.
Definition: qgswkbtypes.h:732
#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)