QGIS API Documentation 3.28.14-Firenze (exported)
Loading...
Searching...
No Matches
qgstiles.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgstiles.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 "qgstiles.h"
17
18#include "qgslogger.h"
20#include "qgsrendercontext.h"
21
23{
24 constexpr double z0xMin = -20037508.3427892;
25 constexpr double z0yMax = 20037508.3427892;
26
27 return fromCustomDef( zoomLevel, QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3857" ) ), QgsPointXY( z0xMin, z0yMax ), 2 * z0yMax );
28}
29
31 const QgsPointXY &z0TopLeftPoint, double z0Dimension, int z0MatrixWidth, int z0MatrixHeight )
32{
33 // Square extent calculation
34 double z0xMin = z0TopLeftPoint.x();
35 double z0yMax = z0TopLeftPoint.y();
36 double z0xMax = z0xMin + z0MatrixWidth * z0Dimension;
37 double z0yMin = z0yMax - z0MatrixHeight * z0Dimension;
38
39 // Constant for scale denominator calculation
40 constexpr double TILE_SIZE = 256.0;
41 constexpr double PIXELS_TO_M = 2.8 / 10000.0; // WMS/WMTS define "standardized rendering pixel size" as 0.28mm
43 // Scale denominator calculation
44 const double scaleDenom0 = ( z0Dimension / TILE_SIZE ) * ( unitToMeters / PIXELS_TO_M );
45
46 int numTiles = static_cast<int>( pow( 2, zoomLevel ) ); // assuming we won't ever go over 30 zoom levels
47
49 tm.mCrs = crs;
50 tm.mZoomLevel = zoomLevel;
51 tm.mMatrixWidth = z0MatrixWidth * numTiles;
52 tm.mMatrixHeight = z0MatrixHeight * numTiles;
53 tm.mTileXSpan = ( z0xMax - z0xMin ) / tm.mMatrixWidth;
54 tm.mTileYSpan = ( z0yMax - z0yMin ) / tm.mMatrixHeight;
55 tm.mExtent = QgsRectangle( z0xMin, z0yMin, z0xMax, z0yMax );
56 tm.mScaleDenom = scaleDenom0 / pow( 2, zoomLevel );
57 return tm;
58}
59
60QgsTileMatrix QgsTileMatrix::fromTileMatrix( const int zoomLevel, const QgsTileMatrix &tileMatrix )
61{
63 int numTiles = static_cast<int>( pow( 2, zoomLevel ) ); // assuming we won't ever go over 30 zoom levels
64 int aZoomLevel = tileMatrix.zoomLevel();
65 int aNumTiles = static_cast<int>( pow( 2, aZoomLevel ) );
66 int aMatrixWidth = tileMatrix.matrixWidth();
67 int aMatrixHeight = tileMatrix.matrixHeight();
68 QgsRectangle aExtent = tileMatrix.extent();
69 tm.mCrs = tileMatrix.crs();
70 tm.mZoomLevel = zoomLevel;
71 tm.mMatrixWidth = aMatrixWidth * numTiles / aNumTiles;
72 tm.mMatrixHeight = aMatrixHeight * numTiles / aNumTiles;
73 tm.mTileXSpan = aExtent.width() / tm.mMatrixWidth;
74 tm.mTileYSpan = aExtent.height() / tm.mMatrixHeight;
75 tm.mExtent = aExtent;
76 tm.mScaleDenom = tileMatrix.scale() * pow( 2, aZoomLevel ) / pow( 2, zoomLevel );
77 return tm;
78}
79
81{
82 double xMin = mExtent.xMinimum() + mTileXSpan * id.column();
83 double xMax = xMin + mTileXSpan;
84 double yMax = mExtent.yMaximum() - mTileYSpan * id.row();
85 double yMin = yMax - mTileYSpan;
86 return QgsRectangle( xMin, yMin, xMax, yMax );
87}
88
90{
91 double x = mExtent.xMinimum() + mTileXSpan * id.column() + mTileXSpan / 2;
92 double y = mExtent.yMaximum() - mTileYSpan * id.row() - mTileYSpan / 2;
93 return QgsPointXY( x, y );
94}
95
97{
98 double x0 = std::clamp( r.xMinimum(), mExtent.xMinimum(), mExtent.xMaximum() );
99 double y0 = std::clamp( r.yMinimum(), mExtent.yMinimum(), mExtent.yMaximum() );
100 double x1 = std::clamp( r.xMaximum(), mExtent.xMinimum(), mExtent.xMaximum() );
101 double y1 = std::clamp( r.yMaximum(), mExtent.yMinimum(), mExtent.yMaximum() );
102 if ( x0 >= x1 || y0 >= y1 )
103 return QgsTileRange(); // nothing to display
104
105 double tileX1 = ( x0 - mExtent.xMinimum() ) / mTileXSpan;
106 double tileX2 = ( x1 - mExtent.xMinimum() ) / mTileXSpan;
107 double tileY1 = ( mExtent.yMaximum() - y1 ) / mTileYSpan;
108 double tileY2 = ( mExtent.yMaximum() - y0 ) / mTileYSpan;
109
110 QgsDebugMsgLevel( QStringLiteral( "Tile range of edges [%1,%2] - [%3,%4]" ).arg( tileX1 ).arg( tileY1 ).arg( tileX2 ).arg( tileY2 ), 2 );
111
112 // figure out tile range from zoom
113 int startColumn = std::clamp( static_cast<int>( floor( tileX1 ) ), 0, mMatrixWidth - 1 );
114 int endColumn = std::clamp( static_cast<int>( floor( tileX2 ) ), 0, mMatrixWidth - 1 );
115 int startRow = std::clamp( static_cast<int>( floor( tileY1 ) ), 0, mMatrixHeight - 1 );
116 int endRow = std::clamp( static_cast<int>( floor( tileY2 ) ), 0, mMatrixHeight - 1 );
117 return QgsTileRange( startColumn, endColumn, startRow, endRow );
118}
119
120QPointF QgsTileMatrix::mapToTileCoordinates( const QgsPointXY &mapPoint ) const
121{
122 double dx = mapPoint.x() - mExtent.xMinimum();
123 double dy = mExtent.yMaximum() - mapPoint.y();
124 return QPointF( dx / mTileXSpan, dy / mTileYSpan );
125}
126
127//
128// QgsTileMatrixSet
129//
130
132{
133 return mTileMatrices.isEmpty();
134}
135
136void QgsTileMatrixSet::addGoogleCrs84QuadTiles( int minimumZoom, int maximumZoom )
137{
138 if ( maximumZoom < minimumZoom )
139 std::swap( minimumZoom, maximumZoom );
140
141 for ( int zoom = minimumZoom; zoom <= maximumZoom; ++zoom )
142 {
144 }
145
146 mRootMatrix = QgsTileMatrix::fromWebMercator( 0 );
147}
148
150{
151 return mTileMatrices.value( zoom );
152}
153
155{
156 return mRootMatrix;
157}
158
160{
161 mRootMatrix = matrix;
162}
163
165{
166 mTileMatrices.insert( matrix.zoomLevel(), matrix );
167}
168
170{
171 int res = -1;
172 for ( auto it = mTileMatrices.constBegin(); it != mTileMatrices.constEnd(); ++it )
173 {
174 if ( res == -1 || it->zoomLevel() < res )
175 res = it->zoomLevel();
176 }
177 return res;
178}
179
181{
182 int res = -1;
183 for ( auto it = mTileMatrices.constBegin(); it != mTileMatrices.constEnd(); ++it )
184 {
185 if ( res == -1 || it->zoomLevel() > res )
186 res = it->zoomLevel();
187 }
188 return res;
189}
190
191void QgsTileMatrixSet::dropMatricesOutsideZoomRange( int minimumZoom, int maximumZoom )
192{
193 for ( auto it = mTileMatrices.begin(); it != mTileMatrices.end(); )
194 {
195 if ( it->zoomLevel() < minimumZoom || it->zoomLevel() > maximumZoom )
196 {
197 it = mTileMatrices.erase( it );
198 }
199 else
200 {
201 ++it;
202 }
203 }
204}
205
207{
208 if ( mTileMatrices.empty() )
210
211 return mTileMatrices.value( minimumZoom() ).crs();
212}
213
214double QgsTileMatrixSet::scaleToZoom( double scale ) const
215{
216 int zoomUnder = -1;
217 int zoomOver = -1;
218 double scaleUnder = 0;
219 double scaleOver = 0;
220
221 switch ( mScaleToTileZoomMethod )
222 {
224 {
225 // TODO: it seems that map scale is double (is that because of high-dpi screen?)
226 // (this TODO was taken straight from QgsVectorTileUtils::scaleToZoom!)
227 scale *= 2;
228 break;
229 }
231 break;
232 }
233
234 for ( auto it = mTileMatrices.constBegin(); it != mTileMatrices.constEnd(); ++it )
235 {
236 if ( it->scale() > scale && ( zoomUnder == -1 || zoomUnder < it->zoomLevel() ) )
237 {
238 zoomUnder = it->zoomLevel();
239 scaleUnder = it->scale();
240 }
241 if ( it->scale() < scale && ( zoomOver == -1 || zoomOver > it->zoomLevel() ) )
242 {
243 zoomOver = it->zoomLevel();
244 scaleOver = it->scale();
245 }
246 }
247
248 if ( zoomUnder < 0 )
249 return zoomOver;
250 if ( zoomOver < 0 )
251 {
252 // allow overzooming, so the styling is applied correctly
253 scaleOver = tileMatrix( maximumZoom() ).scale() / 2;
254 zoomOver = maximumZoom() + 1;
255 while ( true )
256 {
257 if ( scaleOver < scale && scale < scaleUnder )
258 {
259 return ( scaleUnder - scale ) / ( scaleUnder - scaleOver ) * ( zoomOver - zoomUnder ) + zoomUnder;
260 }
261 scaleUnder = scaleOver;
262 zoomUnder = zoomOver;
263 scaleOver = scaleOver / 2;
264 zoomOver += 1;
265 }
266 }
267 else
268 return ( scaleUnder - scale ) / ( scaleUnder - scaleOver ) * ( zoomOver - zoomUnder ) + zoomUnder;
269}
270
271int QgsTileMatrixSet::scaleToZoomLevel( double scale ) const
272{
273 int tileZoom = 0;
274 switch ( mScaleToTileZoomMethod )
275 {
277 tileZoom = static_cast<int>( round( scaleToZoom( scale ) ) );
278 break;
280 tileZoom = static_cast<int>( floor( scaleToZoom( scale ) ) );
281 break;
282 }
283
284 return std::clamp( tileZoom, minimumZoom(), maximumZoom() );
285}
286
288{
289 return calculateTileScaleForMap( context.rendererScale(),
291 context.mapExtent(),
292 context.outputSize(),
293 context.painter()->device()->logicalDpiX() );
294}
295
296double QgsTileMatrixSet::calculateTileScaleForMap( double actualMapScale, const QgsCoordinateReferenceSystem &mapCrs, const QgsRectangle &mapExtent, const QSize mapSize, const double mapDpi ) const
297{
298 switch ( mScaleToTileZoomMethod )
299 {
301 return actualMapScale;
302
304 if ( mapCrs.isGeographic() )
305 {
306 // ESRI calculates the scale for geographic CRS ***ALWAYS*** at the equator, regardless of map extent!
307 // see https://support.esri.com/en/technical-article/000007211, https://gis.stackexchange.com/questions/33270/how-does-arcmap-calculate-scalebar-inside-a-wgs84-layout
308 constexpr double METERS_PER_DEGREE = M_PI / 180.0 * 6378137;
309 constexpr double INCHES_PER_METER = 39.370078;
310 const double mapWidthInches = mapExtent.width() * METERS_PER_DEGREE * INCHES_PER_METER;
311
312 double scale = mapWidthInches * mapDpi / static_cast< double >( mapSize.width() );
313
314 // Note: I **think** there's also some magic which ESRI applies when rendering tiles ON SCREEN,
315 // which may be something like adjusting the scale based on the ratio between the map DPI and 96 DPI,
316 // e.g. scale *= mapDpi / 96.0;
317 // BUT the same adjustment isn't applied when exporting maps. This needs further investigation!
318
319 return scale;
320 }
321 else
322 {
323 return actualMapScale;
324 }
325 }
327}
328
329bool QgsTileMatrixSet::readXml( const QDomElement &element, QgsReadWriteContext & )
330{
331 mTileMatrices.clear();
332
333 mScaleToTileZoomMethod = qgsEnumKeyToValue( element.attribute( QStringLiteral( "scaleToZoomMethod" ) ), Qgis::ScaleToTileZoomLevelMethod::MapBox );
334
335 auto readMatrixFromElement = []( const QDomElement & matrixElement )->QgsTileMatrix
336 {
337 QgsTileMatrix matrix;
338 matrix.mZoomLevel = matrixElement.attribute( QStringLiteral( "zoomLevel" ) ).toInt();
339 matrix.mMatrixWidth = matrixElement.attribute( QStringLiteral( "matrixWidth" ) ).toInt();
340 matrix.mMatrixHeight = matrixElement.attribute( QStringLiteral( "matrixHeight" ) ).toInt();
341 matrix.mExtent = QgsRectangle(
342 matrixElement.attribute( QStringLiteral( "xMin" ) ).toDouble(),
343 matrixElement.attribute( QStringLiteral( "yMin" ) ).toDouble(),
344 matrixElement.attribute( QStringLiteral( "xMax" ) ).toDouble(),
345 matrixElement.attribute( QStringLiteral( "yMax" ) ).toDouble()
346 );
347
348 matrix.mScaleDenom = matrixElement.attribute( QStringLiteral( "scale" ) ).toDouble();
349 matrix.mTileXSpan = matrixElement.attribute( QStringLiteral( "tileXSpan" ) ).toDouble();
350 matrix.mTileYSpan = matrixElement.attribute( QStringLiteral( "tileYSpan" ) ).toDouble();
351 matrix.mCrs.readXml( matrixElement );
352 return matrix;
353 };
354
355 const QDomNodeList children = element.childNodes();
356 for ( int i = 0; i < children.size(); i++ )
357 {
358 const QDomElement matrixElement = children.at( i ).toElement();
359 if ( matrixElement.tagName() == QLatin1String( "rootMatrix" ) )
360 continue;
361
362 QgsTileMatrix matrix = readMatrixFromElement( matrixElement );
363 if ( matrix.zoomLevel() == 0 ) // old project compatibility
364 mRootMatrix = matrix;
365
366 addMatrix( matrix );
367 }
368
369 const QDomElement rootElement = element.firstChildElement( QStringLiteral( "rootMatrix" ) );
370 if ( !rootElement.isNull() )
371 {
372 mRootMatrix = readMatrixFromElement( rootElement );
373 }
374
375 return true;
376}
377
378QDomElement QgsTileMatrixSet::writeXml( QDomDocument &document, const QgsReadWriteContext & ) const
379{
380 QDomElement setElement = document.createElement( QStringLiteral( "matrixSet" ) );
381 setElement.setAttribute( QStringLiteral( "scaleToZoomMethod" ), qgsEnumValueToKey( mScaleToTileZoomMethod ) );
382
383 auto writeMatrixToElement = [&document]( const QgsTileMatrix & matrix, QDomElement & matrixElement )
384 {
385 matrixElement.setAttribute( QStringLiteral( "zoomLevel" ), matrix.zoomLevel() );
386 matrixElement.setAttribute( QStringLiteral( "matrixWidth" ), matrix.matrixWidth() );
387 matrixElement.setAttribute( QStringLiteral( "matrixHeight" ), matrix.matrixHeight() );
388
389 matrixElement.setAttribute( QStringLiteral( "xMin" ), qgsDoubleToString( matrix.mExtent.xMinimum() ) );
390 matrixElement.setAttribute( QStringLiteral( "xMax" ), qgsDoubleToString( matrix.mExtent.xMaximum() ) );
391 matrixElement.setAttribute( QStringLiteral( "yMin" ), qgsDoubleToString( matrix.mExtent.yMinimum() ) );
392 matrixElement.setAttribute( QStringLiteral( "yMax" ), qgsDoubleToString( matrix.mExtent.yMaximum() ) );
393
394 matrixElement.setAttribute( QStringLiteral( "scale" ), qgsDoubleToString( matrix.scale() ) );
395 matrixElement.setAttribute( QStringLiteral( "tileXSpan" ), qgsDoubleToString( matrix.mTileXSpan ) );
396 matrixElement.setAttribute( QStringLiteral( "tileYSpan" ), qgsDoubleToString( matrix.mTileYSpan ) );
397
398 matrix.crs().writeXml( matrixElement, document );
399 };
400
401 for ( auto it = mTileMatrices.constBegin(); it != mTileMatrices.constEnd(); ++it )
402 {
403 QDomElement matrixElement = document.createElement( QStringLiteral( "matrix" ) );
404 writeMatrixToElement( *it, matrixElement );
405 setElement.appendChild( matrixElement );
406 }
407
408 QDomElement rootElement = document.createElement( QStringLiteral( "rootMatrix" ) );
409 writeMatrixToElement( mRootMatrix, rootElement );
410 setElement.appendChild( rootElement );
411
412 return setElement;
413}
@ Esri
No scale doubling, always rounds down when matching to available tile levels.
@ MapBox
Uses a scale doubling approach to account for hi-DPI tiles, and rounds to the nearest tile level for ...
This class represents a coordinate reference system (CRS).
bool readXml(const QDomNode &node)
Restores state from the given DOM node.
bool writeXml(QDomNode &node, QDomDocument &doc) const
Stores state to the given Dom node in the given document.
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination coordinate reference system, which the transform will transform coordinates t...
A class to represent a 2D point.
Definition qgspointxy.h:59
double y
Definition qgspointxy.h:63
double x
Definition qgspointxy.h:62
The class is used as a container of context for various read/write operations on other objects.
A rectangle specified with double values.
double xMinimum() const
Returns the x minimum value (left side of rectangle).
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
double width() const
Returns the width of the rectangle.
double xMaximum() const
Returns the x maximum value (right side of rectangle).
double yMaximum() const
Returns the y maximum value (top side of rectangle).
double height() const
Returns the height of the rectangle.
Contains information about the context of a rendering operation.
QPainter * painter()
Returns the destination QPainter for the render operation.
double rendererScale() const
Returns the renderer map scale.
QSize outputSize() const
Returns the size of the resulting rendered image, in pixels.
QgsRectangle mapExtent() const
Returns the original extent of the map being rendered.
QgsCoordinateTransform coordinateTransform() const
Returns the current coordinate transform for the context.
virtual QDomElement writeXml(QDomDocument &document, const QgsReadWriteContext &context) const
Writes the set to an XML element.
Definition qgstiles.cpp:378
void addGoogleCrs84QuadTiles(int minimumZoom=0, int maximumZoom=14)
Adds tile matrices corresponding to the standard web mercator/GoogleCRS84Quad setup.
Definition qgstiles.cpp:136
QgsCoordinateReferenceSystem crs() const
Returns the coordinate reference system associated with the tiles.
Definition qgstiles.cpp:206
double scaleForRenderContext(const QgsRenderContext &context) const
Calculates the correct scale to use for the tiles when rendered using the specified render context.
Definition qgstiles.cpp:287
int minimumZoom() const
Returns the minimum zoom level for tiles present in the set.
Definition qgstiles.cpp:169
int scaleToZoomLevel(double scale) const
Finds the best fitting (integer) zoom level given a map scale denominator.
Definition qgstiles.cpp:271
double scaleToZoom(double scale) const
Calculates a fractional zoom level given a map scale denominator.
Definition qgstiles.cpp:214
int maximumZoom() const
Returns the maximum zoom level for tiles present in the set.
Definition qgstiles.cpp:180
QgsTileMatrix tileMatrix(int zoom) const
Returns the tile matrix corresponding to the specified zoom.
Definition qgstiles.cpp:149
QgsTileMatrix rootMatrix() const
Returns the root tile matrix (usually corresponding to zoom level 0).
Definition qgstiles.cpp:154
virtual bool readXml(const QDomElement &element, QgsReadWriteContext &context)
Reads the set from an XML element.
Definition qgstiles.cpp:329
void addMatrix(const QgsTileMatrix &matrix)
Adds a matrix to the set.
Definition qgstiles.cpp:164
double calculateTileScaleForMap(double actualMapScale, const QgsCoordinateReferenceSystem &mapCrs, const QgsRectangle &mapExtent, const QSize mapSize, const double mapDpi) const
Calculates the correct scale to use for the tiles when rendered using the specified map properties.
Definition qgstiles.cpp:296
void dropMatricesOutsideZoomRange(int minimumZoom, int maximumZoom)
Deletes any existing matrices which fall outside the zoom range specified by minimumZoom to maximumZo...
Definition qgstiles.cpp:191
bool isEmpty() const
Returns true if the matrix set is empty.
Definition qgstiles.cpp:131
void setRootMatrix(const QgsTileMatrix &matrix)
Sets the root tile matrix (usually corresponding to zoom level 0).
Definition qgstiles.cpp:159
Defines a matrix of tiles for a single zoom level: it is defined by its size (width *.
Definition qgstiles.h:108
QgsRectangle tileExtent(QgsTileXYZ id) const
Returns extent of the given tile in this matrix.
Definition qgstiles.cpp:80
QPointF mapToTileCoordinates(const QgsPointXY &mapPoint) const
Returns row/column coordinates (floating point number) from the given point in map coordinates.
Definition qgstiles.cpp:120
QgsTileRange tileRangeFromExtent(const QgsRectangle &mExtent) const
Returns tile range that fully covers the given extent.
Definition qgstiles.cpp:96
static QgsTileMatrix fromWebMercator(int zoomLevel)
Returns a tile matrix for the usual web mercator.
Definition qgstiles.cpp:22
QgsRectangle extent() const
Returns extent of the tile matrix.
Definition qgstiles.h:163
int matrixWidth() const
Returns number of columns of the tile matrix.
Definition qgstiles.h:157
QgsCoordinateReferenceSystem crs() const
Returns the crs of the tile matrix.
Definition qgstiles.h:131
double scale() const
Returns scale denominator of the tile matrix.
Definition qgstiles.h:170
QgsPointXY tileCenter(QgsTileXYZ id) const
Returns center of the given tile in this matrix.
Definition qgstiles.cpp:89
int matrixHeight() const
Returns number of rows of the tile matrix.
Definition qgstiles.h:160
static QgsTileMatrix fromTileMatrix(int zoomLevel, const QgsTileMatrix &tileMatrix)
Returns a tile matrix based on another one.
Definition qgstiles.cpp:60
int zoomLevel() const
Returns the zoom level of the tile matrix.
Definition qgstiles.h:146
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:30
Range of tiles in a tile matrix to be rendered.
Definition qgstiles.h:71
Stores coordinates of a tile in a tile matrix set.
Definition qgstiles.h:38
@ DistanceMeters
Meters.
static Q_INVOKABLE double fromUnitToUnitFactor(QgsUnitTypes::DistanceUnit fromUnit, QgsUnitTypes::DistanceUnit toUnit)
Returns the conversion factor between the specified distance units.
T qgsEnumKeyToValue(const QString &key, const T &defaultValue, bool tryValueAsKey=true, bool *returnOk=nullptr)
Returns the value corresponding to the given key of an enum.
Definition qgis.h:2700
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition qgis.h:2466
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:2681
#define BUILTIN_UNREACHABLE
Definition qgis.h:3148
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:39
const QgsCoordinateReferenceSystem & crs