QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
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 
37 
38 bool QgsVectorTileMVTDecoder::decode( QgsTileXYZ tileID, const QByteArray &rawTileData )
39 {
40  if ( !tile.ParseFromArray( rawTileData.constData(), rawTileData.count() ) )
41  return false;
42 
43  mTileID = tileID;
44 
45  mLayerNameToIndex.clear();
46  for ( int layerNum = 0; layerNum < tile.layers_size(); layerNum++ )
47  {
48  const ::vector_tile::Tile_Layer &layer = tile.layers( layerNum );
49  const QString layerName = layer.name().c_str();
50  mLayerNameToIndex[layerName] = layerNum;
51  }
52  return true;
53 }
54 
56 {
57  QStringList layerNames;
58  for ( int layerNum = 0; layerNum < tile.layers_size(); layerNum++ )
59  {
60  const ::vector_tile::Tile_Layer &layer = tile.layers( layerNum );
61  const QString layerName = layer.name().c_str();
62  layerNames << layerName;
63  }
64  return layerNames;
65 }
66 
67 QStringList QgsVectorTileMVTDecoder::layerFieldNames( const QString &layerName ) const
68 {
69  if ( !mLayerNameToIndex.contains( layerName ) )
70  return QStringList();
71 
72  const ::vector_tile::Tile_Layer &layer = tile.layers( mLayerNameToIndex[layerName] );
73  QStringList fieldNames;
74  for ( int i = 0; i < layer.keys_size(); ++i )
75  {
76  const QString fieldName = layer.keys( i ).c_str();
77  fieldNames << fieldName;
78  }
79  return fieldNames;
80 }
81 
82 QgsVectorTileFeatures QgsVectorTileMVTDecoder::layerFeatures( const QMap<QString, QgsFields> &perLayerFields, const QgsCoordinateTransform &ct, const QSet<QString> *layerSubset ) const
83 {
84  QgsVectorTileFeatures features;
85 
86  const int numTiles = static_cast<int>( pow( 2, mTileID.zoomLevel() ) ); // assuming we won't ever go over 30 zoom levels
87  double z0xMin = -20037508.3427892, z0yMin = -20037508.3427892;
88  double z0xMax = 20037508.3427892, z0yMax = 20037508.3427892;
89  const double tileDX = ( z0xMax - z0xMin ) / numTiles;
90  const double tileDY = ( z0yMax - z0yMin ) / numTiles;
91  const double tileXMin = z0xMin + mTileID.column() * tileDX;
92  const double tileYMax = z0yMax - mTileID.row() * tileDY;
93 
94  for ( int layerNum = 0; layerNum < tile.layers_size(); layerNum++ )
95  {
96  const ::vector_tile::Tile_Layer &layer = tile.layers( layerNum );
97 
98  const QString layerName = layer.name().c_str();
99  if ( layerSubset && !layerSubset->contains( QString() ) && !layerSubset->contains( layerName ) )
100  continue;
101 
102  QVector<QgsFeature> layerFeatures;
103  const QgsFields layerFields = perLayerFields[layerName];
104 
105  // figure out how field indexes in MVT encoding map to field indexes in QgsFields (we may not use all available fields)
106  QHash<int, int> tagKeyIndexToFieldIndex;
107  for ( int i = 0; i < layer.keys_size(); ++i )
108  {
109  const int fieldIndex = layerFields.indexOf( layer.keys( i ).c_str() );
110  if ( fieldIndex != -1 )
111  tagKeyIndexToFieldIndex.insert( i, fieldIndex );
112  }
113 
114  // go through features of a layer
115  for ( int featureNum = 0; featureNum < layer.features_size(); featureNum++ )
116  {
117  const ::vector_tile::Tile_Feature &feature = layer.features( featureNum );
118 
119  QgsFeatureId fid;
120 #if 0
121  // even if a feature has an internal ID, it's not guaranteed to be unique across different
122  // tiles. This may violate the specifications, but it's been seen on mbtiles files in the wild...
123  if ( feature.has_id() )
124  fid = static_cast<QgsFeatureId>( feature.id() );
125  else
126 #endif
127  {
128  // There is no assigned ID, but some parts of QGIS do not work correctly if all IDs are zero
129  // (e.g. labeling will not register two features with the same FID within a single layer),
130  // so let's generate some pseudo-unique FIDs to keep those bits happy
131  fid = featureNum;
132  fid |= ( layerNum & 0xff ) << 24;
133  fid |= ( static_cast<QgsFeatureId>( mTileID.row() ) & 0xff ) << 32;
134  fid |= ( static_cast<QgsFeatureId>( mTileID.column() ) & 0xff ) << 40;
135  }
136 
137  QgsFeature f( layerFields, fid );
138 
139  //
140  // parse attributes
141  //
142 
143  for ( int tagNum = 0; tagNum + 1 < feature.tags_size(); tagNum += 2 )
144  {
145  const int keyIndex = static_cast<int>( feature.tags( tagNum ) );
146  const int fieldIndex = tagKeyIndexToFieldIndex.value( keyIndex, -1 );
147  if ( fieldIndex == -1 )
148  continue;
149 
150  const int valueIndex = static_cast<int>( feature.tags( tagNum + 1 ) );
151  if ( valueIndex >= layer.values_size() )
152  {
153  QgsDebugMsg( QStringLiteral( "Invalid value index for attribute" ) );
154  continue;
155  }
156  const ::vector_tile::Tile_Value &value = layer.values( valueIndex );
157 
158  if ( value.has_string_value() )
159  f.setAttribute( fieldIndex, QString::fromStdString( value.string_value() ) );
160  else if ( value.has_float_value() )
161  f.setAttribute( fieldIndex, static_cast<double>( value.float_value() ) );
162  else if ( value.has_double_value() )
163  f.setAttribute( fieldIndex, value.double_value() );
164  else if ( value.has_int_value() )
165  f.setAttribute( fieldIndex, static_cast<int>( value.int_value() ) );
166  else if ( value.has_uint_value() )
167  f.setAttribute( fieldIndex, static_cast<int>( value.uint_value() ) );
168  else if ( value.has_sint_value() )
169  f.setAttribute( fieldIndex, static_cast<int>( value.sint_value() ) );
170  else if ( value.has_bool_value() )
171  f.setAttribute( fieldIndex, static_cast<bool>( value.bool_value() ) );
172  else
173  {
174  QgsDebugMsg( QStringLiteral( "Unexpected attribute value" ) );
175  }
176  }
177 
178  //
179  // parse geometry
180  //
181 
182  const int extent = static_cast<int>( layer.extent() );
183  int cursorx = 0, cursory = 0;
184 
185  QVector<QgsPoint *> outputPoints; // for point/multi-point
186  QVector<QgsLineString *> outputLinestrings; // for linestring/multi-linestring
187  QVector<QgsPolygon *> outputPolygons;
188  QVector<QgsPoint> tmpPoints;
189 
190  for ( int i = 0; i < feature.geometry_size(); i ++ )
191  {
192  const unsigned g = feature.geometry( i );
193  const unsigned cmdId = g & 0x7;
194  const unsigned cmdCount = g >> 3;
195  if ( cmdId == 1 ) // MoveTo
196  {
197  if ( i + static_cast<int>( cmdCount ) * 2 >= feature.geometry_size() )
198  {
199  QgsDebugMsg( QStringLiteral( "Malformed geometry: invalid cmdCount" ) );
200  break;
201  }
202 
203  if ( feature.type() == vector_tile::Tile_GeomType_POINT )
204  outputPoints.reserve( outputPoints.size() + cmdCount );
205  else
206  tmpPoints.reserve( tmpPoints.size() + cmdCount );
207 
208  for ( unsigned j = 0; j < cmdCount; j++ )
209  {
210  const unsigned v = feature.geometry( i + 1 );
211  const unsigned w = feature.geometry( i + 2 );
212  const int dx = ( ( v >> 1 ) ^ ( -( v & 1 ) ) );
213  const int dy = ( ( w >> 1 ) ^ ( -( w & 1 ) ) );
214  cursorx += dx;
215  cursory += dy;
216  const double px = tileXMin + tileDX * double( cursorx ) / double( extent );
217  const double py = tileYMax - tileDY * double( cursory ) / double( extent );
218 
219  if ( feature.type() == vector_tile::Tile_GeomType_POINT )
220  {
221  outputPoints.append( new QgsPoint( px, py ) );
222  }
223  else if ( feature.type() == vector_tile::Tile_GeomType_LINESTRING )
224  {
225  if ( tmpPoints.size() > 0 )
226  {
227  outputLinestrings.append( new QgsLineString( tmpPoints ) );
228  tmpPoints.clear();
229  }
230  tmpPoints.append( QgsPoint( px, py ) );
231  }
232  else if ( feature.type() == vector_tile::Tile_GeomType_POLYGON )
233  {
234  tmpPoints.append( QgsPoint( px, py ) );
235  }
236  i += 2;
237  }
238  }
239  else if ( cmdId == 2 ) // LineTo
240  {
241  if ( i + static_cast<int>( cmdCount ) * 2 >= feature.geometry_size() )
242  {
243  QgsDebugMsg( QStringLiteral( "Malformed geometry: invalid cmdCount" ) );
244  break;
245  }
246  tmpPoints.reserve( tmpPoints.size() + cmdCount );
247  for ( unsigned j = 0; j < cmdCount; j++ )
248  {
249  const unsigned v = feature.geometry( i + 1 );
250  const unsigned w = feature.geometry( i + 2 );
251  const int dx = ( ( v >> 1 ) ^ ( -( v & 1 ) ) );
252  const int dy = ( ( w >> 1 ) ^ ( -( w & 1 ) ) );
253  cursorx += dx;
254  cursory += dy;
255  const double px = tileXMin + tileDX * double( cursorx ) / double( extent );
256  const double py = tileYMax - tileDY * double( cursory ) / double( extent );
257 
258  tmpPoints.push_back( QgsPoint( px, py ) );
259  i += 2;
260  }
261  }
262  else if ( cmdId == 7 ) // ClosePath
263  {
264  if ( feature.type() == vector_tile::Tile_GeomType_POLYGON )
265  {
266  tmpPoints.append( tmpPoints.first() ); // close the ring
267 
268  std::unique_ptr<QgsLineString> ring( new QgsLineString( tmpPoints ) );
269  tmpPoints.clear();
270 
271  if ( QgsVectorTileMVTUtils::isExteriorRing( ring.get() ) )
272  {
273  // start a new polygon
274  QgsPolygon *p = new QgsPolygon;
275  p->setExteriorRing( ring.release() );
276  outputPolygons.append( p );
277  }
278  else
279  {
280  // interior ring (hole)
281  if ( outputPolygons.count() != 0 )
282  {
283  outputPolygons[outputPolygons.count() - 1]->addInteriorRing( ring.release() );
284  }
285  else
286  {
287  QgsDebugMsg( QStringLiteral( "Malformed geometry: first ring of a polygon is interior ring" ) );
288  }
289  }
290  }
291 
292  }
293  else
294  {
295  QgsDebugMsg( QStringLiteral( "Unexpected command ID: %1" ).arg( cmdId ) );
296  }
297  }
298 
299  QString geomType;
300  if ( feature.type() == vector_tile::Tile_GeomType_POINT )
301  {
302  geomType = QStringLiteral( "Point" );
303  if ( outputPoints.count() == 1 )
304  f.setGeometry( QgsGeometry( outputPoints.at( 0 ) ) );
305  else
306  {
307  QgsMultiPoint *mp = new QgsMultiPoint;
308  mp->reserve( outputPoints.count() );
309  for ( int k = 0; k < outputPoints.count(); ++k )
310  mp->addGeometry( outputPoints[k] );
311  f.setGeometry( QgsGeometry( mp ) );
312  }
313  }
314  else if ( feature.type() == vector_tile::Tile_GeomType_LINESTRING )
315  {
316  geomType = QStringLiteral( "LineString" );
317 
318  // finish the linestring we have started
319  outputLinestrings.append( new QgsLineString( tmpPoints ) );
320 
321  if ( outputLinestrings.count() == 1 )
322  f.setGeometry( QgsGeometry( outputLinestrings.at( 0 ) ) );
323  else
324  {
326  mls->reserve( outputLinestrings.size() );
327  for ( int k = 0; k < outputLinestrings.count(); ++k )
328  mls->addGeometry( outputLinestrings[k] );
329  f.setGeometry( QgsGeometry( mls ) );
330  }
331  }
332  else if ( feature.type() == vector_tile::Tile_GeomType_POLYGON )
333  {
334  geomType = QStringLiteral( "Polygon" );
335 
336  if ( outputPolygons.count() == 1 )
337  f.setGeometry( QgsGeometry( outputPolygons.at( 0 ) ) );
338  else
339  {
340  QgsMultiPolygon *mpl = new QgsMultiPolygon;
341  mpl->reserve( outputPolygons.size() );
342  for ( int k = 0; k < outputPolygons.count(); ++k )
343  mpl->addGeometry( outputPolygons[k] );
344  f.setGeometry( QgsGeometry( mpl ) );
345  }
346  }
347 
348  f.setAttribute( QStringLiteral( "_geom_type" ), geomType );
349  f.geometry().transform( ct );
350 
351  layerFeatures.append( f );
352  }
353 
354  features[layerName] = layerFeatures;
355  }
356  return features;
357 }
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:44
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
Stores coordinates of a tile in a tile matrix set.
Definition: qgstiles.h:34
int zoomLevel() const
Returns tile's zoom level (Z)
Definition: qgstiles.h:47
int column() const
Returns tile's column index (X)
Definition: qgstiles.h:43
int row() const
Returns tile's row index (Y)
Definition: qgstiles.h:45
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()
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...
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)