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