Skip to content

Commit

Permalink
Fix handling of vector tiles when zoom level 0 is not available
Browse files Browse the repository at this point in the history
E.g. when a vector tile connection has a manually set zoom level
range which doesn't include level 0, we still need this available
for correct tile feature decoding

Fixes #47934
  • Loading branch information
nyalldawson committed Sep 27, 2022
1 parent 056ee1f commit a0fb44b
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 30 deletions.
14 changes: 14 additions & 0 deletions python/core/auto_generated/qgstiles.sip.in
Expand Up @@ -247,6 +247,20 @@ Adds tile matrices corresponding to the standard web mercator/GoogleCRS84Quad se
QgsTileMatrix tileMatrix( int zoom ) const;
%Docstring
Returns the tile matrix corresponding to the specified ``zoom``.
%End

QgsTileMatrix rootMatrix() const;
%Docstring
Returns the root tile matrix (usually corresponding to zoom level 0).

.. versionadded:: 3.28
%End

void setRootMatrix( const QgsTileMatrix &matrix );
%Docstring
Sets the root tile ``matrix`` (usually corresponding to zoom level 0).

.. versionadded:: 3.28
%End

void addMatrix( const QgsTileMatrix &matrix );
Expand Down
85 changes: 62 additions & 23 deletions src/core/qgstiles.cpp
Expand Up @@ -17,7 +17,6 @@

#include "qgslogger.h"
#include "qgscoordinatereferencesystem.h"
#include "qgssettings.h"
#include "qgsrendercontext.h"

QgsTileMatrix QgsTileMatrix::fromWebMercator( int zoomLevel )
Expand Down Expand Up @@ -143,13 +142,25 @@ void QgsTileMatrixSet::addGoogleCrs84QuadTiles( int minimumZoom, int maximumZoom
{
addMatrix( QgsTileMatrix::fromWebMercator( zoom ) );
}

mRootMatrix = QgsTileMatrix::fromWebMercator( 0 );
}

QgsTileMatrix QgsTileMatrixSet::tileMatrix( int zoom ) const
{
return mTileMatrices.value( zoom );
}

QgsTileMatrix QgsTileMatrixSet::rootMatrix() const
{
return mRootMatrix;
}

void QgsTileMatrixSet::setRootMatrix( const QgsTileMatrix &matrix )
{
mRootMatrix = matrix;
}

void QgsTileMatrixSet::addMatrix( const QgsTileMatrix &matrix )
{
mTileMatrices.insert( matrix.zoomLevel(), matrix );
Expand Down Expand Up @@ -306,28 +317,46 @@ bool QgsTileMatrixSet::readXml( const QDomElement &element, QgsReadWriteContext

mScaleToTileZoomMethod = qgsEnumKeyToValue( element.attribute( QStringLiteral( "scaleToZoomMethod" ) ), Qgis::ScaleToTileZoomLevelMethod::MapBox );

const QDomNodeList children = element.childNodes();
for ( int i = 0; i < children.size(); i++ )
auto readMatrixFromElement = []( const QDomElement & matrixElement )->QgsTileMatrix
{
const QDomElement matrixElement = children.at( i ).toElement();

QgsTileMatrix matrix;
matrix.mZoomLevel = matrixElement.attribute( QStringLiteral( "zoomLevel" ) ).toInt();
matrix.mMatrixWidth = matrixElement.attribute( QStringLiteral( "matrixWidth" ) ).toInt();
matrix.mMatrixHeight = matrixElement.attribute( QStringLiteral( "matrixHeight" ) ).toInt();
matrix.mExtent = QgsRectangle(
matrixElement.attribute( QStringLiteral( "xMin" ) ).toDouble(),
matrixElement.attribute( QStringLiteral( "yMin" ) ).toDouble(),
matrixElement.attribute( QStringLiteral( "xMax" ) ).toDouble(),
matrixElement.attribute( QStringLiteral( "yMax" ) ).toDouble()
);
matrixElement.attribute( QStringLiteral( "xMin" ) ).toDouble(),
matrixElement.attribute( QStringLiteral( "yMin" ) ).toDouble(),
matrixElement.attribute( QStringLiteral( "xMax" ) ).toDouble(),
matrixElement.attribute( QStringLiteral( "yMax" ) ).toDouble()
);

matrix.mScaleDenom = matrixElement.attribute( QStringLiteral( "scale" ) ).toDouble();
matrix.mTileXSpan = matrixElement.attribute( QStringLiteral( "tileXSpan" ) ).toDouble();
matrix.mTileYSpan = matrixElement.attribute( QStringLiteral( "tileYSpan" ) ).toDouble();
matrix.mCrs.readXml( matrixElement );
return matrix;
};

const QDomNodeList children = element.childNodes();
for ( int i = 0; i < children.size(); i++ )
{
const QDomElement matrixElement = children.at( i ).toElement();
if ( matrixElement.tagName() == QLatin1String( "rootMatrix" ) )
continue;

QgsTileMatrix matrix = readMatrixFromElement( matrixElement );
if ( matrix.zoomLevel() == 0 ) // old project compatibility
mRootMatrix = matrix;

addMatrix( matrix );
}

const QDomElement rootElement = element.firstChildElement( QStringLiteral( "rootMatrix" ) );
if ( !rootElement.isNull() )
{
mRootMatrix = readMatrixFromElement( rootElement );
}

return true;
}

