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