QGIS API Documentation 3.41.0-Master (fda2aa46e9a)
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#include <QNetworkRequest>
33
34
36 : mStructure( structure )
37{}
38
40
42{
43 mLayerNameToIndex.clear();
44
45 QMap<QString, QByteArray>::const_iterator it = rawTileData.data.constBegin();
46 for ( ; it != rawTileData.data.constEnd(); ++it )
47 {
48 QString sourceId = it.key();
49
50 vector_tile::Tile tile;
51 if ( !tile.ParseFromArray( it.value().constData(), it.value().count() ) )
52 return false;
53
54 for ( int layerNum = 0; layerNum < tile.layers_size(); layerNum++ )
55 {
56 const ::vector_tile::Tile_Layer &layer = tile.layers( layerNum );
57 const QString layerName = layer.name().c_str();
58 mLayerNameToIndex[sourceId][layerName] = layerNum;
59 }
60
61 tiles[sourceId] = tile;
62
63 }
64
65 mTileID = rawTileData.tileGeometryId;
66
67 return true;
68}
69
71{
72 QStringList layerNames;
73 const int layerSize = std::accumulate( tiles.constBegin(), tiles.constEnd(), 0, []( int count, const vector_tile::Tile & tile ) {return count + tile.layers_size();} );
74 layerNames.reserve( layerSize );
75
76 QMap<QString, vector_tile::Tile>::const_iterator it = tiles.constBegin();
77 for ( ; it != tiles.constEnd(); ++it )
78 {
79 for ( int layerNum = 0; layerNum < layerSize; layerNum++ )
80 {
81 const ::vector_tile::Tile_Layer &layer = it.value().layers( layerNum );
82 const QString layerName = layer.name().c_str();
83 layerNames << layerName;
84 }
85 }
86 return layerNames;
87}
88
89QStringList QgsVectorTileMVTDecoder::layerFieldNames( const QString &layerName ) const
90{
91 QMap<QString, vector_tile::Tile>::const_iterator it = tiles.constBegin();
92 for ( ; it != tiles.constEnd(); ++it )
93 {
94 if ( !mLayerNameToIndex[it.key()].contains( layerName ) )
95 continue;
96
97 const ::vector_tile::Tile_Layer &layer = it.value().layers( mLayerNameToIndex[it.key()][layerName] );
98 QStringList fieldNames;
99 const int size = layer.keys_size();
100 fieldNames.reserve( size );
101 for ( int i = 0; i < size; ++i )
102 {
103 const QString fieldName = layer.keys( i ).c_str();
104 fieldNames << fieldName;
105 }
106 return fieldNames;
107 }
108 return QStringList();
109}
110
111QgsVectorTileFeatures QgsVectorTileMVTDecoder::layerFeatures( const QMap<QString, QgsFields> &perLayerFields, const QgsCoordinateTransform &ct, const QSet<QString> *layerSubset ) const
112{
113 QgsVectorTileFeatures features;
114
115 const int numTiles = static_cast<int>( pow( 2, mTileID.zoomLevel() ) ); // assuming we won't ever go over 30 zoom levels
116 const QgsTileMatrix &rootMatrix = mStructure.rootMatrix();
117 const double z0Width = rootMatrix.extent().width();
118 const double z0Height = rootMatrix.extent().height();
119 const double z0xMinimum = rootMatrix.extent().xMinimum();
120 const double z0yMaximum = rootMatrix.extent().yMaximum();
121
122 const double tileDX = z0Width / numTiles;
123 const double tileDY = z0Height / numTiles;
124 const double tileXMin = z0xMinimum + mTileID.column() * tileDX;
125 const double tileYMax = z0yMaximum - mTileID.row() * tileDY;
126
127 QMap<QString, vector_tile::Tile>::const_iterator it = tiles.constBegin();
128 for ( ; it != tiles.constEnd(); ++it )
129 {
130 vector_tile::Tile tile = it.value();
131
132 for ( int layerNum = 0; layerNum < tile.layers_size(); layerNum++ )
133 {
134 const ::vector_tile::Tile_Layer &layer = tile.layers( layerNum );
135
136 const QString layerName = layer.name().c_str();
137 if ( layerSubset && !layerSubset->contains( QString() ) && !layerSubset->contains( layerName ) )
138 continue;
139
140 QVector<QgsFeature> layerFeatures;
141 QgsFields layerFields = perLayerFields[layerName];
142
143 const auto allLayerFields = perLayerFields.find( QString() );
144 if ( allLayerFields != perLayerFields.end() )
145 {
146 // need to add the fields from any "all layer" rules to every layer
147 for ( const QgsField &field : allLayerFields.value() )
148 {
149 if ( layerFields.lookupField( field.name() ) == -1 )
150 {
151 layerFields.append( field );
152 }
153 }
154 }
155
156 // figure out how field indexes in MVT encoding map to field indexes in QgsFields (we may not use all available fields)
157 QHash<int, int> tagKeyIndexToFieldIndex;
158 for ( int i = 0; i < layer.keys_size(); ++i )
159 {
160 const int fieldIndex = layerFields.indexOf( layer.keys( i ).c_str() );
161 if ( fieldIndex != -1 )
162 tagKeyIndexToFieldIndex.insert( i, fieldIndex );
163 }
164
165 // go through features of a layer
166 for ( int featureNum = 0; featureNum < layer.features_size(); featureNum++ )
167 {
168 const ::vector_tile::Tile_Feature &feature = layer.features( featureNum );
169
170 QgsFeatureId fid;
171 {
172 // There is no assigned ID, but some parts of QGIS do not work correctly if all IDs are zero
173 // (e.g. labeling will not register two features with the same FID within a single layer),
174 // so let's generate some pseudo-unique FIDs to keep those bits happy
175 fid = featureNum;
176 fid |= ( layerNum & 0xff ) << 24;
177 fid |= ( static_cast<QgsFeatureId>( mTileID.row() ) & 0xff ) << 32;
178 fid |= ( static_cast<QgsFeatureId>( mTileID.column() ) & 0xff ) << 40;
179 }
180
181 QgsFeature f( layerFields, fid );
182
183 //
184 // parse attributes
185 //
186
187 for ( int tagNum = 0; tagNum + 1 < feature.tags_size(); tagNum += 2 )
188 {
189 const int keyIndex = static_cast<int>( feature.tags( tagNum ) );
190 const int fieldIndex = tagKeyIndexToFieldIndex.value( keyIndex, -1 );
191 if ( fieldIndex == -1 )
192 continue;
193
194 const int valueIndex = static_cast<int>( feature.tags( tagNum + 1 ) );
195 if ( valueIndex >= layer.values_size() )
196 {
197 QgsDebugError( QStringLiteral( "Invalid value index for attribute" ) );
198 continue;
199 }
200 const ::vector_tile::Tile_Value &value = layer.values( valueIndex );
201
202 if ( value.has_string_value() )
203 f.setAttribute( fieldIndex, QString::fromStdString( value.string_value() ) );
204 else if ( value.has_float_value() )
205 f.setAttribute( fieldIndex, static_cast<double>( value.float_value() ) );
206 else if ( value.has_double_value() )
207 f.setAttribute( fieldIndex, value.double_value() );
208 else if ( value.has_int_value() )
209 f.setAttribute( fieldIndex, static_cast<int>( value.int_value() ) );
210 else if ( value.has_uint_value() )
211 f.setAttribute( fieldIndex, static_cast<int>( value.uint_value() ) );
212 else if ( value.has_sint_value() )
213 f.setAttribute( fieldIndex, static_cast<int>( value.sint_value() ) );
214 else if ( value.has_bool_value() )
215 f.setAttribute( fieldIndex, static_cast<bool>( value.bool_value() ) );
216 else
217 {
218 QgsDebugError( QStringLiteral( "Unexpected attribute value" ) );
219 }
220 }
221
222 //
223 // parse geometry
224 //
225
226 const int extent = static_cast<int>( layer.extent() );
227 int cursorx = 0, cursory = 0;
228
229 QVector<QgsPoint *> outputPoints; // for point/multi-point
230 QVector<QgsLineString *> outputLinestrings; // for linestring/multi-linestring
231 QVector<QgsPolygon *> outputPolygons;
232 QVector<QgsPoint> tmpPoints;
233
234 for ( int i = 0; i < feature.geometry_size(); i ++ )
235 {
236 const unsigned g = feature.geometry( i );
237 const unsigned cmdId = g & 0x7;
238 const int cmdCount = static_cast<int>( g >> 3 );
239 if ( cmdId == 1 ) // MoveTo
240 {
241 if ( i + static_cast<int>( cmdCount ) * 2 >= feature.geometry_size() )
242 {
243 QgsDebugError( QStringLiteral( "Malformed geometry: invalid cmdCount" ) );
244 break;
245 }
246
247 if ( feature.type() == vector_tile::Tile_GeomType_POINT )
248 outputPoints.reserve( static_cast<int>( outputPoints.size() ) + cmdCount );
249 else
250 tmpPoints.reserve( static_cast<int>( tmpPoints.size() ) + cmdCount );
251
252 for ( int j = 0; j < cmdCount; j++ )
253 {
254 const int v = static_cast<int>( feature.geometry( i + 1 ) );
255 const int w = static_cast<int>( feature.geometry( i + 2 ) );
256 const int dx = ( ( v >> 1 ) ^ ( -( v & 1 ) ) );
257 const int dy = ( ( w >> 1 ) ^ ( -( w & 1 ) ) );
258 cursorx += dx;
259 cursory += dy;
260 const double px = tileXMin + tileDX * double( cursorx ) / double( extent );
261 const double py = tileYMax - tileDY * double( cursory ) / double( extent );
262
263 if ( feature.type() == vector_tile::Tile_GeomType_POINT )
264 {
265 outputPoints.append( new QgsPoint( px, py ) );
266 }
267 else if ( feature.type() == vector_tile::Tile_GeomType_LINESTRING )
268 {
269 if ( tmpPoints.size() > 0 )
270 {
271 outputLinestrings.append( new QgsLineString( tmpPoints ) );
272 tmpPoints.clear();
273 }
274 tmpPoints.append( QgsPoint( px, py ) );
275 }
276 else if ( feature.type() == vector_tile::Tile_GeomType_POLYGON )
277 {
278 tmpPoints.append( QgsPoint( px, py ) );
279 }
280 i += 2;
281 }
282 }
283 else if ( cmdId == 2 ) // LineTo
284 {
285 if ( i + static_cast<int>( cmdCount ) * 2 >= feature.geometry_size() )
286 {
287 QgsDebugError( QStringLiteral( "Malformed geometry: invalid cmdCount" ) );
288 break;
289 }
290 tmpPoints.reserve( tmpPoints.size() + cmdCount );
291 for ( int j = 0; j < cmdCount; j++ )
292 {
293 const int v = static_cast<int>( feature.geometry( i + 1 ) );
294 const int w = static_cast<int>( feature.geometry( i + 2 ) );
295 const int dx = ( v >> 1 ) ^ ( -( v & 1 ) );
296 const int dy = ( w >> 1 ) ^ ( -( w & 1 ) );
297 cursorx += dx;
298 cursory += dy;
299 const double px = tileXMin + tileDX * double( cursorx ) / double( extent );
300 const double py = tileYMax - tileDY * double( cursory ) / double( extent );
301
302 tmpPoints.push_back( QgsPoint( px, py ) );
303 i += 2;
304 }
305 }
306 else if ( cmdId == 7 ) // ClosePath
307 {
308 if ( feature.type() == vector_tile::Tile_GeomType_POLYGON )
309 {
310 tmpPoints.append( tmpPoints.first() ); // close the ring
311
312 std::unique_ptr<QgsLineString> ring( new QgsLineString( tmpPoints ) );
313 tmpPoints.clear();
314
315 if ( QgsVectorTileMVTUtils::isExteriorRing( ring.get() ) )
316 {
317 // start a new polygon
318 QgsPolygon *p = new QgsPolygon;
319 p->setExteriorRing( ring.release() );
320 outputPolygons.append( p );
321 }
322 else
323 {
324 // interior ring (hole)
325 if ( outputPolygons.count() != 0 )
326 {
327 outputPolygons[outputPolygons.count() - 1]->addInteriorRing( ring.release() );
328 }
329 else
330 {
331 QgsDebugError( QStringLiteral( "Malformed geometry: first ring of a polygon is interior ring" ) );
332 }
333 }
334 }
335
336 }
337 else
338 {
339 QgsDebugError( QStringLiteral( "Unexpected command ID: %1" ).arg( cmdId ) );
340 }
341 }
342
343 QString geomType;
344 if ( feature.type() == vector_tile::Tile_GeomType_POINT )
345 {
346 geomType = QStringLiteral( "Point" );
347 if ( outputPoints.count() == 1 )
348 f.setGeometry( QgsGeometry( outputPoints.at( 0 ) ) );
349 else
350 {
352 mp->reserve( outputPoints.count() );
353 for ( int k = 0; k < outputPoints.count(); ++k )
354 mp->addGeometry( outputPoints[k] );
355 f.setGeometry( QgsGeometry( mp ) );
356 }
357 }
358 else if ( feature.type() == vector_tile::Tile_GeomType_LINESTRING )
359 {
360 geomType = QStringLiteral( "LineString" );
361
362 // finish the linestring we have started
363 outputLinestrings.append( new QgsLineString( tmpPoints ) );
364
365 if ( outputLinestrings.count() == 1 )
366 f.setGeometry( QgsGeometry( outputLinestrings.at( 0 ) ) );
367 else
368 {
370 mls->reserve( outputLinestrings.size() );
371 for ( int k = 0; k < outputLinestrings.count(); ++k )
372 mls->addGeometry( outputLinestrings[k] );
373 f.setGeometry( QgsGeometry( mls ) );
374 }
375 }
376 else if ( feature.type() == vector_tile::Tile_GeomType_POLYGON )
377 {
378 geomType = QStringLiteral( "Polygon" );
379
380 if ( outputPolygons.count() == 1 )
381 f.setGeometry( QgsGeometry( outputPolygons.at( 0 ) ) );
382 else
383 {
385 mpl->reserve( outputPolygons.size() );
386 for ( int k = 0; k < outputPolygons.count(); ++k )
387 mpl->addGeometry( outputPolygons[k] );
388 f.setGeometry( QgsGeometry( mpl ) );
389 }
390 }
391
392 f.setAttribute( QStringLiteral( "_geom_type" ), geomType );
393 f.geometry().transform( ct );
394
395 layerFeatures.append( f );
396 }
397
398 features[layerName] = layerFeatures;
399 }
400 }
401 return features;
402}
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:70
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:136
QgsRectangle extent() const
Returns extent of the tile matrix.
Definition qgstiles.h:191
int zoomLevel() const
Returns tile's zoom level (Z)
Definition qgstiles.h:53
int column() const
Returns tile's column index (X)
Definition qgstiles.h:49
int row() const
Returns tile's row index (Y)
Definition qgstiles.h:51
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 from one or more sources that need to be decoded.
QMap< QString, QByteArray > data
Raw tile data by source ID.
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)