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