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