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