QGIS API Documentation 3.99.0-Master (21b3aa880ba)
Loading...
Searching...
No Matches
qgsvectortilematrixset.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsvectortilematrixset.cpp
3 --------------------------------------
4 Date : March 2022
5 Copyright : (C) 2022 by Nyall Dawson
6 Email : nyall dot dawson 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 "qgsarcgisrestutils.h"
19#include "qgslogger.h"
20#include "qgsmessagelog.h"
21#include "qgstiles.h"
22
29
30bool QgsVectorTileMatrixSet::fromEsriJson( const QVariantMap &json, const QVariantMap &rootTileMap )
31{
33
34 const QVariantMap tileInfo = json.value( QStringLiteral( "tileInfo" ) ).toMap();
35
36 const QVariantMap origin = tileInfo.value( QStringLiteral( "origin" ) ).toMap();
37 const double originX = origin.value( QStringLiteral( "x" ) ).toDouble();
38 const double originY = origin.value( QStringLiteral( "y" ) ).toDouble();
39
40 const int rows = tileInfo.value( QStringLiteral( "rows" ), QStringLiteral( "512" ) ).toInt();
41 const int cols = tileInfo.value( QStringLiteral( "cols" ), QStringLiteral( "512" ) ).toInt();
42 if ( rows != cols )
43 {
44 QgsDebugError( QStringLiteral( "row/col size mismatch: %1 vs %2 - tile misalignment may occur" ).arg( rows ).arg( cols ) );
45 }
46
47 const QgsCoordinateReferenceSystem crs = QgsArcGisRestUtils::convertSpatialReference( tileInfo.value( QStringLiteral( "spatialReference" ) ).toMap() );
48
49 const QVariantList lodList = tileInfo.value( QStringLiteral( "lods" ) ).toList();
50 bool foundLevel0 = false;
51 double z0Dimension = 0;
52
53 for ( const QVariant &lod : lodList )
54 {
55 const QVariantMap lodMap = lod.toMap();
56 const int level = lodMap.value( QStringLiteral( "level" ) ).toInt();
57 if ( level == 0 )
58 {
59 z0Dimension = lodMap.value( QStringLiteral( "resolution" ) ).toDouble() * rows;
60 foundLevel0 = true;
61 break;
62 }
63 }
64
65 if ( !foundLevel0 )
66 return false;
67
68 for ( const QVariant &lod : lodList )
69 {
70 const QVariantMap lodMap = lod.toMap();
71 const int level = lodMap.value( QStringLiteral( "level" ) ).toInt();
72
73 // TODO -- we shouldn't be using z0Dimension here, but rather the actual dimension and properties of
74 // this exact LOD
76 level,
77 crs,
78 QgsPointXY( originX, originY ),
79 z0Dimension );
80 tm.setScale( lodMap.value( QStringLiteral( "scale" ) ).toDouble() );
81 addMatrix( tm );
82 }
83
84 setRootMatrix( QgsTileMatrix::fromCustomDef( 0, crs, QgsPointXY( originX, originY ), z0Dimension, 1, 1 ) );
85
86 const QVariantList tileMap = rootTileMap.value( QStringLiteral( "index" ) ).toList();
87 if ( !tileMap.isEmpty() )
88 {
89 // QUESTION: why do things this way?
90 // ANSWERS:
91 // - While we could do an upfront interpretation of the tilemap once, we'd end up storing
92 // the tile availability for every single tile in the matrix. This will quickly end up
93 // with a stupidly large amount of data, even for relatively small zoom level ranges.
94 // - Even if we only store the availability for "not available" and "no child tiles" tiles,
95 // we can still end up storing a huge amount of tile information. Testing with relatively
96 // confined tileset extents still resulted in storage of 200k+ tile status due to the number
97 // of tiles which are skipped during the indexing.
98 // - It's quite cheap to just pass the tile map every time we want to find the desired
99 // tile for a given extent.
100 // - I don't want virtual methods in QgsTileMatrixSet and the complexity of handling copies
101 // of matrix sets when there's an inheritance involved
102
103 mTileReplacementFunction = [tileMap]( QgsTileXYZ id, QgsTileXYZ & replacement ) -> Qgis::TileAvailability
104 {
105 /*
106 Punch holes in matrix set according to tile map.
107 From the ESRI documentation:
108 "The tilemap resource describes a quadtree of tiles and can be used to avoid
109 requesting tiles that don't exist in the server. Each node of the tree
110 has an associated tile. The root node (lod 0) covers the entire extent of the
111 data. Children are identified by their position with NW, NE, SW, and SE. Tiles
112 are identified by lod/h/v, where h and v are indexes on a 2^lod by 2^lod grid .
113 These values are derived from the position in the tree. The tree has a variable
114 depth. A node doesn’t have children if the complexity of the data in the
115 associated tile is below a threshold. This threshold is based on a combination
116 of number of features, attributes, and vertices."
117
118 And
119
120 "Where <node> is : [<node>,<node>,<node>,<node>] in order NW,NE,SW,SE with possible values:
121 1 // tile with no children (referred to as a leaf tile)
122 0 // no tile (because there's no data here, so the tile file does not exist)
123 2 // subtree defined in a different index file (to mitigate the index being too large)"
124 */
125
126 replacement = id;
127 std::vector< QgsTileXYZ > bottomToTopQueue;
128 bottomToTopQueue.reserve( id.zoomLevel() );
129 int column = id.column();
130 int row = id.row();
131 // to handle the "tile with no children" states we need to start at the lowest zoom level
132 // and then zoom in to the target zoom level. So let's first build up a queue of the lower level
133 // tiles covering the tile at the target zoom level.
134 for ( int zoomLevel = id.zoomLevel(); zoomLevel > 0; zoomLevel-- )
135 {
136 bottomToTopQueue.emplace_back( QgsTileXYZ( column, row, zoomLevel ) );
137 column /= 2;
138 row /= 2;
139 }
140
141 // now we'll zoom back in, checking the tilemap information for each tile as we go
142 // in order to catch "tile with no children" states
143 QVariantList node = tileMap;
144 for ( int index = static_cast<int>( bottomToTopQueue.size() ) - 1; index >= 0; --index )
145 {
146 const QgsTileXYZ &tile = bottomToTopQueue[ index ];
147 int childColumn = tile.column() - column;
148 int childRow = tile.row() - row;
149 int childIndex = 0;
150 if ( childColumn == 1 && childRow == 0 )
151 childIndex = 1;
152 else if ( childColumn == 0 && childRow == 1 )
153 childIndex = 2;
154 else if ( childColumn == 1 && childRow == 1 )
155 childIndex = 3;
156
157 const QVariant childNode = node.at( childIndex );
158 if ( childNode.userType() == QMetaType::Type::QVariantList )
159 {
160 node = childNode.toList();
161 column = tile.column() * 2;
162 row = tile.row() * 2;
163 continue;
164 }
165 else
166 {
167 bool ok = false;
168 const long long nodeInt = childNode.toLongLong( &ok );
169
170 if ( !ok )
171 {
172 QgsDebugError( QStringLiteral( "Found tile index node with unsupported value: %1" ).arg( childNode.toString() ) );
173 }
174 else if ( nodeInt == 0 )
175 {
176 // "no tile (because there's no data here, so the tile file does not exist)"
178 }
179 else if ( nodeInt == 1 )
180 {
181 // "tile with no children (referred to as a leaf tile)"
182 replacement = tile;
184 }
185 else if ( nodeInt == 2 )
186 {
187 // "subtree defined in a different index file (to mitigate the index being too large)"
188 // I've never seen this in the wild, and don't know how it's actually structured. There's no documentation
189 // which describes where this "different index file" will be! I suspect it's something which was added to the
190 // specification as a "just in case we need in future" thing which isn't actually in use right now.
191 QgsMessageLog::logMessage( QObject::tr( "Found tile index node with subtree defined in a different index file -- this is not yet supported!" ), QObject::tr( "Vector Tiles" ), Qgis::MessageLevel::Critical );
192 // assume available
194 }
195 }
196 }
198 };
199
200 // we explicitly need to capture a copy of the member variable here
201 const QMap< int, QgsTileMatrix > tileMatrices = mTileMatrices;
202 mTileAvailabilityFunction = [this, tileMatrices]( QgsTileXYZ id ) -> Qgis::TileAvailability
203 {
204 // find zoom level matrix
205 const auto it = tileMatrices.constFind( id.zoomLevel() );
206 if ( it == tileMatrices.constEnd() )
208
209 // check if column/row is within matrix
210 if ( id.column() >= it->matrixWidth() || id.row() >= it->matrixHeight() )
212
213 QgsTileXYZ replacement;
214 return mTileReplacementFunction( id, replacement );
215 };
216 }
217 return true;
218}
@ Critical
Critical/error message.
Definition qgis.h:159
@ Esri
No scale doubling, always rounds down when matching to available tile levels.
Definition qgis.h:3422
TileAvailability
Possible availability states for a tile within a tile matrix.
Definition qgis.h:5607
@ UseLowerZoomLevelTile
Tile is not available at the requested zoom level, it should be replaced by a tile from a lower zoom ...
Definition qgis.h:5611
@ NotAvailable
Tile is not available within the matrix, e.g. there is no content for the tile.
Definition qgis.h:5609
@ AvailableNoChildren
Tile is available within the matrix, and is known to have no children (ie no higher zoom level tiles ...
Definition qgis.h:5610
@ Available
Tile is available within the matrix.
Definition qgis.h:5608
static QgsCoordinateReferenceSystem convertSpatialReference(const QVariantMap &spatialReferenceMap)
Converts a spatial reference JSON definition to a QgsCoordinateReferenceSystem value.
Represents a coordinate reference system (CRS).
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE())
Adds a message to the log instance (and creates it if necessary).
Represents a 2D point.
Definition qgspointxy.h:60
void addGoogleCrs84QuadTiles(int minimumZoom=0, int maximumZoom=14)
Adds tile matrices corresponding to the standard web mercator/GoogleCRS84Quad setup.
Definition qgstiles.cpp:143
QgsCoordinateReferenceSystem crs() const
Returns the coordinate reference system associated with the tiles.
Definition qgstiles.cpp:218
std::function< Qgis::TileAvailability(QgsTileXYZ id) > mTileAvailabilityFunction
Definition qgstiles.h:428
int minimumZoom() const
Returns the minimum zoom level for tiles present in the set.
Definition qgstiles.cpp:176
QMap< int, QgsTileMatrix > mTileMatrices
Definition qgstiles.h:433
int maximumZoom() const
Returns the maximum zoom level for tiles present in the set.
Definition qgstiles.cpp:187
void addMatrix(const QgsTileMatrix &matrix)
Adds a matrix to the set.
Definition qgstiles.cpp:171
void setRootMatrix(const QgsTileMatrix &matrix)
Sets the root tile matrix (usually corresponding to zoom level 0).
Definition qgstiles.cpp:166
void setScaleToTileZoomMethod(Qgis::ScaleToTileZoomLevelMethod method)
Sets the scale to tile zoom method.
Definition qgstiles.h:418
std::function< Qgis::TileAvailability(QgsTileXYZ id, QgsTileXYZ &replacement) > mTileReplacementFunction
Definition qgstiles.h:429
Defines a matrix of tiles for a single zoom level: it is defined by its size (width *.
Definition qgstiles.h:158
void setScale(double scale)
Sets the scale denominator of the tile matrix.
Definition qgstiles.h:228
static QgsTileMatrix fromCustomDef(int zoomLevel, const QgsCoordinateReferenceSystem &crs, const QgsPointXY &z0TopLeftPoint, double z0Dimension, int z0MatrixWidth=1, int z0MatrixHeight=1)
Returns a tile matrix for a specific CRS, top left point, zoom level 0 dimension in CRS units.
Definition qgstiles.cpp:31
Stores coordinates of a tile in a tile matrix set.
Definition qgstiles.h:39
int zoomLevel() const
Returns tile's zoom level (Z).
Definition qgstiles.h:52
int column() const
Returns tile's column index (X).
Definition qgstiles.h:48
int row() const
Returns tile's row index (Y).
Definition qgstiles.h:50
Encapsulates properties of a vector tile matrix set, including tile origins and scaling information.
bool fromEsriJson(const QVariantMap &json, const QVariantMap &rootTileMap=QVariantMap())
Initializes the tile structure settings from an ESRI REST VectorTileService json map.
static QgsVectorTileMatrixSet fromWebMercator(int minimumZoom=0, int maximumZoom=14)
Returns a vector tile structure corresponding to the standard web mercator/GoogleCRS84Quad setup.
#define QgsDebugError(str)
Definition qgslogger.h:57