Expand All @@ -336,24 +365,34 @@ QDomElement QgsTileMatrixSet::writeXml( QDomDocument &document, const QgsReadWri
QDomElement setElement = document.createElement( QStringLiteral( "matrixSet" ) );
setElement.setAttribute( QStringLiteral( "scaleToZoomMethod" ), qgsEnumValueToKey( mScaleToTileZoomMethod ) );

for ( auto it = mTileMatrices.constBegin(); it != mTileMatrices.constEnd(); ++it )
auto writeMatrixToElement = [&document]( const QgsTileMatrix & matrix, QDomElement & matrixElement )
{
QDomElement matrixElement = document.createElement( QStringLiteral( "matrix" ) );
matrixElement.setAttribute( QStringLiteral( "zoomLevel" ), it->zoomLevel() );
matrixElement.setAttribute( QStringLiteral( "matrixWidth" ), it->matrixWidth() );
matrixElement.setAttribute( QStringLiteral( "matrixHeight" ), it->matrixHeight() );
matrixElement.setAttribute( QStringLiteral( "zoomLevel" ), matrix.zoomLevel() );
matrixElement.setAttribute( QStringLiteral( "matrixWidth" ), matrix.matrixWidth() );
matrixElement.setAttribute( QStringLiteral( "matrixHeight" ), matrix.matrixHeight() );

matrixElement.setAttribute( QStringLiteral( "xMin" ), qgsDoubleToString( matrix.mExtent.xMinimum() ) );
matrixElement.setAttribute( QStringLiteral( "xMax" ), qgsDoubleToString( matrix.mExtent.xMaximum() ) );
matrixElement.setAttribute( QStringLiteral( "yMin" ), qgsDoubleToString( matrix.mExtent.yMinimum() ) );
matrixElement.setAttribute( QStringLiteral( "yMax" ), qgsDoubleToString( matrix.mExtent.yMaximum() ) );

matrixElement.setAttribute( QStringLiteral( "xMin" ), qgsDoubleToString( it->mExtent.xMinimum() ) );
matrixElement.setAttribute( QStringLiteral( "xMax" ), qgsDoubleToString( it->mExtent.xMaximum() ) );
matrixElement.setAttribute( QStringLiteral( "yMin" ), qgsDoubleToString( it->mExtent.yMinimum() ) );
matrixElement.setAttribute( QStringLiteral( "yMax" ), qgsDoubleToString( it->mExtent.yMaximum() ) );
matrixElement.setAttribute( QStringLiteral( "scale" ), qgsDoubleToString( matrix.scale() ) );
matrixElement.setAttribute( QStringLiteral( "tileXSpan" ), qgsDoubleToString( matrix.mTileXSpan ) );
matrixElement.setAttribute( QStringLiteral( "tileYSpan" ), qgsDoubleToString( matrix.mTileYSpan ) );

matrixElement.setAttribute( QStringLiteral( "scale" ), qgsDoubleToString( it->scale() ) );
matrixElement.setAttribute( QStringLiteral( "tileXSpan" ), qgsDoubleToString( it->mTileXSpan ) );
matrixElement.setAttribute( QStringLiteral( "tileYSpan" ), qgsDoubleToString( it->mTileYSpan ) );
matrix.crs().writeXml( matrixElement, document );
};

it->crs().writeXml( matrixElement, document );
for ( auto it = mTileMatrices.constBegin(); it != mTileMatrices.constEnd(); ++it )
{
QDomElement matrixElement = document.createElement( QStringLiteral( "matrix" ) );
writeMatrixToElement( *it, matrixElement );
setElement.appendChild( matrixElement );
}

QDomElement rootElement = document.createElement( QStringLiteral( "rootMatrix" ) );
writeMatrixToElement( mRootMatrix, rootElement );
setElement.appendChild( rootElement );

return setElement;
}
16 changes: 16 additions & 0 deletions src/core/qgstiles.h
Expand Up @@ -242,6 +242,20 @@ class CORE_EXPORT QgsTileMatrixSet
*/
QgsTileMatrix tileMatrix( int zoom ) const;

