QGIS API Documentation 3.39.0-Master (73c886ac97c)
Loading...
Searching...
No Matches
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 QgsFields layerFields = perLayerFields[layerName];
114
115 const auto allLayerFields = perLayerFields.find( QString() );
116 if ( allLayerFields != perLayerFields.end() )
117 {
118 // need to add the fields from any "all layer" rules to every layer
119 for ( const QgsField &field : allLayerFields.value() )
120 {
121 if ( layerFields.lookupField( field.name() ) == -1 )
122 {
123 layerFields.append( field );
124 }
125 }
126 }
127
128 // figure out how field indexes in MVT encoding map to field indexes in QgsFields (we may not use all available fields)
129 QHash<int, int> tagKeyIndexToFieldIndex;
130 for ( int i = 0; i < layer.keys_size(); ++i )
131 {
132 const int fieldIndex = layerFields.indexOf( layer.keys( i ).c_str() );
133 if ( fieldIndex != -1 )
134 tagKeyIndexToFieldIndex.insert( i, fieldIndex );
135 }
136
137 // go through features of a layer
138 for ( int featureNum = 0; featureNum < layer.features_size(); featureNum++ )
139 {
140 const ::vector_tile::Tile_Feature &feature = layer.features( featureNum );
141
142 QgsFeatureId fid;
143#if 0
144 // even if a feature has an internal ID, it's not guaranteed to be unique across different
145 // tiles. This may violate the specifications, but it's been seen on mbtiles files in the wild...
146 if ( feature.has_id() )
147 fid = static_cast<QgsFeatureId>( feature.id() );
148 else
149#endif
150 {
151 // There is no assigned ID, but some parts of QGIS do not work correctly if all IDs are zero
152 // (e.g. labeling will not register two features with the same FID within a single layer),
153 // so let's generate some pseudo-unique FIDs to keep those bits happy
154 fid = featureNum;
155 fid |= ( layerNum & 0xff ) << 24;
156 fid |= ( static_cast<QgsFeatureId>( mTileID.row() ) & 0xff ) << 32;
157 fid |= ( static_cast<QgsFeatureId>( mTileID.column() ) & 0xff ) << 40;
158 }
159
160 QgsFeature f( layerFields, fid );
161
162 //
163 // parse attributes
164 //
165
166 for ( int tagNum = 0; tagNum + 1 < feature.tags_size(); tagNum += 2 )
167 {
168 const int keyIndex = static_cast<int>( feature.tags( tagNum ) );
169 const int fieldIndex = tagKeyIndexToFieldIndex.value( keyIndex, -1 );
170 if ( fieldIndex == -1 )
171 continue;
172
173 const int valueIndex = static_cast<int>( feature.tags( tagNum + 1 ) );
174 if ( valueIndex >= layer.values_size() )
175 {
176 QgsDebugError( QStringLiteral( "Invalid value index for attribute" ) );
177 continue;
178 }
179 const ::vector_tile::Tile_Value &value = layer.values( valueIndex );
180
181 if ( value.has_string_value() )
182 f.setAttribute( fieldIndex, QString::fromStdString( value.string_value() ) );
183 else if ( value.has_float_value() )
184 f.setAttribute( fieldIndex, static_cast<double>( value.float_value() ) );
185 else if ( value.has_double_value() )
186 f.setAttribute( fieldIndex, value.double_value() );
187 else if ( value.has_int_value() )
188 f.setAttribute( fieldIndex, static_cast<int>( value.int_value() ) );
189 else if ( value.has_uint_value() )
190 f.setAttribute( fieldIndex, static_cast<int>( value.uint_value() ) );
191 else if ( value.has_sint_value() )
192 f.setAttribute( fieldIndex, static_cast<int>( value.sint_value() ) );
193 else if ( value.has_bool_value() )
194 f.setAttribute( fieldIndex, static_cast<bool>( value.bool_value() ) );
195 else
196 {
197 QgsDebugError( QStringLiteral( "Unexpected attribute value" ) );
198 }
199 }
200
201 //
202 // parse geometry
203 //
204
205 const int extent = static_cast<int>( layer.extent() );
206 int cursorx = 0, cursory = 0;
207
208 QVector<QgsPoint *> outputPoints; // for point/multi-point
209 QVector<QgsLineString *> outputLinestrings; // for linestring/multi-linestring
210 QVector<QgsPolygon *> outputPolygons;
211 QVector<QgsPoint> tmpPoints;
212
213 for ( int i = 0; i < feature.geometry_size(); i ++ )
214 {
215 const unsigned g = feature.geometry( i );
216 const unsigned cmdId = g & 0x7;
217 const unsigned cmdCount = g >> 3;
218 if ( cmdId == 1 ) // MoveTo
219 {
220 if ( i + static_cast<int>( cmdCount ) * 2 >= feature.geometry_size() )
221 {
222 QgsDebugError( QStringLiteral( "Malformed geometry: invalid cmdCount" ) );
223 break;
224 }
225
226 if ( feature.type() == vector_tile::Tile_GeomType_POINT )
227 outputPoints.reserve( outputPoints.size() + cmdCount );
228 else
229 tmpPoints.reserve( tmpPoints.size() + cmdCount );
230
231 for ( unsigned j = 0; j < cmdCount; j++ )
232 {
233 const unsigned v = feature.geometry( i + 1 );
234 const unsigned w = feature.geometry( i + 2 );
235 const int dx = ( ( v >> 1 ) ^ ( -( v & 1 ) ) );
236 const int dy = ( ( w >> 1 ) ^ ( -( w & 1 ) ) );
237 cursorx += dx;
238 cursory += dy;
239 const double px = tileXMin + tileDX * double( cursorx ) / double( extent );
240 const double py = tileYMax - tileDY * double( cursory ) / double( extent );
241
242 if ( feature.type() == vector_tile::Tile_GeomType_POINT )
243 {
244 outputPoints.append( new QgsPoint( px, py ) );
245 }
246 else if ( feature.type() == vector_tile::Tile_GeomType_LINESTRING )
247 {
248 if ( tmpPoints.size() > 0 )
249 {
250 outputLinestrings.append( new QgsLineString( tmpPoints ) );
251 tmpPoints.clear();
252 }
253 tmpPoints.append( QgsPoint( px, py ) );
254 }
255 else if ( feature.type() == vector_tile::Tile_GeomType_POLYGON )
256 {
257 tmpPoints.append( QgsPoint( px, py ) );
258 }
259 i += 2;
260 }
261 }
262 else if ( cmdId == 2 ) // LineTo
263 {
264 if ( i + static_cast<int>( cmdCount ) * 2 >= feature.geometry_size() )
265 {
266 QgsDebugError( QStringLiteral( "Malformed geometry: invalid cmdCount" ) );
267 break;
268 }
269 tmpPoints.reserve( tmpPoints.size() + cmdCount );
270 for ( unsigned j = 0; j < cmdCount; j++ )
271 {
272 const unsigned v = feature.geometry( i + 1 );
273 const unsigned w = feature.geometry( i + 2 );
274 const int dx = ( ( v >> 1 ) ^ ( -( v & 1 ) ) );
275 const int dy = ( ( w >> 1 ) ^ ( -( w & 1 ) ) );
276 cursorx += dx;
277 cursory += dy;
278 const double px = tileXMin + tileDX * double( cursorx ) / double( extent );
279 const double py = tileYMax - tileDY * double( cursory ) / double( extent );
280
281 tmpPoints.push_back( QgsPoint( px, py ) );
282 i += 2;
283 }
284 }
285 else if ( cmdId == 7 ) // ClosePath
286 {
287 if ( feature.type() == vector_tile::Tile_GeomType_POLYGON )
288 {
289 tmpPoints.append( tmpPoints.first() ); // close the ring
290
291 std::unique_ptr<QgsLineString> ring( new QgsLineString( tmpPoints ) );
292 tmpPoints.clear();
293
294 if ( QgsVectorTileMVTUtils::isExteriorRing( ring.get() ) )
295 {
296 // start a new polygon
297 QgsPolygon *p = new QgsPolygon;
298 p->setExteriorRing( ring.release() );
299 outputPolygons.append( p );
300 }
301 else
302 {
303 // interior ring (hole)
304 if ( outputPolygons.count() != 0 )
305 {
306 outputPolygons[outputPolygons.count() - 1]->addInteriorRing( ring.release() );
307 }
308 else
309 {
310 QgsDebugError( QStringLiteral( "Malformed geometry: first ring of a polygon is interior ring" ) );
311 }
312 }
313 }
314
315 }
316 else
317 {
318 QgsDebugError( QStringLiteral( "Unexpected command ID: %1" ).arg( cmdId ) );
319 }
320 }
321
322 QString geomType;
323 if ( feature.type() == vector_tile::Tile_GeomType_POINT )
324 {
325 geomType = QStringLiteral( "Point" );
326 if ( outputPoints.count() == 1 )
327 f.setGeometry( QgsGeometry( outputPoints.at( 0 ) ) );
328 else
329 {
331 mp->reserve( outputPoints.count() );
332 for ( int k = 0; k < outputPoints.count(); ++k )
333 mp->addGeometry( outputPoints[k] );
334 f.setGeometry( QgsGeometry( mp ) );
335 }
336 }
337 else if ( feature.type() == vector_tile::Tile_GeomType_LINESTRING )
338 {
339 geomType = QStringLiteral( "LineString" );
340
341 // finish the linestring we have started
342 outputLinestrings.append( new QgsLineString( tmpPoints ) );
343
344 if ( outputLinestrings.count() == 1 )
345 f.setGeometry( QgsGeometry( outputLinestrings.at( 0 ) ) );
346 else
347 {
349 mls->reserve( outputLinestrings.size() );
350 for ( int k = 0; k < outputLinestrings.count(); ++k )
351 mls->addGeometry( outputLinestrings[k] );
352 f.setGeometry( QgsGeometry( mls ) );
353 }
354 }
355 else if ( feature.type() == vector_tile::Tile_GeomType_POLYGON )
356 {
357 geomType = QStringLiteral( "Polygon" );
358
359 if ( outputPolygons.count() == 1 )
360 f.setGeometry( QgsGeometry( outputPolygons.at( 0 ) ) );
361 else
362 {
364 mpl->reserve( outputPolygons.size() );
365 for ( int k = 0; k < outputPolygons.count(); ++k )
366 mpl->addGeometry( outputPolygons[k] );
367 f.setGeometry( QgsGeometry( mpl ) );
368 }
369 }
370
371 f.setAttribute( QStringLiteral( "_geom_type" ), geomType );
372 f.geometry().transform( ct );
373
374 layerFeatures.append( f );
375 }
376
377 features[layerName] = layerFeatures;
378 }
379 return features;
380}
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:58
Q_INVOKABLE bool setAttribute(int field, const QVariant &attr)
Sets an attribute's value by field index.
QgsGeometry geometry
Definition qgsfeature.h:69
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:53
Container of fields for a vector layer.
Definition qgsfields.h:46
bool append(const QgsField &field, Qgis::FieldOrigin origin=Qgis::FieldOrigin::Provider, int originIndex=-1)
Appends a field.
Definition qgsfields.cpp:60
Q_INVOKABLE int indexOf(const QString &fieldName) const
Gets the field index from the field name.
Q_INVOKABLE int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
void reserve(int size)
Attempts to allocate memory for at least size geometries.
A geometry is the spatial representation of a feature.
Qgis::GeometryOperationResult transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool transformZ=false)
Transforms this geometry as described by the coordinate transform ct.
Line string geometry type, with support for z-dimension and m-values.
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.
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:33
void setExteriorRing(QgsCurve *ring) override
Sets the exterior ring of the polygon.
double xMinimum() const
Returns the x minimum value (left side of rectangle).
double width() const
Returns the width of the rectangle.
double yMaximum() const
Returns the y maximum value (top side of rectangle).
double height() const
Returns the height of the rectangle.
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
#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)