diff --git a/python/core/auto_generated/qgstiles.sip.in b/python/core/auto_generated/qgstiles.sip.in index fae369b89e5..82cf0d6410c 100644 --- a/python/core/auto_generated/qgstiles.sip.in +++ b/python/core/auto_generated/qgstiles.sip.in @@ -302,6 +302,24 @@ Reads the set from an XML ``element``. virtual QDomElement writeXml( QDomDocument &document, const QgsReadWriteContext &context ) const; %Docstring Writes the set to an XML element. +%End + + bool applyTileScaleDoublingHack() const; +%Docstring +Returns ``True`` if the scale doubling hack used to match MapBox scale to tile zoom levels should be applied. + +The default is that this hack will be applied. + +.. seealso:: :py:func:`setApplyTileScaleDoublingHack` +%End + + void setApplyTileScaleDoublingHack( bool apply ); +%Docstring +Sets whether the scale doubling hack used to match MapBox scale to tile zoom levels should be applied. + +The default is that this hack will be applied. + +.. seealso:: :py:func:`applyTileScaleDoublingHack` %End }; diff --git a/python/core/auto_generated/vectortile/qgsvectortilematrixset.sip.in b/python/core/auto_generated/vectortile/qgsvectortilematrixset.sip.in index 457cce34e9f..2aa39b9ec73 100644 --- a/python/core/auto_generated/vectortile/qgsvectortilematrixset.sip.in +++ b/python/core/auto_generated/vectortile/qgsvectortilematrixset.sip.in @@ -35,6 +35,7 @@ Initializes the tile structure settings from an ESRI REST VectorTileService ``js This same structure is utilized in ESRI vtpk archives in the root.json file. %End + }; /************************************************************************ diff --git a/src/core/qgstiles.cpp b/src/core/qgstiles.cpp index 16222dc7d58..0c52fc0982e 100644 --- a/src/core/qgstiles.cpp +++ b/src/core/qgstiles.cpp @@ -201,9 +201,12 @@ double QgsTileMatrixSet::scaleToZoom( double scale ) const double scaleUnder = 0; double scaleOver = 0; - // TODO: it seems that map scale is double (is that because of high-dpi screen?) - // (this TODO was taken straight from QgsVectorTileUtils::scaleToZoom!) - scale *= 2; + if ( mApplyTileScaleDoubleHack ) + { + // TODO: it seems that map scale is double (is that because of high-dpi screen?) + // (this TODO was taken straight from QgsVectorTileUtils::scaleToZoom!) + scale *= 2; + } for ( auto it = mTileMatrices.constBegin(); it != mTileMatrices.constEnd(); ++it ) { @@ -238,6 +241,8 @@ bool QgsTileMatrixSet::readXml( const QDomElement &element, QgsReadWriteContext { mTileMatrices.clear(); + mApplyTileScaleDoubleHack = element.attribute( QStringLiteral( "applyScaleDoubleHack" ), QStringLiteral( "1" ) ).toInt(); + const QDomNodeList children = element.childNodes(); for ( int i = 0; i < children.size(); i++ ) { @@ -266,6 +271,7 @@ bool QgsTileMatrixSet::readXml( const QDomElement &element, QgsReadWriteContext QDomElement QgsTileMatrixSet::writeXml( QDomDocument &document, const QgsReadWriteContext & ) const { QDomElement setElement = document.createElement( QStringLiteral( "matrixSet" ) ); + setElement.setAttribute( QStringLiteral( "applyScaleDoubleHack" ), mApplyTileScaleDoubleHack ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ); for ( auto it = mTileMatrices.constBegin(); it != mTileMatrices.constEnd(); ++it ) { diff --git a/src/core/qgstiles.h b/src/core/qgstiles.h index 49377f1bd64..c3b97a03779 100644 --- a/src/core/qgstiles.h +++ b/src/core/qgstiles.h @@ -294,10 +294,28 @@ class CORE_EXPORT QgsTileMatrixSet */ virtual QDomElement writeXml( QDomDocument &document, const QgsReadWriteContext &context ) const; + /** + * Returns TRUE if the scale doubling hack used to match MapBox scale to tile zoom levels should be applied. + * + * The default is that this hack will be applied. + * + * \see setApplyTileScaleDoublingHack() + */ + bool applyTileScaleDoublingHack() const { return mApplyTileScaleDoubleHack; } + + /** + * Sets whether the scale doubling hack used to match MapBox scale to tile zoom levels should be applied. + * + * The default is that this hack will be applied. + * + * \see applyTileScaleDoublingHack() + */ + void setApplyTileScaleDoublingHack( bool apply ) { mApplyTileScaleDoubleHack = apply; } + private: QMap< int, QgsTileMatrix > mTileMatrices; - + bool mApplyTileScaleDoubleHack = true; }; #endif // QGSTILES_H diff --git a/src/core/vectortile/qgsvectortilematrixset.cpp b/src/core/vectortile/qgsvectortilematrixset.cpp index d38ed779614..b620117fba2 100644 --- a/src/core/vectortile/qgsvectortilematrixset.cpp +++ b/src/core/vectortile/qgsvectortilematrixset.cpp @@ -28,6 +28,9 @@ QgsVectorTileMatrixSet QgsVectorTileMatrixSet::fromWebMercator() bool QgsVectorTileMatrixSet::fromEsriJson( const QVariantMap &json ) { + // doesn't apply to ESRI tile zoom levels + setApplyTileScaleDoublingHack( false ); + const QVariantMap tileInfo = json.value( QStringLiteral( "tileInfo" ) ).toMap(); const QVariantMap origin = tileInfo.value( QStringLiteral( "origin" ) ).toMap(); diff --git a/src/core/vectortile/qgsvectortilematrixset.h b/src/core/vectortile/qgsvectortilematrixset.h index dadf4fb3d33..f7f008ac9c3 100644 --- a/src/core/vectortile/qgsvectortilematrixset.h +++ b/src/core/vectortile/qgsvectortilematrixset.h @@ -43,6 +43,7 @@ class CORE_EXPORT QgsVectorTileMatrixSet : public QgsTileMatrixSet * \note This same structure is utilized in ESRI vtpk archives in the root.json file. */ bool fromEsriJson( const QVariantMap &json ); + }; #endif // QGSVECTORTILEMATRIXSET_H diff --git a/tests/src/python/test_qgstiles.py b/tests/src/python/test_qgstiles.py index a0854868897..b66bd082627 100644 --- a/tests/src/python/test_qgstiles.py +++ b/tests/src/python/test_qgstiles.py @@ -63,6 +63,10 @@ class TestQgsTiles(unittest.TestCase): def testQgsTileMatrixSet(self): matrix_set = QgsTileMatrixSet() + + # should be applied by default in order to match MapBox rendering of tiles + self.assertTrue(matrix_set.applyTileScaleDoublingHack()) + self.assertEqual(matrix_set.minimumZoom(), -1) self.assertEqual(matrix_set.maximumZoom(), -1) self.assertFalse(matrix_set.crs().isValid()) @@ -132,6 +136,25 @@ class TestQgsTiles(unittest.TestCase): self.assertEqual(matrix_set.scaleToZoomLevel(198251572), 2) self.assertEqual(matrix_set.scaleToZoomLevel(6503144), 3) + # disable scale doubling hack + matrix_set.setApplyTileScaleDoublingHack(False) + self.assertFalse(matrix_set.applyTileScaleDoublingHack()) + + self.assertAlmostEqual(matrix_set.scaleToZoom(776503144), 1, 5) + self.assertEqual(matrix_set.scaleToZoom(1776503144), 1) + self.assertAlmostEqual(matrix_set.scaleToZoom(388251572), 2, 5) + self.assertAlmostEqual(matrix_set.scaleToZoom(288251572), 2.515, 2) + self.assertAlmostEqual(matrix_set.scaleToZoom(194125786), 3, 5) + self.assertAlmostEqual(matrix_set.scaleToZoom(188251572), 3.0, 3) + self.assertEqual(matrix_set.scaleToZoom(6503144), 3) + self.assertEqual(matrix_set.scaleToZoomLevel(776503144), 1) + self.assertEqual(matrix_set.scaleToZoomLevel(1776503144), 1) + self.assertEqual(matrix_set.scaleToZoomLevel(76503144), 3) + self.assertEqual(matrix_set.scaleToZoomLevel(388251572), 2) + self.assertEqual(matrix_set.scaleToZoomLevel(298251572), 2) + self.assertEqual(matrix_set.scaleToZoomLevel(198251572), 3) + self.assertEqual(matrix_set.scaleToZoomLevel(6503144), 3) + def testTileMatrixSetGoogle(self): matrix_set = QgsTileMatrixSet() matrix_set.addGoogleCrs84QuadTiles(1, 13) @@ -176,15 +199,6 @@ class TestQgsTiles(unittest.TestCase): def testVectorTileMatrixSet(self): matrix_set = QgsVectorTileMatrixSet() - matrix_set.setZ0xMinimum(-1000) - matrix_set.setZ0xMaximum(1300) - matrix_set.setZ0yMinimum(3000) - matrix_set.setZ0yMaximum(4300) - - self.assertEqual(matrix_set.z0xMinimum(), -1000) - self.assertEqual(matrix_set.z0xMaximum(), 1300) - self.assertEqual(matrix_set.z0yMinimum(), 3000) - self.assertEqual(matrix_set.z0yMaximum(), 4300) matrix_set.addMatrix( QgsTileMatrix.fromCustomDef(1, QgsCoordinateReferenceSystem('EPSG:4326'), QgsPointXY(1, 2), 1000, 4, 8)) @@ -201,11 +215,6 @@ class TestQgsTiles(unittest.TestCase): self.assertEqual(set2.minimumZoom(), 1) self.assertEqual(set2.maximumZoom(), 3) - self.assertEqual(set2.z0xMinimum(), -1000) - self.assertEqual(set2.z0xMaximum(), 1300) - self.assertEqual(set2.z0yMinimum(), 3000) - self.assertEqual(set2.z0yMaximum(), 4300) - self.assertEqual(set2.tileMatrix(1).crs().authid(), 'EPSG:4326') self.assertEqual(set2.tileMatrix(2).crs().authid(), 'EPSG:4326') self.assertEqual(set2.tileMatrix(3).crs().authid(), 'EPSG:3857') @@ -347,6 +356,10 @@ class TestQgsTiles(unittest.TestCase): self.assertFalse(vector_tile_set.fromEsriJson({})) self.assertTrue(vector_tile_set.fromEsriJson(esri_metadata)) + # we should NOT apply the tile scale doubling hack to ESRI tiles, otherwise our scales + # are double what ESRI use for the same tile sets + self.assertFalse(vector_tile_set.applyTileScaleDoublingHack()) + self.assertEqual(vector_tile_set.minimumZoom(), 0) self.assertEqual(vector_tile_set.maximumZoom(), 14)