/**
* Returns the root tile matrix (usually corresponding to zoom level 0).
*
* \since QGIS 3.28
*/
QgsTileMatrix rootMatrix() const;

/**
* Sets the root tile \a matrix (usually corresponding to zoom level 0).
*
* \since QGIS 3.28
*/
void setRootMatrix( const QgsTileMatrix &matrix );

/**
* Adds a \a matrix to the set.
*
Expand Down Expand Up @@ -338,6 +352,8 @@ class CORE_EXPORT QgsTileMatrixSet

private:

// Usually corresponds to zoom level 0, even if that zoom level is NOT present in the actual tile matrices for this set
QgsTileMatrix mRootMatrix;
QMap< int, QgsTileMatrix > mTileMatrices;
Qgis::ScaleToTileZoomLevelMethod mScaleToTileZoomMethod = Qgis::ScaleToTileZoomLevelMethod::MapBox;
};
Expand Down
3 changes: 2 additions & 1 deletion src/core/vectortile/qgsvectortilematrixset.cpp
Expand Up @@ -15,7 +15,6 @@

#include "qgsvectortilematrixset.h"
#include "qgstiles.h"
#include "qgsvectortileutils.h"
#include "qgsarcgisrestutils.h"
#include "qgslogger.h"

Expand Down Expand Up @@ -79,5 +78,7 @@ bool QgsVectorTileMatrixSet::fromEsriJson( const QVariantMap &json )
tm.setScale( lodMap.value( QStringLiteral( "scale" ) ).toDouble() );
addMatrix( tm );
}

setRootMatrix( QgsTileMatrix::fromCustomDef( 0, crs, QgsPointXY( originX, originY ), z0Dimension, 1, 1 ) );
return true;
}
1 change: 0 additions & 1 deletion src/core/vectortile/qgsvectortilematrixset.h
Expand Up @@ -18,7 +18,6 @@

#include "qgis_core.h"
#include "qgis_sip.h"
#include "qgscoordinatereferencesystem.h"
#include "qgstiles.h"

class QgsTileMatrix;
Expand Down
10 changes: 5 additions & 5 deletions src/core/vectortile/qgsvectortilemvtdecoder.cpp
Expand Up @@ -90,11 +90,11 @@ QgsVectorTileFeatures QgsVectorTileMVTDecoder::layerFeatures( const QMap<QString
QgsVectorTileFeatures features;

const int numTiles = static_cast<int>( pow( 2, mTileID.zoomLevel() ) ); // assuming we won't ever go over 30 zoom levels

const double z0Width = mStructure.tileMatrix( 0 ).extent().width();
const double z0Height = mStructure.tileMatrix( 0 ).extent().height();
const double z0xMinimum = mStructure.tileMatrix( 0 ).extent().xMinimum();
const double z0yMaximum = mStructure.tileMatrix( 0 ).extent().yMaximum();
const QgsTileMatrix &rootMatrix = mStructure.rootMatrix();
const double z0Width = rootMatrix.extent().width();
const double z0Height = rootMatrix.extent().height();
const double z0xMinimum = rootMatrix.extent().xMinimum();
const double z0yMaximum = rootMatrix.extent().yMaximum();

const double tileDX = z0Width / numTiles;
const double tileDY = z0Height / numTiles;
Expand Down
30 changes: 30 additions & 0 deletions tests/src/python/test_qgstiles.py
Expand Up @@ -177,6 +177,18 @@ def testTileMatrixSetGoogle(self):
self.assertAlmostEqual(matrix_set.tileMatrix(4).scale(), 34942642, 0)
self.assertAlmostEqual(matrix_set.tileMatrix(5).scale(), 17471321, 0)

# tile matrix 0 should not be present -- we restricted the range to 1-13
self.assertAlmostEqual(matrix_set.tileMatrix(0).zoomLevel(), -1)
# but the root tile matrix should still be available for calculations
self.assertTrue(matrix_set.rootMatrix().isRootTileMatrix())
self.assertEqual(matrix_set.rootMatrix().matrixWidth(), 1)
self.assertEqual(matrix_set.rootMatrix().matrixHeight(), 1)
self.assertEqual(matrix_set.rootMatrix().crs().authid(), 'EPSG:3857')
self.assertAlmostEqual(matrix_set.rootMatrix().extent().xMinimum(), -20037508.3427892, 3)
self.assertAlmostEqual(matrix_set.rootMatrix().extent().xMaximum(), 20037508.3427892, 3)
self.assertAlmostEqual(matrix_set.rootMatrix().extent().yMinimum(), -20037508.3427892, 3)
self.assertAlmostEqual(matrix_set.rootMatrix().extent().yMaximum(), 20037508.3427892, 3)

def testTileMatrixSetRemoveTiles(self):
matrix_set = QgsTileMatrixSet()
matrix_set.addGoogleCrs84QuadTiles(1, 13)
Expand All @@ -194,6 +206,8 @@ def testReadWriteXml(self):
matrix_set.addMatrix(
QgsTileMatrix.fromCustomDef(3, QgsCoordinateReferenceSystem('EPSG:3857'), QgsPointXY(1, 2), 1000, 4, 8))

matrix_set.setRootMatrix(QgsTileMatrix.fromCustomDef(0, QgsCoordinateReferenceSystem('EPSG:3857'), QgsPointXY(1, 2), 1000, 1, 1))

doc = QDomDocument("testdoc")
res = matrix_set.writeXml(doc, QgsReadWriteContext())

Expand All @@ -206,6 +220,13 @@ def testReadWriteXml(self):
self.assertEqual(set2.tileMatrix(2).crs().authid(), 'EPSG:4326')
self.assertEqual(set2.tileMatrix(3).crs().authid(), 'EPSG:3857')

self.assertEqual(set2.rootMatrix().crs().authid(), 'EPSG:3857')
self.assertTrue(set2.rootMatrix().isRootTileMatrix())
self.assertAlmostEqual(set2.rootMatrix().extent().xMinimum(), 1, 3)
self.assertAlmostEqual(set2.rootMatrix().extent().xMaximum(), 1001, 3)
self.assertAlmostEqual(set2.rootMatrix().extent().yMinimum(), -998, 3)
self.assertAlmostEqual(set2.rootMatrix().extent().yMaximum(), 2, 3)

def testVectorTileMatrixSet(self):
matrix_set = QgsVectorTileMatrixSet()

Expand Down Expand Up @@ -384,6 +405,15 @@ def testVectorTileMatrixSetFromESRI(self):
self.assertEqual(vector_tile_set.minimumZoom(), 0)
self.assertEqual(vector_tile_set.maximumZoom(), 14)

self.assertTrue(vector_tile_set.rootMatrix().isRootTileMatrix())
self.assertEqual(vector_tile_set.rootMatrix().matrixWidth(), 1)
self.assertEqual(vector_tile_set.rootMatrix().matrixHeight(), 1)
self.assertEqual(vector_tile_set.rootMatrix().crs().authid(), 'EPSG:3978')
self.assertAlmostEqual(vector_tile_set.rootMatrix().extent().xMinimum(), -34655613.47869982, 3)
self.assertAlmostEqual(vector_tile_set.rootMatrix().extent().xMaximum(), 34655613.47869982, 3)
self.assertAlmostEqual(vector_tile_set.rootMatrix().extent().yMinimum(), -30836282.31264031, 3)
self.assertAlmostEqual(vector_tile_set.rootMatrix().extent().yMaximum(), 38474944.64475933, 3)

self.assertEqual(vector_tile_set.crs().authid(), 'EPSG:3978')
self.assertAlmostEqual(vector_tile_set.tileMatrix(0).extent().xMinimum(), -34655613.47869982, 3)
self.assertAlmostEqual(vector_tile_set.tileMatrix(0).extent().yMinimum(), -30836282.31264031, 3)
Expand Down

0 comments on commit a0fb44b

Please sign in to comment.