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