QGIS API Documentation 3.41.0-Master (cea29feecf2)
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
double yMaximum
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)