QGIS API Documentation  3.20.0-Odense (decaadbb31)
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  qint32 vx = pt.x() - cursor.x();
68  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  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 }
149 
150 void QgsVectorTileMVTEncoder::addLayer( QgsVectorLayer *layer, QgsFeedback *feedback, QString filterExpression, QString layerName )
151 {
152  if ( feedback && feedback->isCanceled() )
153  return;
154 
155  QgsCoordinateTransform ct( layer->crs(), QgsCoordinateReferenceSystem( "EPSG:3857" ), mTransformContext );
156 
157  QgsRectangle layerTileExtent = mTileExtent;
158  try
159  {
160  layerTileExtent = ct.transformBoundingBox( layerTileExtent, QgsCoordinateTransform::ReverseTransform );
161  if ( !layerTileExtent.intersects( layer->extent() ) )
162  {
163  return; // tile is completely outside of the layer'e extent
164  }
165  }
166  catch ( const QgsCsException & )
167  {
168  QgsDebugMsg( "Failed to reproject tile extent to the layer" );
169  return;
170  }
171 
172  if ( layerName.isEmpty() )
173  layerName = layer->name();
174 
175  // add buffer to both filter extent in layer CRS (for feature request) and tile extent in target CRS (for clipping)
176  double bufferRatio = static_cast<double>( mBuffer ) / mResolution;
177  QgsRectangle tileExtent = mTileExtent;
178  tileExtent.grow( bufferRatio * mTileExtent.width() );
179  layerTileExtent.grow( bufferRatio * std::max( layerTileExtent.width(), layerTileExtent.height() ) );
180 
181  QgsFeatureRequest request;
182  request.setFilterRect( layerTileExtent );
183  if ( !filterExpression.isEmpty() )
184  request.setFilterExpression( filterExpression );
185  QgsFeatureIterator fit = layer->getFeatures( request );
186 
187  QgsFeature f;
188  if ( !fit.nextFeature( f ) )
189  {
190  return; // nothing to write - do not add the layer at all
191  }
192 
193  vector_tile::Tile_Layer *tileLayer = tile.add_layers();
194  tileLayer->set_name( layerName.toUtf8() );
195  tileLayer->set_version( 2 ); // 2 means MVT spec version 2.1
196  tileLayer->set_extent( static_cast<::google::protobuf::uint32>( mResolution ) );
197 
198  const QgsFields fields = layer->fields();
199  for ( int i = 0; i < fields.count(); ++i )
200  {
201  tileLayer->add_keys( fields[i].name().toUtf8() );
202  }
203 
204  do
205  {
206  if ( feedback && feedback->isCanceled() )
207  break;
208 
209  QgsGeometry g = f.geometry();
210 
211  // reproject
212  try
213  {
214  g.transform( ct );
215  }
216  catch ( const QgsCsException & )
217  {
218  QgsDebugMsg( "Failed to reproject geometry " + QString::number( f.id() ) );
219  continue;
220  }
221 
222  // clip
223  g = g.clipped( tileExtent );
224 
225  f.setGeometry( g );
226 
227  addFeature( tileLayer, f );
228  }
229  while ( fit.nextFeature( f ) );
230 
231  mKnownValues.clear();
232 }
233 
234 void QgsVectorTileMVTEncoder::addFeature( vector_tile::Tile_Layer *tileLayer, const QgsFeature &f )
235 {
236  QgsGeometry g = f.geometry();
237  QgsWkbTypes::GeometryType geomType = g.type();
238  double onePixel = mTileExtent.width() / mResolution;
239 
240  if ( geomType == QgsWkbTypes::LineGeometry )
241  {
242  if ( g.length() < onePixel )
243  return; // too short
244  }
245  else if ( geomType == QgsWkbTypes::PolygonGeometry )
246  {
247  if ( g.area() < onePixel * onePixel )
248  return; // too small
249  }
250 
251  vector_tile::Tile_Feature *feature = tileLayer->add_features();
252 
253  feature->set_id( static_cast<quint64>( f.id() ) );
254 
255  //
256  // encode attributes
257  //
258 
259  const QgsAttributes attrs = f.attributes();
260  for ( int i = 0; i < attrs.count(); ++i )
261  {
262  const QVariant v = attrs.at( i );
263  if ( !v.isValid() || v.isNull() )
264  continue;
265 
266  int valueIndex;
267  if ( mKnownValues.contains( v ) )
268  {
269  valueIndex = mKnownValues[v];
270  }
271  else
272  {
273  vector_tile::Tile_Value *value = tileLayer->add_values();
274  valueIndex = tileLayer->values_size() - 1;
275  mKnownValues[v] = valueIndex;
276 
277  if ( v.type() == QVariant::Double )
278  value->set_double_value( v.toDouble() );
279  else if ( v.type() == QVariant::Int )
280  value->set_int_value( v.toInt() );
281  else if ( v.type() == QVariant::Bool )
282  value->set_bool_value( v.toBool() );
283  else
284  value->set_string_value( v.toString().toUtf8().toStdString() );
285  }
286 
287  feature->add_tags( static_cast<quint32>( i ) );
288  feature->add_tags( static_cast<quint32>( valueIndex ) );
289  }
290 
291  //
292  // encode geometry
293  //
294 
295  vector_tile::Tile_GeomType mvtGeomType = vector_tile::Tile_GeomType_UNKNOWN;
296  if ( geomType == QgsWkbTypes::PointGeometry )
297  mvtGeomType = vector_tile::Tile_GeomType_POINT;
298  else if ( geomType == QgsWkbTypes::LineGeometry )
299  mvtGeomType = vector_tile::Tile_GeomType_LINESTRING;
300  else if ( geomType == QgsWkbTypes::PolygonGeometry )
301  mvtGeomType = vector_tile::Tile_GeomType_POLYGON;
302  feature->set_type( mvtGeomType );
303 
304  if ( QgsWkbTypes::isCurvedType( g.wkbType() ) )
305  {
306  g = QgsGeometry( g.get()->segmentize() );
307  }
308 
309  MVTGeometryWriter geomWriter( feature, mResolution, mTileExtent );
310 
311  const QgsAbstractGeometry *geom = g.constGet();
312  switch ( QgsWkbTypes::flatType( g.wkbType() ) )
313  {
314  case QgsWkbTypes::Point:
315  {
316  const QgsPoint *pt = static_cast<const QgsPoint *>( geom );
317  geomWriter.addMoveTo( 1 );
318  geomWriter.addPoint( *pt );
319  }
320  break;
321 
323  {
324  encodeLineString( qgsgeometry_cast<const QgsLineString *>( geom ), true, false, geomWriter );
325  }
326  break;
327 
329  {
330  encodePolygon( static_cast<const QgsPolygon *>( geom ), geomWriter );
331  }
332  break;
333 
335  {
336  const QgsMultiPoint *mpt = static_cast<const QgsMultiPoint *>( geom );
337  geomWriter.addMoveTo( mpt->numGeometries() );
338  for ( int i = 0; i < mpt->numGeometries(); ++i )
339  geomWriter.addPoint( *mpt->pointN( i ) );
340  }
341  break;
342 
344  {
345  const QgsMultiLineString *mls = qgsgeometry_cast<const QgsMultiLineString *>( geom );
346  for ( int i = 0; i < mls->numGeometries(); ++i )
347  {
348  encodeLineString( mls->lineStringN( i ), true, false, geomWriter );
349  }
350  }
351  break;
352 
354  {
355  const QgsMultiPolygon *mp = qgsgeometry_cast<const QgsMultiPolygon *>( geom );
356  for ( int i = 0; i < mp->numGeometries(); ++i )
357  {
358  encodePolygon( mp->polygonN( i ), geomWriter );
359  }
360  }
361  break;
362 
363  default:
364  break;
365  }
366 }
367 
368 
370 {
371  return QByteArray::fromStdString( tile.SerializeAsString() );
372 }
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
This class represents a coordinate reference system (CRS).
Class for doing transforms between two map coordinate systems.
@ ReverseTransform
Transform from destination to source CRS.
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, TransformDirection direction=ForwardTransform, 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:145
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:124
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.)
QgsAbstractGeometry * get()
Returns a modifiable (non-const) reference to the underlying abstract geometry primitive.
QgsWkbTypes::GeometryType type
Definition: qgsgeometry.h:127
double area() const
Returns the planar, 2-dimensional area of the geometry.
OperationResult transform(const QgsCoordinateTransform &ct, QgsCoordinateTransform::TransformDirection direction=QgsCoordinateTransform::ForwardTransform, bool transformZ=false) SIP_THROW(QgsCsException)
Transforms this geometry as described by the coordinate transform ct.
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:73
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:76
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:103
QgsRectangle tileExtent(QgsTileXYZ id) const
Returns extent of the given tile in this matrix.
Definition: qgstiles.cpp:38
static QgsTileMatrix fromWebMercator(int mZoomLevel)
Returns a tile matrix for the usual web mercator.
Definition: qgstiles.cpp:20
Stores coordinates of a tile in a tile matrix set.
Definition: qgstiles.h:33
int zoomLevel() const
Returns tile's zoom level (Z)
Definition: qgstiles.h:46
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.
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:881
static Type flatType(Type type) SIP_HOLDGIL
Returns the flat type for a WKB type.
Definition: qgswkbtypes.h:702
#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)