QGIS API Documentation 3.30.0-'s-Hertogenbosch (f186b8efe0)
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
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
40bool 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
71QStringList 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
88QgsVectorTileFeatures 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 const QgsTileMatrix &rootMatrix = mStructure.rootMatrix();
94 const double z0Width = rootMatrix.extent().width();
95 const double z0Height = rootMatrix.extent().height();
96 const double z0xMinimum = rootMatrix.extent().xMinimum();
97 const double z0yMaximum = rootMatrix.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 {
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 {
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:265
QgsGeometry geometry
Definition: qgsfeature.h:67
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
Definition: qgsfeature.cpp:170
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:164
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 rootMatrix() const
Returns the root tile matrix (usually corresponding to zoom level 0).
Definition: qgstiles.cpp:155
Defines a matrix of tiles for a single zoom level: it is defined by its size (width *.
Definition: qgstiles.h:108
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)