QGIS API Documentation  3.27.0-Master (0e23467727)
qgsvectortilemvtdecoder.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsvectortilemvtdecoder.cpp
3  --------------------------------------
4  Date : March 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 
16 #include <string>
17 
19 
21 #include "qgsvectortilemvtutils.h"
22 #include "qgsvectortileutils.h"
23 
24 #include "qgslogger.h"
25 #include "qgsmultipoint.h"
26 #include "qgslinestring.h"
27 #include "qgsmultilinestring.h"
28 #include "qgsmultipolygon.h"
29 #include "qgspolygon.h"
30 
31 #include <QPointer>
32 
33 
35  : mStructure( structure )
36 {}
37 
39 
40 bool QgsVectorTileMVTDecoder::decode( QgsTileXYZ tileID, const QByteArray &rawTileData )
41 {
42  if ( !tile.ParseFromArray( rawTileData.constData(), rawTileData.count() ) )
43  return false;
44 
45  mTileID = tileID;
46 
47  mLayerNameToIndex.clear();
48  for ( int layerNum = 0; layerNum < tile.layers_size(); layerNum++ )
49  {
50  const ::vector_tile::Tile_Layer &layer = tile.layers( layerNum );
51  const QString layerName = layer.name().c_str();
52  mLayerNameToIndex[layerName] = layerNum;
53  }
54  return true;
55 }
56 
58 {
59  QStringList layerNames;
60  const int layerSize = tile.layers_size();
61  layerNames.reserve( layerSize );
62  for ( int layerNum = 0; layerNum < layerSize; layerNum++ )
63  {
64  const ::vector_tile::Tile_Layer &layer = tile.layers( layerNum );
65  const QString layerName = layer.name().c_str();
66  layerNames << layerName;
67  }
68  return layerNames;
69 }
70 
71 QStringList QgsVectorTileMVTDecoder::layerFieldNames( const QString &layerName ) const
72 {
73  if ( !mLayerNameToIndex.contains( layerName ) )
74  return QStringList();
75 
76  const ::vector_tile::Tile_Layer &layer = tile.layers( mLayerNameToIndex[layerName] );
77  QStringList fieldNames;
78  const int size = layer.keys_size();
79  fieldNames.reserve( size );
80  for ( int i = 0; i < size; ++i )
81  {
82  const QString fieldName = layer.keys( i ).c_str();
83  fieldNames << fieldName;
84  }
85  return fieldNames;
86 }
87 
88 QgsVectorTileFeatures QgsVectorTileMVTDecoder::layerFeatures( const QMap<QString, QgsFields> &perLayerFields, const QgsCoordinateTransform &ct, const QSet<QString> *layerSubset ) const
89 {
90  QgsVectorTileFeatures features;
91 
92  const int numTiles = static_cast<int>( pow( 2, mTileID.zoomLevel() ) ); // assuming we won't ever go over 30 zoom levels
93 
94  const double z0Width = mStructure.tileMatrix( 0 ).extent().width();
95  const double z0Height = mStructure.tileMatrix( 0 ).extent().height();
96  const double z0xMinimum = mStructure.tileMatrix( 0 ).extent().xMinimum();
97  const double z0yMaximum = mStructure.tileMatrix( 0 ).extent().yMaximum();
98 
99  const double tileDX = z0Width / numTiles;
100  const double tileDY = z0Height / numTiles;
101  const double tileXMin = z0xMinimum + mTileID.column() * tileDX;
102  const double tileYMax = z0yMaximum - mTileID.row() * tileDY;
103 
104  for ( int layerNum = 0; layerNum < tile.layers_size(); layerNum++ )
105  {
106  const ::vector_tile::Tile_Layer &layer = tile.layers( layerNum );
107 
108  const QString layerName = layer.name().c_str();
109  if ( layerSubset && !layerSubset->contains( QString() ) && !layerSubset->contains( layerName ) )
110  continue;
111 
112  QVector<QgsFeature> layerFeatures;
113  const QgsFields layerFields = perLayerFields[layerName];
114 
115  // figure out how field indexes in MVT encoding map to field indexes in QgsFields (we may not use all available fields)
116  QHash<int, int> tagKeyIndexToFieldIndex;
117  for ( int i = 0; i < layer.keys_size(); ++i )
118  {
119  const int fieldIndex = layerFields.indexOf( layer.keys( i ).c_str() );
120  if ( fieldIndex != -1 )
121  tagKeyIndexToFieldIndex.insert( i, fieldIndex );
122  }
123 
124  // go through features of a layer
125  for ( int featureNum = 0; featureNum < layer.features_size(); featureNum++ )
126  {
127  const ::vector_tile::Tile_Feature &feature = layer.features( featureNum );
128 
129  QgsFeatureId fid;
130 #if 0
131  // even if a feature has an internal ID, it's not guaranteed to be unique across different
132  // tiles. This may violate the specifications, but it's been seen on mbtiles files in the wild...
133  if ( feature.has_id() )
134  fid = static_cast<QgsFeatureId>( feature.id() );
135  else
136 #endif
137  {
138  // There is no assigned ID, but some parts of QGIS do not work correctly if all IDs are zero
139  // (e.g. labeling will not register two features with the same FID within a single layer),
140  // so let's generate some pseudo-unique FIDs to keep those bits happy
141  fid = featureNum;
142  fid |= ( layerNum & 0xff ) << 24;
143  fid |= ( static_cast<QgsFeatureId>( mTileID.row() ) & 0xff ) << 32;
144  fid |= ( static_cast<QgsFeatureId>( mTileID.column() ) & 0xff ) << 40;
145  }
146 
147  QgsFeature f( layerFields, fid );
148 
149  //
150  // parse attributes
151  //
152 
153  for ( int tagNum = 0; tagNum + 1 < feature.tags_size(); tagNum += 2 )
154  {
155  const int keyIndex = static_cast<int>( feature.tags( tagNum ) );
156  const int fieldIndex = tagKeyIndexToFieldIndex.value( keyIndex, -1 );
157  if ( fieldIndex == -1 )
158  continue;
159 
160  const int valueIndex = static_cast<int>( feature.tags( tagNum + 1 ) );
161  if ( valueIndex >= layer.values_size() )
162  {
163  QgsDebugMsg( QStringLiteral( "Invalid value index for attribute" ) );
164  continue;
165  }
166  const ::vector_tile::Tile_Value &value = layer.values( valueIndex );
167 
168  if ( value.has_string_value() )
169  f.setAttribute( fieldIndex, QString::fromStdString( value.string_value() ) );
170  else if ( value.has_float_value() )
171  f.setAttribute( fieldIndex, static_cast<double>( value.float_value() ) );
172  else if ( value.has_double_value() )
173  f.setAttribute( fieldIndex, value.double_value() );
174  else if ( value.has_int_value() )
175  f.setAttribute( fieldIndex, static_cast<int>( value.int_value() ) );
176  else if ( value.has_uint_value() )
177  f.setAttribute( fieldIndex, static_cast<int>( value.uint_value() ) );
178  else if ( value.has_sint_value() )
179  f.setAttribute( fieldIndex, static_cast<int>( value.sint_value() ) );
180  else if ( value.has_bool_value() )
181  f.setAttribute( fieldIndex, static_cast<bool>( value.bool_value() ) );
182  else
183  {
184  QgsDebugMsg( QStringLiteral( "Unexpected attribute value" ) );
185  }
186  }
187 
188  //
189  // parse geometry
190  //
191 
192  const int extent = static_cast<int>( layer.extent() );
193  int cursorx = 0, cursory = 0;
194 
195  QVector<QgsPoint *> outputPoints; // for point/multi-point
196  QVector<QgsLineString *> outputLinestrings; // for linestring/multi-linestring
197  QVector<QgsPolygon *> outputPolygons;
198  QVector<QgsPoint> tmpPoints;
199 
200  for ( int i = 0; i < feature.geometry_size(); i ++ )
201  {
202  const unsigned g = feature.geometry( i );
203  const unsigned cmdId = g & 0x7;
204  const unsigned cmdCount = g >> 3;
205  if ( cmdId == 1 ) // MoveTo
206  {
207  if ( i + static_cast<int>( cmdCount ) * 2 >= feature.geometry_size() )
208  {
209  QgsDebugMsg( QStringLiteral( "Malformed geometry: invalid cmdCount" ) );
210  break;
211  }
212 
213  if ( feature.type() == vector_tile::Tile_GeomType_POINT )
214  outputPoints.reserve( outputPoints.size() + cmdCount );
215  else
216  tmpPoints.reserve( tmpPoints.size() + cmdCount );
217 
218  for ( unsigned j = 0; j < cmdCount; j++ )
219  {
220  const unsigned v = feature.geometry( i + 1 );
221  const unsigned w = feature.geometry( i + 2 );
222  const int dx = ( ( v >> 1 ) ^ ( -( v & 1 ) ) );
223  const int dy = ( ( w >> 1 ) ^ ( -( w & 1 ) ) );
224  cursorx += dx;
225  cursory += dy;
226  const double px = tileXMin + tileDX * double( cursorx ) / double( extent );
227  const double py = tileYMax - tileDY * double( cursory ) / double( extent );
228 
229  if ( feature.type() == vector_tile::Tile_GeomType_POINT )
230  {
231  outputPoints.append( new QgsPoint( px, py ) );
232  }
233  else if ( feature.type() == vector_tile::Tile_GeomType_LINESTRING )
234  {
235  if ( tmpPoints.size() > 0 )
236  {
237  outputLinestrings.append( new QgsLineString( tmpPoints ) );
238  tmpPoints.clear();
239  }
240  tmpPoints.append( QgsPoint( px, py ) );
241  }
242  else if ( feature.type() == vector_tile::Tile_GeomType_POLYGON )
243  {
244  tmpPoints.append( QgsPoint( px, py ) );
245  }
246  i += 2;
247  }
248  }
249  else if ( cmdId == 2 ) // LineTo
250  {
251  if ( i + static_cast<int>( cmdCount ) * 2 >= feature.geometry_size() )
252  {
253  QgsDebugMsg( QStringLiteral( "Malformed geometry: invalid cmdCount" ) );
254  break;
255  }
256  tmpPoints.reserve( tmpPoints.size() + cmdCount );
257  for ( unsigned j = 0; j < cmdCount; j++ )
258  {
259  const unsigned v = feature.geometry( i + 1 );
260  const unsigned w = feature.geometry( i + 2 );
261  const int dx = ( ( v >> 1 ) ^ ( -( v & 1 ) ) );
262  const int dy = ( ( w >> 1 ) ^ ( -( w & 1 ) ) );
263  cursorx += dx;
264  cursory += dy;
265  const double px = tileXMin + tileDX * double( cursorx ) / double( extent );
266  const double py = tileYMax - tileDY * double( cursory ) / double( extent );
267 
268  tmpPoints.push_back( QgsPoint( px, py ) );
269  i += 2;
270  }
271  }
272  else if ( cmdId == 7 ) // ClosePath
273  {
274  if ( feature.type() == vector_tile::Tile_GeomType_POLYGON )
275  {
276  tmpPoints.append( tmpPoints.first() ); // close the ring
277 
278  std::unique_ptr<QgsLineString> ring( new QgsLineString( tmpPoints ) );
279  tmpPoints.clear();
280 
281  if ( QgsVectorTileMVTUtils::isExteriorRing( ring.get() ) )
282  {
283  // start a new polygon
284  QgsPolygon *p = new QgsPolygon;
285  p->setExteriorRing( ring.release() );
286  outputPolygons.append( p );
287  }
288  else
289  {
290  // interior ring (hole)
291  if ( outputPolygons.count() != 0 )
292  {
293  outputPolygons[outputPolygons.count() - 1]->addInteriorRing( ring.release() );
294  }
295  else
296  {
297  QgsDebugMsg( QStringLiteral( "Malformed geometry: first ring of a polygon is interior ring" ) );
298  }
299  }
300  }
301 
302  }
303  else
304  {
305  QgsDebugMsg( QStringLiteral( "Unexpected command ID: %1" ).arg( cmdId ) );
306  }
307  }
308 
309  QString geomType;
310  if ( feature.type() == vector_tile::Tile_GeomType_POINT )
311  {
312  geomType = QStringLiteral( "Point" );
313  if ( outputPoints.count() == 1 )
314  f.setGeometry( QgsGeometry( outputPoints.at( 0 ) ) );
315  else
316  {
317  QgsMultiPoint *mp = new QgsMultiPoint;
318  mp->reserve( outputPoints.count() );
319  for ( int k = 0; k < outputPoints.count(); ++k )
320  mp->addGeometry( outputPoints[k] );
321  f.setGeometry( QgsGeometry( mp ) );
322  }
323  }
324  else if ( feature.type() == vector_tile::Tile_GeomType_LINESTRING )
325  {
326  geomType = QStringLiteral( "LineString" );
327 
328  // finish the linestring we have started
329  outputLinestrings.append( new QgsLineString( tmpPoints ) );
330 
331  if ( outputLinestrings.count() == 1 )
332  f.setGeometry( QgsGeometry( outputLinestrings.at( 0 ) ) );
333  else
334  {
336  mls->reserve( outputLinestrings.size() );
337  for ( int k = 0; k < outputLinestrings.count(); ++k )
338  mls->addGeometry( outputLinestrings[k] );
339  f.setGeometry( QgsGeometry( mls ) );
340  }
341  }
342  else if ( feature.type() == vector_tile::Tile_GeomType_POLYGON )
343  {
344  geomType = QStringLiteral( "Polygon" );
345 
346  if ( outputPolygons.count() == 1 )
347  f.setGeometry( QgsGeometry( outputPolygons.at( 0 ) ) );
348  else
349  {
350  QgsMultiPolygon *mpl = new QgsMultiPolygon;
351  mpl->reserve( outputPolygons.size() );
352  for ( int k = 0; k < outputPolygons.count(); ++k )
353  mpl->addGeometry( outputPolygons[k] );
354  f.setGeometry( QgsGeometry( mpl ) );
355  }
356  }
357 
358  f.setAttribute( QStringLiteral( "_geom_type" ), geomType );
359  f.geometry().transform( ct );
360 
361  layerFeatures.append( f );
362  }
363 
364  features[layerName] = layerFeatures;
365  }
366  return features;
367 }
Class for doing transforms between two map coordinate systems.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
bool setAttribute(int field, const QVariant &attr)
Sets an attribute's value by field index.
Definition: qgsfeature.cpp:255
QgsGeometry geometry
Definition: qgsfeature.h:67
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
Definition: qgsfeature.cpp:163
Container of fields for a vector layer.
Definition: qgsfields.h:45
int indexOf(const QString &fieldName) const
Gets the field index from the field name.
Definition: qgsfields.cpp:207
void reserve(int size) SIP_HOLDGIL
Attempts to allocate memory for at least size geometries.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:125
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.
Line string geometry type, with support for z-dimension and m-values.
Definition: qgslinestring.h:45
Multi line string geometry collection.
bool addGeometry(QgsAbstractGeometry *g) override
Adds a geometry and takes ownership. Returns true in case of success.
Multi point geometry collection.
Definition: qgsmultipoint.h:30
bool addGeometry(QgsAbstractGeometry *g) override
Adds a geometry and takes ownership. Returns true in case of success.
Multi polygon geometry collection.
bool addGeometry(QgsAbstractGeometry *g) override
Adds a geometry and takes ownership. Returns true in case of success.
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:49
Polygon geometry type.
Definition: qgspolygon.h:34
void setExteriorRing(QgsCurve *ring) override
Sets the exterior ring of the polygon.
Definition: qgspolygon.cpp:219
double yMaximum() const SIP_HOLDGIL
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:193
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:188
double height() const SIP_HOLDGIL
Returns the height of the rectangle.
Definition: qgsrectangle.h:230
double width() const SIP_HOLDGIL
Returns the width of the rectangle.
Definition: qgsrectangle.h:223
QgsTileMatrix tileMatrix(int zoom) const
Returns the tile matrix corresponding to the specified zoom.
Definition: qgstiles.cpp:148
QgsRectangle extent() const
Returns extent of the tile matrix.
Definition: qgstiles.h:163
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
int column() const
Returns tile's column index (X)
Definition: qgstiles.h:47
int row() const
Returns tile's row index (Y)
Definition: qgstiles.h:49
QStringList layerFieldNames(const QString &layerName) const
Returns a list of all field names in a tile. It can only be called after a successful decode()
QStringList layers() const
Returns a list of sub-layer names in a tile. It can only be called after a successful decode()
QgsVectorTileMVTDecoder(const QgsVectorTileMatrixSet &structure)
Constructor for QgsVectorTileMVTDecoder, using the specified tile structure.
QgsVectorTileFeatures layerFeatures(const QMap< QString, QgsFields > &perLayerFields, const QgsCoordinateTransform &ct, const QSet< QString > *layerSubset=nullptr) const
Returns decoded features grouped by sub-layers.
bool decode(QgsTileXYZ tileID, const QByteArray &rawTileData)
Tries to decode raw tile data, returns true on success.
static bool isExteriorRing(const QgsLineString *lineString)
Returns whether this linear ring forms an exterior ring according to MVT spec (depending on the orien...
Encapsulates properties of a vector tile matrix set, including tile origins and scaling information.
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
Definition: qgsfeatureid.h:28
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QMap< QString, QVector< QgsFeature > > QgsVectorTileFeatures
Features of a vector tile, grouped by sub-layer names (key of the map)