QGIS API Documentation  3.14.0-Pi (9f7028fd23)
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
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  QString layerName = layer.name().c_str();
97  QVector<QgsFeature> layerFeatures;
98  QgsFields layerFields = perLayerFields[layerName];
99 
100  // figure out how field indexes in MVT encoding map to field indexes in QgsFields (we may not use all available fields)
101  QHash<int, int> tagKeyIndexToFieldIndex;
102  for ( int i = 0; i < layer.keys_size(); ++i )
103  {
104  int fieldIndex = layerFields.indexOf( layer.keys( i ).c_str() );
105  if ( fieldIndex != -1 )
106  tagKeyIndexToFieldIndex.insert( i, fieldIndex );
107  }
108 
109  // go through features of a layer
110  for ( int featureNum = 0; featureNum < layer.features_size(); featureNum++ )
111  {
112  const ::vector_tile::Tile_Feature &feature = layer.features( featureNum );
113 
114  QgsFeatureId fid;
115  if ( feature.has_id() )
116  fid = static_cast<QgsFeatureId>( feature.id() );
117  else
118  {
119  // There is no assigned ID, but some parts of QGIS do not work correctly if all IDs are zero
120  // (e.g. labeling will not register two features with the same FID within a single layer),
121  // so let's generate some pseudo-unique FIDs to keep those bits happy
122  fid = featureNum;
123  fid |= ( layerNum & 0xff ) << 24;
124  fid |= ( static_cast<QgsFeatureId>( mTileID.row() ) & 0xff ) << 32;
125  fid |= ( static_cast<QgsFeatureId>( mTileID.column() ) & 0xff ) << 40;
126  }
127 
128  QgsFeature f( layerFields, fid );
129 
130  //
131  // parse attributes
132  //
133 
134  for ( int tagNum = 0; tagNum + 1 < feature.tags_size(); tagNum += 2 )
135  {
136  int keyIndex = static_cast<int>( feature.tags( tagNum ) );
137  int fieldIndex = tagKeyIndexToFieldIndex.value( keyIndex, -1 );
138  if ( fieldIndex == -1 )
139  continue;
140 
141  int valueIndex = static_cast<int>( feature.tags( tagNum + 1 ) );
142  if ( valueIndex >= layer.values_size() )
143  {
144  QgsDebugMsg( QStringLiteral( "Invalid value index for attribute" ) );
145  continue;
146  }
147  const ::vector_tile::Tile_Value &value = layer.values( valueIndex );
148 
149  if ( value.has_string_value() )
150  f.setAttribute( fieldIndex, QString::fromStdString( value.string_value() ) );
151  else if ( value.has_float_value() )
152  f.setAttribute( fieldIndex, static_cast<double>( value.float_value() ) );
153  else if ( value.has_double_value() )
154  f.setAttribute( fieldIndex, value.double_value() );
155  else if ( value.has_int_value() )
156  f.setAttribute( fieldIndex, static_cast<int>( value.int_value() ) );
157  else if ( value.has_uint_value() )
158  f.setAttribute( fieldIndex, static_cast<int>( value.uint_value() ) );
159  else if ( value.has_sint_value() )
160  f.setAttribute( fieldIndex, static_cast<int>( value.sint_value() ) );
161  else if ( value.has_bool_value() )
162  f.setAttribute( fieldIndex, static_cast<bool>( value.bool_value() ) );
163  else
164  {
165  QgsDebugMsg( QStringLiteral( "Unexpected attribute value" ) );
166  }
167  }
168 
169  //
170  // parse geometry
171  //
172 
173  int extent = static_cast<int>( layer.extent() );
174  int cursorx = 0, cursory = 0;
175 
176  QVector<QgsPoint *> outputPoints; // for point/multi-point
177  QVector<QgsLineString *> outputLinestrings; // for linestring/multi-linestring
178  QVector<QgsPolygon *> outputPolygons;
179  QVector<QgsPoint> tmpPoints;
180 
181  for ( int i = 0; i < feature.geometry_size(); i ++ )
182  {
183  unsigned g = feature.geometry( i );
184  unsigned cmdId = g & 0x7;
185  unsigned cmdCount = g >> 3;
186  if ( cmdId == 1 ) // MoveTo
187  {
188  if ( i + static_cast<int>( cmdCount ) * 2 >= feature.geometry_size() )
189  {
190  QgsDebugMsg( QStringLiteral( "Malformed geometry: invalid cmdCount" ) );
191  break;
192  }
193  for ( unsigned j = 0; j < cmdCount; j++ )
194  {
195  unsigned v = feature.geometry( i + 1 );
196  unsigned w = feature.geometry( i + 2 );
197  int dx = ( ( v >> 1 ) ^ ( -( v & 1 ) ) );
198  int dy = ( ( w >> 1 ) ^ ( -( w & 1 ) ) );
199  cursorx += dx;
200  cursory += dy;
201  double px = tileXMin + tileDX * double( cursorx ) / double( extent );
202  double py = tileYMax - tileDY * double( cursory ) / double( extent );
203 
204  if ( feature.type() == vector_tile::Tile_GeomType_POINT )
205  {
206  outputPoints.append( new QgsPoint( px, py ) );
207  }
208  else if ( feature.type() == vector_tile::Tile_GeomType_LINESTRING )
209  {
210  if ( tmpPoints.size() > 0 )
211  {
212  outputLinestrings.append( new QgsLineString( tmpPoints ) );
213  tmpPoints.clear();
214  }
215  tmpPoints.append( QgsPoint( px, py ) );
216  }
217  else if ( feature.type() == vector_tile::Tile_GeomType_POLYGON )
218  {
219  tmpPoints.append( QgsPoint( px, py ) );
220  }
221  i += 2;
222  }
223  }
224  else if ( cmdId == 2 ) // LineTo
225  {
226  if ( i + static_cast<int>( cmdCount ) * 2 >= feature.geometry_size() )
227  {
228  QgsDebugMsg( QStringLiteral( "Malformed geometry: invalid cmdCount" ) );
229  break;
230  }
231  for ( unsigned j = 0; j < cmdCount; j++ )
232  {
233  unsigned v = feature.geometry( i + 1 );
234  unsigned w = feature.geometry( i + 2 );
235  int dx = ( ( v >> 1 ) ^ ( -( v & 1 ) ) );
236  int dy = ( ( w >> 1 ) ^ ( -( w & 1 ) ) );
237  cursorx += dx;
238  cursory += dy;
239  double px = tileXMin + tileDX * double( cursorx ) / double( extent );
240  double py = tileYMax - tileDY * double( cursory ) / double( extent );
241 
242  tmpPoints.push_back( QgsPoint( px, py ) );
243  i += 2;
244  }
245  }
246  else if ( cmdId == 7 ) // ClosePath
247  {
248  if ( feature.type() == vector_tile::Tile_GeomType_POLYGON )
249  {
250  tmpPoints.append( tmpPoints.first() ); // close the ring
251 
252  std::unique_ptr<QgsLineString> ring( new QgsLineString( tmpPoints ) );
253  tmpPoints.clear();
254 
255  if ( QgsVectorTileMVTUtils::isExteriorRing( ring.get() ) )
256  {
257  // start a new polygon
258  QgsPolygon *p = new QgsPolygon;
259  p->setExteriorRing( ring.release() );
260  outputPolygons.append( p );
261  }
262  else
263  {
264  // interior ring (hole)
265  if ( outputPolygons.count() != 0 )
266  {
267  outputPolygons[outputPolygons.count() - 1]->addInteriorRing( ring.release() );
268  }
269  else
270  {
271  QgsDebugMsg( QStringLiteral( "Malformed geometry: first ring of a polygon is interior ring" ) );
272  }
273  }
274  }
275 
276  }
277  else
278  {
279  QgsDebugMsg( QStringLiteral( "Unexpected command ID: %1" ).arg( cmdId ) );
280  }
281  }
282 
283  QString geomType;
284  if ( feature.type() == vector_tile::Tile_GeomType_POINT )
285  {
286  geomType = QStringLiteral( "Point" );
287  if ( outputPoints.count() == 1 )
288  f.setGeometry( QgsGeometry( outputPoints[0] ) );
289  else
290  {
291  QgsMultiPoint *mp = new QgsMultiPoint;
292  for ( int k = 0; k < outputPoints.count(); ++k )
293  mp->addGeometry( outputPoints[k] );
294  f.setGeometry( QgsGeometry( mp ) );
295  }
296  }
297  else if ( feature.type() == vector_tile::Tile_GeomType_LINESTRING )
298  {
299  geomType = QStringLiteral( "LineString" );
300 
301  // finish the linestring we have started
302  outputLinestrings.append( new QgsLineString( tmpPoints ) );
303 
304  if ( outputLinestrings.count() == 1 )
305  f.setGeometry( QgsGeometry( outputLinestrings[0] ) );
306  else
307  {
309  for ( int k = 0; k < outputLinestrings.count(); ++k )
310  mls->addGeometry( outputLinestrings[k] );
311  f.setGeometry( QgsGeometry( mls ) );
312  }
313  }
314  else if ( feature.type() == vector_tile::Tile_GeomType_POLYGON )
315  {
316  geomType = QStringLiteral( "Polygon" );
317 
318  if ( outputPolygons.count() == 1 )
319  f.setGeometry( QgsGeometry( outputPolygons[0] ) );
320  else
321  {
322  QgsMultiPolygon *mpl = new QgsMultiPolygon;
323  for ( int k = 0; k < outputPolygons.count(); ++k )
324  mpl->addGeometry( outputPolygons[k] );
325  f.setGeometry( QgsGeometry( mpl ) );
326  }
327  }
328 
329  f.setAttribute( QStringLiteral( "_geom_type" ), geomType );
330  f.geometry().transform( ct );
331 
332  layerFeatures.append( f );
333  }
334 
335  features[layerName] = layerFeatures;
336  }
337  return features;
338 }
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:2836
QgsTileXYZ
Definition: qgstiles.h:32
QgsPolygon
Polygon geometry type.
Definition: qgspolygon.h:33
qgslinestring.h
QgsPoint
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:37
QgsPolygon::setExteriorRing
void setExteriorRing(QgsCurve *ring) override
Sets the exterior ring of the polygon.
Definition: qgspolygon.cpp:214
QgsMultiPoint::addGeometry
bool addGeometry(QgsAbstractGeometry *g) override
Adds a geometry and takes ownership. Returns true in case of success.
Definition: qgsmultipoint.cpp:138
QgsFields
Definition: qgsfields.h:44
QgsMultiLineString
Multi line string geometry collection.
Definition: qgsmultilinestring.h:29
qgsmultipoint.h
QgsFeature::geometry
QgsGeometry geometry
Definition: qgsfeature.h:71
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:43
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:137
qgsvectortilemvtutils.h
qgsvectortilemvtdecoder.h
QgsMultiPolygon
Multi polygon geometry collection.
Definition: qgsmultipolygon.h:29
QgsVectorTileMVTDecoder::~QgsVectorTileMVTDecoder
~QgsVectorTileMVTDecoder()
QgsTileXYZ::zoomLevel
int zoomLevel() const
Returns tile's zoom level (Z)
Definition: qgstiles.h:59
QgsMultiPoint
Multi point geometry collection.
Definition: qgsmultipoint.h:29
QgsFeature::setAttribute
bool setAttribute(int field, const QVariant &attr)
Set an attribute's value by field index.
Definition: qgsfeature.cpp:211
QgsVectorTileMVTDecoder::layerFeatures
QgsVectorTileFeatures layerFeatures(const QMap< QString, QgsFields > &perLayerFields, const QgsCoordinateTransform &ct) const
Returns decoded features grouped by sub-layers. It can only be called after a successful decode()
Definition: qgsvectortilemvtdecoder.cpp:80
QgsTileXYZ::row
int row() const
Returns tile's row index (Y)
Definition: qgstiles.h:57
QgsGeometry
Definition: qgsgeometry.h:122
QgsMultiPolygon::addGeometry
bool addGeometry(QgsAbstractGeometry *g) override
Adds a geometry and takes ownership. Returns true in case of success.
Definition: qgsmultipolygon.cpp:135
qgsvectortilelayerrenderer.h
QgsMultiLineString::addGeometry
bool addGeometry(QgsAbstractGeometry *g) override
Adds a geometry and takes ownership. Returns true in case of success.
Definition: qgsmultilinestring.cpp:121
QgsFeature
Definition: qgsfeature.h:55
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
QgsCoordinateTransform
Definition: qgscoordinatetransform.h:52
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:55
QgsFeatureId
qint64 QgsFeatureId
Definition: qgsfeatureid.h:25