QGIS API Documentation 3.32.0-Lima (311a8cb8a6)
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
18#include "qgsvectortileloader.h"
20
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
41{
42 if ( !tile.ParseFromArray( rawTileData.data.constData(), rawTileData.data.count() ) )
43 return false;
44
45 mTileID = rawTileData.tileGeometryId;
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 QgsDebugError( 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 QgsDebugError( 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 QgsDebugError( 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 QgsDebugError( 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 QgsDebugError( QStringLiteral( "Malformed geometry: first ring of a polygon is interior ring" ) );
298 }
299 }
300 }
301
302 }
303 else
304 {
305 QgsDebugError( 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:262
QgsGeometry geometry
Definition: qgsfeature.h:67
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
Definition: qgsfeature.cpp:167
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:161
Defines a matrix of tiles for a single zoom level: it is defined by its size (width *.
Definition: qgstiles.h:134
QgsRectangle extent() const
Returns extent of the tile matrix.
Definition: qgstiles.h:189
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()
bool decode(const QgsVectorTileRawData &rawTileData)
Tries to decode raw tile data, returns true on success.
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.
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.
Keeps track of raw tile data that need to be decoded.
QByteArray data
Raw tile data.
QgsTileXYZ tileGeometryId
Tile id associated with the raw tile data.
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
Definition: qgsfeatureid.h:28
#define QgsDebugError(str)
Definition: qgslogger.h:38
QMap< QString, QVector< QgsFeature > > QgsVectorTileFeatures
Features of a vector tile, grouped by sub-layer names (key of the map)