Ensure that non-EPSG:3857 vector tiles layers can be rendered in the

correct place

This fixes the rendering of vector tiles layers which are constructed
in any non web mercator CRS.

(Unfortunately the required information is not recorded in mbtiles
packages, so it's necessary to set the correct parameters via
API calls when trying to load non-3857 mbtiles.)
This commit is contained in:
Nyall Dawson 2022-03-02 15:20:43 +10:00
parent 1879f51781
commit b39761f8f0
18 changed files with 619 additions and 76 deletions

View File

@ -117,7 +117,9 @@ Returns a tile matrix for the usual web mercator
const QgsPointXY &z0TopLeftPoint, double z0Dimension,
int z0MatrixWidth = 1, int z0MatrixHeight = 1 );
%Docstring
Returns a tile matrix for a specific CRS, top left point, zoom level 0 dimension in CRS units
Returns a tile matrix for a specific CRS, top left point, zoom level 0 dimension in CRS units.
The ``z0Dimension`` argument must specify the dimension (width or height, in map units) of the root tiles in zoom level 0.
%End
static QgsTileMatrix fromTileMatrix( const int &zoomLevel, const QgsTileMatrix &tileMatrix );

View File

@ -130,6 +130,13 @@ Constructs a new vector tile layer
QgsVectorTileStructure &tileStructure();
%Docstring
Returns the vector tile structure.
.. versionadded:: 3.22.6
%End
QString sourceType() const;
%Docstring
Returns type of the data source

View File

@ -0,0 +1,197 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/vectortile/qgsvectortilestructure.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsVectorTileStructure
{
%Docstring(signature="appended")
Encapsulates properties of a vector tile structure, including tile origins and scaling information.
.. versionadded:: 3.22.6
%End
%TypeHeaderCode
#include "qgsvectortilestructure.h"
%End
public:
static QgsVectorTileStructure fromWebMercator();
%Docstring
Returns a vector tile structure corresponding to the standard web mercator/GoogleCRS84Quad setup.
%End
double z0xMin() const;
%Docstring
Returns the minimum x coordinate for zoom level 0.
This property is used when scaling raw vector tile coordinates.
.. seealso:: :py:func:`setZ0xMin`
%End
void setZ0xMin( double x );
%Docstring
Sets the minimum ``x`` coordinate for zoom level 0.
This property is used when scaling raw vector tile coordinates.
.. seealso:: :py:func:`z0xMin`
%End
double z0xMax() const;
%Docstring
Returns the maximum x coordinate for zoom level 0.
This property is used when scaling raw vector tile coordinates.
.. seealso:: :py:func:`setZ0xMax`
%End
void setZ0xMax( double x );
%Docstring
Sets the maximum ``x`` coordinate for zoom level 0.
This property is used when scaling raw vector tile coordinates.
.. seealso:: :py:func:`z0xMin`
%End
double z0yMin() const;
%Docstring
Returns the minimum y coordinate for zoom level 0.
This property is used when scaling raw vector tile coordinates.
.. seealso:: :py:func:`setZ0xMin`
%End
void setZ0yMin( double y );
%Docstring
Sets the minimum ``y`` coordinate for zoom level 0.
This property is used when scaling raw vector tile coordinates.
.. seealso:: :py:func:`z0yMin`
%End
double z0yMax() const;
%Docstring
Returns the maximum y coordinate for zoom level 0.
This property is used when scaling raw vector tile coordinates.
.. seealso:: :py:func:`setZ0yMax`
%End
void setZ0yMax( double y );
%Docstring
Sets the maximum ``y`` coordinate for zoom level 0.
This property is used when scaling raw vector tile coordinates.
.. seealso:: :py:func:`z0yMin`
%End
double z0Dimension() const;
%Docstring
Returns the dimension (width or height, in map units) of the root tiles in zoom level 0.
.. seealso:: :py:func:`setZ0Dimension`
%End
void setZ0Dimension( double dimension );
%Docstring
Sets the ``dimension`` (width or height, in map units) of the root tiles in zoom level 0.
.. seealso:: :py:func:`z0Dimension`
%End
double z0Scale() const;
%Docstring
Returns the scale denominator corresponding to root tiles in zoom level 0.
.. seealso:: :py:func:`setZ0Dimension`
%End
void setZ0Scale( double scale );
%Docstring
Sets the ``scale`` denominator of the root tiles in zoom level 0.
.. seealso:: :py:func:`z0Scale`
%End
QgsCoordinateReferenceSystem crs() const;
%Docstring
Returns the coordinate reference system associated with the tiles.
.. seealso:: :py:func:`setCrs`
%End
void setCrs( const QgsCoordinateReferenceSystem &crs );
%Docstring
Sets the coordinate reference system associated with the tiles.
.. seealso:: :py:func:`crs`
%End
int minimumZoom() const;
%Docstring
Returns the minimum zoom level for tiles (where negative = unconstrained).
.. seealso:: :py:func:`setMinimumZoom`
%End
void setMinimumZoom( int zoom );
%Docstring
Sets the minimum ``zoom`` level for tiles (where negative = unconstrained).
.. seealso:: :py:func:`minimumZoom`
%End
int maximumZoom() const;
%Docstring
Returns the maximum zoom level for tiles (where negative = unconstrained).
.. seealso:: :py:func:`setMaximumZoom`
%End
void setMaximumZoom( int zoom );
%Docstring
Sets the maximum ``zoom`` level for tiles (where negative = unconstrained).
.. seealso:: :py:func:`maximumZoom`
%End
QgsTileMatrix tileMatrix( int zoom ) const;
%Docstring
Returns the tile matrix corresponding to the specified ``zoom``.
%End
double scaleToZoom( double scale ) const;
%Docstring
Finds zoom level given a map ``scale`` denominator.
%End
int scaleToZoomLevel( double scale ) const;
%Docstring
Finds the best fitting (integer) zoom level given a map ``scale`` denominator.
Values are constrained to the zoom levels between :py:func:`~QgsVectorTileStructure.minimumZoom` and :py:func:`~QgsVectorTileStructure.maximumZoom`.
%End
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/vectortile/qgsvectortilestructure.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -692,6 +692,7 @@
%Include auto_generated/vectortile/qgsvectortilelabeling.sip
%Include auto_generated/vectortile/qgsvectortilelayer.sip
%Include auto_generated/vectortile/qgsvectortilerenderer.sip
%Include auto_generated/vectortile/qgsvectortilestructure.sip
%Include auto_generated/vectortile/qgsvectortilewriter.sip
%Include auto_generated/gps/qgsqtlocationconnection.sip
%Include auto_generated/gps/qgsgpsconnectionregistry.sip

View File

@ -845,6 +845,7 @@ set(QGIS_CORE_SRCS
vectortile/qgsvectortilemvtencoder.cpp
vectortile/qgsvectortilemvtutils.cpp
vectortile/qgsvectortileprovidermetadata.cpp
vectortile/qgsvectortilestructure.cpp
vectortile/qgsvectortileutils.cpp
vectortile/qgsvectortilewriter.cpp
@ -1788,6 +1789,7 @@ set(QGIS_CORE_HDRS
vectortile/qgsvectortilemvtutils.h
vectortile/qgsvectortileprovidermetadata.h
vectortile/qgsvectortilerenderer.h
vectortile/qgsvectortilestructure.h
vectortile/qgsvectortileutils.h
vectortile/qgsvectortilewriter.h
)

View File

@ -107,7 +107,11 @@ class CORE_EXPORT QgsTileMatrix
//! Returns a tile matrix for the usual web mercator
static QgsTileMatrix fromWebMercator( int zoomLevel );
//! Returns a tile matrix for a specific CRS, top left point, zoom level 0 dimension in CRS units
/**
* Returns a tile matrix for a specific CRS, top left point, zoom level 0 dimension in CRS units.
*
* The \a z0Dimension argument must specify the dimension (width or height, in map units) of the root tiles in zoom level 0.
*/
static QgsTileMatrix fromCustomDef( int zoomLevel, const QgsCoordinateReferenceSystem &crs,
const QgsPointXY &z0TopLeftPoint, double z0Dimension,
int z0MatrixWidth = 1, int z0MatrixHeight = 1 );

View File

@ -40,6 +40,8 @@ QgsVectorTileLayer::QgsVectorTileLayer( const QString &uri, const QString &baseN
: QgsMapLayer( QgsMapLayerType::VectorTileLayer, baseName )
, mTransformContext( options.transformContext )
{
mTileStructure = QgsVectorTileStructure::fromWebMercator();
mDataSource = uri;
setValid( loadDataSource() );
@ -80,13 +82,13 @@ bool QgsVectorTileLayer::loadDataSource()
}
// online tiles
mSourceMinZoom = 0;
mSourceMaxZoom = 14;
mTileStructure.setMinimumZoom( 0 );
mTileStructure.setMaximumZoom( 14 );
if ( dsUri.hasParam( QStringLiteral( "zmin" ) ) )
mSourceMinZoom = dsUri.param( QStringLiteral( "zmin" ) ).toInt();
mTileStructure.setMinimumZoom( dsUri.param( QStringLiteral( "zmin" ) ).toInt() );
if ( dsUri.hasParam( QStringLiteral( "zmax" ) ) )
mSourceMaxZoom = dsUri.param( QStringLiteral( "zmax" ) ).toInt();
mTileStructure.setMaximumZoom( dsUri.param( QStringLiteral( "zmax" ) ).toInt() );
setExtent( QgsRectangle( -20037508.3427892, -20037508.3427892, 20037508.3427892, 20037508.3427892 ) );
}
@ -111,10 +113,10 @@ bool QgsVectorTileLayer::loadDataSource()
const int minZoom = reader.metadataValue( QStringLiteral( "minzoom" ) ).toInt( &minZoomOk );
const int maxZoom = reader.metadataValue( QStringLiteral( "maxzoom" ) ).toInt( &maxZoomOk );
if ( minZoomOk )
mSourceMinZoom = minZoom;
mTileStructure.setMinimumZoom( minZoom );
if ( maxZoomOk )
mSourceMaxZoom = maxZoom;
QgsDebugMsgLevel( QStringLiteral( "zoom range: %1 - %2" ).arg( mSourceMinZoom ).arg( mSourceMaxZoom ), 2 );
mTileStructure.setMaximumZoom( maxZoom );
QgsDebugMsgLevel( QStringLiteral( "zoom range: %1 - %2" ).arg( mTileStructure.minimumZoom() ).arg( mTileStructure.maximumZoom() ), 2 );
QgsRectangle r = reader.extent();
QgsCoordinateTransform ct( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ),
@ -190,14 +192,14 @@ bool QgsVectorTileLayer::setupArcgisVectorTileServiceConnection( const QString &
// if hardcoded zoom limits aren't specified, take them from the server
if ( !dataSourceUri.hasParam( QStringLiteral( "zmin" ) ) )
mSourceMinZoom = 0;
mTileStructure.setMinimumZoom( 0 );
else
mSourceMinZoom = dataSourceUri.param( QStringLiteral( "zmin" ) ).toInt();
mTileStructure.setMinimumZoom( dataSourceUri.param( QStringLiteral( "zmin" ) ).toInt() );
if ( !dataSourceUri.hasParam( QStringLiteral( "zmax" ) ) )
mSourceMaxZoom = mArcgisLayerConfiguration.value( QStringLiteral( "maxzoom" ) ).toInt();
mTileStructure.setMaximumZoom( mArcgisLayerConfiguration.value( QStringLiteral( "maxzoom" ) ).toInt() );
else
mSourceMaxZoom = dataSourceUri.param( QStringLiteral( "zmax" ) ).toInt();
mTileStructure.setMaximumZoom( dataSourceUri.param( QStringLiteral( "zmax" ) ).toInt() );
setExtent( QgsRectangle( -20037508.3427892, -20037508.3427892, 20037508.3427892, 20037508.3427892 ) );
@ -234,6 +236,26 @@ bool QgsVectorTileLayer::readXml( const QDomNode &layerNode, QgsReadWriteContext
{
setValid( loadDataSource() );
const QDomElement structureElement = layerNode.firstChildElement( QStringLiteral( "tileStructure" ) );
if ( !structureElement.isNull() )
{
QgsVectorTileStructure structure;
structure.setZ0xMin( structureElement.attribute( QStringLiteral( "z0xMin" ) ).toDouble() );
structure.setZ0xMax( structureElement.attribute( QStringLiteral( "z0xMax" ) ).toDouble() );
structure.setZ0yMin( structureElement.attribute( QStringLiteral( "z0yMin" ) ).toDouble() );
structure.setZ0yMax( structureElement.attribute( QStringLiteral( "z0yMax" ) ).toDouble() );
structure.setZ0Dimension( structureElement.attribute( QStringLiteral( "z0Dimension" ) ).toDouble() );
structure.setZ0Scale( structureElement.attribute( QStringLiteral( "z0Scale" ) ).toDouble() );
structure.setMinimumZoom( structureElement.attribute( QStringLiteral( "minZoom" ) ).toInt() );
structure.setMaximumZoom( structureElement.attribute( QStringLiteral( "maxZoom" ) ).toInt() );
QgsCoordinateReferenceSystem crs;
crs.readXml( structureElement );
structure.setCrs( crs );
mTileStructure = structure;
}
QString errorMsg;
if ( !readSymbology( layerNode, errorMsg, context ) )
return false;
@ -247,6 +269,20 @@ bool QgsVectorTileLayer::writeXml( QDomNode &layerNode, QDomDocument &doc, const
QDomElement mapLayerNode = layerNode.toElement();
mapLayerNode.setAttribute( QStringLiteral( "type" ), QgsMapLayerFactory::typeToString( QgsMapLayerType::VectorTileLayer ) );
QDomElement structureElement = doc.createElement( QStringLiteral( "tileStructure" ) );
structureElement.setAttribute( QStringLiteral( "z0xMin" ), qgsDoubleToString( mTileStructure.z0xMin() ) );
structureElement.setAttribute( QStringLiteral( "z0xMax" ), qgsDoubleToString( mTileStructure.z0xMax() ) );
structureElement.setAttribute( QStringLiteral( "z0yMin" ), qgsDoubleToString( mTileStructure.z0yMin() ) );
structureElement.setAttribute( QStringLiteral( "z0yMax" ), qgsDoubleToString( mTileStructure.z0yMax() ) );
structureElement.setAttribute( QStringLiteral( "z0Dimension" ), qgsDoubleToString( mTileStructure.z0Dimension() ) );
structureElement.setAttribute( QStringLiteral( "z0Scale" ), qgsDoubleToString( mTileStructure.z0Scale() ) );
structureElement.setAttribute( QStringLiteral( "minZoom" ), mTileStructure.minimumZoom() );
structureElement.setAttribute( QStringLiteral( "maxZoom" ), mTileStructure.maximumZoom() );
mTileStructure.crs().writeXml( structureElement, doc );
mapLayerNode.appendChild( structureElement );
mapLayerNode.setAttribute( QStringLiteral( "type" ), QgsMapLayerFactory::typeToString( QgsMapLayerType::VectorTileLayer ) );
// add provider node
if ( mDataProvider )
{
@ -702,7 +738,7 @@ QString QgsVectorTileLayer::htmlMetadata() const
QByteArray QgsVectorTileLayer::getRawTile( QgsTileXYZ tileID )
{
const QgsTileMatrix tileMatrix = QgsTileMatrix::fromWebMercator( tileID.zoomLevel() );
const QgsTileMatrix tileMatrix = mTileStructure.tileMatrix( tileID.zoomLevel() );
const QgsTileRange tileRange( tileID.column(), tileID.column(), tileID.row(), tileID.row() );
QgsDataSourceUri dsUri;

View File

@ -20,6 +20,7 @@
#include "qgis_sip.h"
#include "qgsmaplayer.h"
#include "qgsvectortilestructure.h"
class QgsVectorTileLabeling;
class QgsVectorTileRenderer;
@ -155,15 +156,22 @@ class CORE_EXPORT QgsVectorTileLayer : public QgsMapLayer
// new methods
/**
* Returns the vector tile structure.
*
* \since QGIS 3.22.6
*/
QgsVectorTileStructure &tileStructure() { return mTileStructure; }
//! Returns type of the data source
QString sourceType() const { return mSourceType; }
//! Returns URL/path of the data source (syntax different to each data source type)
QString sourcePath() const { return mSourcePath; }
//! Returns minimum zoom level at which source has any valid tiles (negative = unconstrained)
int sourceMinZoom() const { return mSourceMinZoom; }
int sourceMinZoom() const { return mTileStructure.minimumZoom(); }
//! Returns maximum zoom level at which source has any valid tiles (negative = unconstrained)
int sourceMaxZoom() const { return mSourceMaxZoom; }
int sourceMaxZoom() const { return mTileStructure.maximumZoom(); }
/**
* Fetches raw tile data for the give tile coordinates. If failed to fetch tile data,
@ -203,10 +211,8 @@ class CORE_EXPORT QgsVectorTileLayer : public QgsMapLayer
QString mSourceType;
//! URL/Path of the data source
QString mSourcePath;
//! Minimum zoom level at which source has any valid tiles (negative = unconstrained)
int mSourceMinZoom = -1;
//! Maximum zoom level at which source has any valid tiles (negative = unconstrained)
int mSourceMaxZoom = -1;
QgsVectorTileStructure mTileStructure;
//! Renderer assigned to the layer to draw map
std::unique_ptr<QgsVectorTileRenderer> mRenderer;

View File

@ -35,12 +35,11 @@ QgsVectorTileLayerRenderer::QgsVectorTileLayerRenderer( QgsVectorTileLayer *laye
: QgsMapLayerRenderer( layer->id(), &context )
, mSourceType( layer->sourceType() )
, mSourcePath( layer->sourcePath() )
, mSourceMinZoom( layer->sourceMinZoom() )
, mSourceMaxZoom( layer->sourceMaxZoom() )
, mRenderer( layer->renderer()->clone() )
, mDrawTileBoundaries( layer->isTileBorderRenderingEnabled() )
, mFeedback( new QgsFeedback )
, mLayerOpacity( layer->opacity() )
, mTileStructure( layer->tileStructure() )
{
QgsDataSourceUri dsUri;
@ -86,10 +85,10 @@ bool QgsVectorTileLayerRenderer::render()
QgsDebugMsgLevel( QStringLiteral( "Vector tiles rendering extent: " ) + ctx.extent().toString( -1 ), 2 );
QgsDebugMsgLevel( QStringLiteral( "Vector tiles map scale 1 : %1" ).arg( ctx.rendererScale() ), 2 );
mTileZoom = QgsVectorTileUtils::scaleToZoomLevel( ctx.rendererScale(), mSourceMinZoom, mSourceMaxZoom );
mTileZoom = mTileStructure.scaleToZoomLevel( ctx.rendererScale() );
QgsDebugMsgLevel( QStringLiteral( "Vector tiles zoom level: %1" ).arg( mTileZoom ), 2 );
mTileMatrix = QgsTileMatrix::fromWebMercator( mTileZoom );
mTileMatrix = QgsTileMatrix::fromCustomDef( mTileZoom, mTileStructure.crs(), QgsPointXY( mTileStructure.z0xMin(), mTileStructure.z0yMax() ), mTileStructure.z0Dimension() );
mTileRange = mTileMatrix.tileRangeFromExtent( ctx.extent() );
QgsDebugMsgLevel( QStringLiteral( "Vector tiles range X: %1 - %2 Y: %3 - %4" )
@ -150,7 +149,7 @@ bool QgsVectorTileLayerRenderer::render()
// add @zoom_level variable which can be used in styling
QgsExpressionContextScope *scope = new QgsExpressionContextScope( QObject::tr( "Tiles" ) ); // will be deleted by popper
scope->setVariable( QStringLiteral( "zoom_level" ), mTileZoom, true );
scope->setVariable( QStringLiteral( "vector_tile_zoom" ), QgsVectorTileUtils::scaleToZoom( ctx.rendererScale() ), true );
scope->setVariable( QStringLiteral( "vector_tile_zoom" ), mTileStructure.scaleToZoom( ctx.rendererScale() ), true );
const QgsExpressionContextScopePopper popper( ctx.expressionContext(), scope );
mRenderer->startRender( *renderContext(), mTileZoom, mTileRange );
@ -188,7 +187,7 @@ bool QgsVectorTileLayerRenderer::render()
if ( !isAsync )
{
for ( const QgsVectorTileRawData &rawTile : rawTiles )
for ( const QgsVectorTileRawData &rawTile : std::as_const( rawTiles ) )
{
if ( ctx.renderingStopped() )
break;
@ -229,7 +228,7 @@ void QgsVectorTileLayerRenderer::decodeAndDrawTile( const QgsVectorTileRawData &
tLoad.start();
// currently only MVT encoding supported
QgsVectorTileMVTDecoder decoder;
QgsVectorTileMVTDecoder decoder( mTileStructure );
if ( !decoder.decode( rawTile.id, rawTile.data ) )
{
QgsDebugMsgLevel( QStringLiteral( "Failed to parse raw tile data! " ) + rawTile.id.toString(), 2 );
@ -262,17 +261,19 @@ void QgsVectorTileLayerRenderer::decodeAndDrawTile( const QgsVectorTileRawData &
if ( ctx.renderingStopped() )
return;
// set up clipping so that rendering does not go behind tile's extent
const QgsScopedQPainterState savePainterState( ctx.painter() );
// we have to intersect with any existing painter clip regions, or we risk overwriting valid clip
// regions setup outside of the vector tile renderer (e.g. layout map clip region)
ctx.painter()->setClipRegion( QRegion( tile.tilePolygon() ), Qt::IntersectClip );
{
// set up clipping so that rendering does not go behind tile's extent
const QgsScopedQPainterState savePainterState( ctx.painter() );
// we have to intersect with any existing painter clip regions, or we risk overwriting valid clip
// regions setup outside of the vector tile renderer (e.g. layout map clip region)
ctx.painter()->setClipRegion( QRegion( tile.tilePolygon() ), Qt::IntersectClip );
QElapsedTimer tDraw;
tDraw.start();
QElapsedTimer tDraw;
tDraw.start();
mRenderer->renderTile( tile, ctx );
mTotalDrawTime += tDraw.elapsed();
mRenderer->renderTile( tile, ctx );
mTotalDrawTime += tDraw.elapsed();
}
if ( mLabelProvider )
mLabelProvider->registerTileFeatures( tile, ctx );
@ -284,7 +285,14 @@ void QgsVectorTileLayerRenderer::decodeAndDrawTile( const QgsVectorTileRawData &
QPen pen( Qt::red );
pen.setWidth( 3 );
QBrush brush( QColor( 255, 0, 0, 40 ), Qt::BrushStyle::Dense3Pattern );
ctx.painter()->setPen( pen );
ctx.painter()->setBrush( brush );
ctx.painter()->drawPolygon( tile.tilePolygon() );
#if 0
ctx.painter()->setBrush( QBrush( QColor( 255, 0, 0 ) ) );
ctx.painter()->drawText( tile.tilePolygon().boundingRect().center(), tile.id().toString() );
#endif
}
}

View File

@ -27,6 +27,7 @@ class QgsVectorTileLabelProvider;
#include "qgsvectortilerenderer.h"
#include "qgsmapclippingregion.h"
#include "qgshttpheaders.h"
#include "qgsvectortilestructure.h"
/**
* \ingroup core
@ -62,10 +63,6 @@ class QgsVectorTileLayerRenderer : public QgsMapLayerRenderer
QString mAuthCfg;
QgsHttpHeaders mHeaders;
//! Minimum zoom level at which source has any valid tiles (negative = unconstrained)
int mSourceMinZoom = -1;
//! Maximum zoom level at which source has any valid tiles (negative = unconstrained)
int mSourceMaxZoom = -1;
//! Tile renderer object to do rendering of individual tiles
std::unique_ptr<QgsVectorTileRenderer> mRenderer;
@ -101,6 +98,9 @@ class QgsVectorTileLayerRenderer : public QgsMapLayerRenderer
QList< QgsMapClippingRegion > mClippingRegions;
double mLayerOpacity = 1.0;
QgsVectorTileStructure mTileStructure;
};

View File

@ -31,7 +31,9 @@
#include <QPointer>
QgsVectorTileMVTDecoder::QgsVectorTileMVTDecoder() = default;
QgsVectorTileMVTDecoder::QgsVectorTileMVTDecoder( const QgsVectorTileStructure &structure )
: mStructure( structure )
{}
QgsVectorTileMVTDecoder::~QgsVectorTileMVTDecoder() = default;
@ -55,7 +57,9 @@ bool QgsVectorTileMVTDecoder::decode( QgsTileXYZ tileID, const QByteArray &rawTi
QStringList QgsVectorTileMVTDecoder::layers() const
{
QStringList layerNames;
for ( int layerNum = 0; layerNum < tile.layers_size(); layerNum++ )
const int layerSize = tile.layers_size();
layerNames.reserve( layerSize );
for ( int layerNum = 0; layerNum < layerSize; layerNum++ )
{
const ::vector_tile::Tile_Layer &layer = tile.layers( layerNum );
const QString layerName = layer.name().c_str();
@ -71,7 +75,9 @@ QStringList QgsVectorTileMVTDecoder::layerFieldNames( const QString &layerName )
const ::vector_tile::Tile_Layer &layer = tile.layers( mLayerNameToIndex[layerName] );
QStringList fieldNames;
for ( int i = 0; i < layer.keys_size(); ++i )
const int size = layer.keys_size();
fieldNames.reserve( size );
for ( int i = 0; i < size; ++i )
{
const QString fieldName = layer.keys( i ).c_str();
fieldNames << fieldName;
@ -84,12 +90,10 @@ 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
double z0xMin = -20037508.3427892, z0yMin = -20037508.3427892;
double z0xMax = 20037508.3427892, z0yMax = 20037508.3427892;
const double tileDX = ( z0xMax - z0xMin ) / numTiles;
const double tileDY = ( z0yMax - z0yMin ) / numTiles;
const double tileXMin = z0xMin + mTileID.column() * tileDX;
const double tileYMax = z0yMax - mTileID.row() * tileDY;
const double tileDX = ( mStructure.z0xMax() - mStructure.z0xMin() ) / numTiles;
const double tileDY = ( mStructure.z0yMax() - mStructure.z0yMin() ) / numTiles;
const double tileXMin = mStructure.z0xMin() + mTileID.column() * tileDX;
const double tileYMax = mStructure.z0yMax() - mTileID.row() * tileDY;
for ( int layerNum = 0; layerNum < tile.layers_size(); layerNum++ )
{

View File

@ -26,6 +26,8 @@ class QgsFeature;
#include "vector_tile.pb.h"
#include "qgsvectortilerenderer.h"
#include "qgsvectortilestructure.h"
/**
* \ingroup core
@ -36,7 +38,11 @@ class QgsFeature;
class CORE_EXPORT QgsVectorTileMVTDecoder
{
public:
QgsVectorTileMVTDecoder();
/**
* Constructor for QgsVectorTileMVTDecoder, using the specified tile \a structure.
*/
QgsVectorTileMVTDecoder( const QgsVectorTileStructure &structure );
~QgsVectorTileMVTDecoder();
//! Tries to decode raw tile data, returns true on success
@ -59,6 +65,7 @@ class CORE_EXPORT QgsVectorTileMVTDecoder
private:
vector_tile::Tile tile;
QgsTileXYZ mTileID;
QgsVectorTileStructure mStructure;
QMap<QString, int> mLayerNameToIndex;
};

View File

@ -0,0 +1,46 @@
/***************************************************************************
qgsvectortilestructure.cpp
--------------------------------------
Date : March 2022
Copyright : (C) 2022 by Nyall Dawson
Email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsvectortilestructure.h"
#include "qgstiles.h"
#include "qgsvectortileutils.h"
QgsVectorTileStructure QgsVectorTileStructure::fromWebMercator()
{
QgsVectorTileStructure res;
res.setCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3857" ) ) );
res.setZ0xMin( -20037508.3427892 );
res.setZ0xMax( 20037508.3427892 );
res.setZ0yMin( -20037508.3427892 );
res.setZ0yMax( 20037508.3427892 );
res.setZ0Dimension( 2 * 20037508.3427892 );
res.setZ0Scale( 559082264.0287178 );
return res;
}
QgsTileMatrix QgsVectorTileStructure::tileMatrix( int zoom ) const
{
return QgsTileMatrix::fromCustomDef( zoom, mCrs, QgsPointXY( mZ0xMin, mZ0yMax ), mZ0Dimension );
}
double QgsVectorTileStructure::scaleToZoom( double scale ) const
{
return QgsVectorTileUtils::scaleToZoom( scale, mZ0Scale );
}
int QgsVectorTileStructure::scaleToZoomLevel( double scale ) const
{
return QgsVectorTileUtils::scaleToZoomLevel( scale, mMinZoom, mMaxZoom, mZ0Scale );
}

View File

@ -0,0 +1,215 @@
/***************************************************************************
qgsvectortilestructure.h
--------------------------------------
Date : March 2022
Copyright : (C) 2022 by Nyall Dawson
Email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSVECTORTILESTRUCTURE_H
#define QGSVECTORTILESTRUCTURE_H
#include "qgis_core.h"
#include "qgis_sip.h"
#include "qgscoordinatereferencesystem.h"
class QgsTileMatrix;
/**
* Encapsulates properties of a vector tile structure, including tile origins and scaling information.
* \ingroup core
* \since QGIS 3.22.6
*/
class CORE_EXPORT QgsVectorTileStructure
{
public:
/**
* Returns a vector tile structure corresponding to the standard web mercator/GoogleCRS84Quad setup.
*/
static QgsVectorTileStructure fromWebMercator();
/**
* Returns the minimum x coordinate for zoom level 0.
*
* This property is used when scaling raw vector tile coordinates.
*
* \see setZ0xMin()
*/
double z0xMin() const { return mZ0xMin; }
/**
* Sets the minimum \a x coordinate for zoom level 0.
*
* This property is used when scaling raw vector tile coordinates.
*
* \see z0xMin()
*/
void setZ0xMin( double x ) { mZ0xMin = x; }
/**
* Returns the maximum x coordinate for zoom level 0.
*
* This property is used when scaling raw vector tile coordinates.
*
* \see setZ0xMax()
*/
double z0xMax() const { return mZ0xMax; }
/**
* Sets the maximum \a x coordinate for zoom level 0.
*
* This property is used when scaling raw vector tile coordinates.
*
* \see z0xMin()
*/
void setZ0xMax( double x ) { mZ0xMax = x; }
/**
* Returns the minimum y coordinate for zoom level 0.
*
* This property is used when scaling raw vector tile coordinates.
*
* \see setZ0xMin()
*/
double z0yMin() const { return mZ0yMin; }
/**
* Sets the minimum \a y coordinate for zoom level 0.
*
* This property is used when scaling raw vector tile coordinates.
*
* \see z0yMin()
*/
void setZ0yMin( double y ) { mZ0yMin = y; }
/**
* Returns the maximum y coordinate for zoom level 0.
*
* This property is used when scaling raw vector tile coordinates.
*
* \see setZ0yMax()
*/
double z0yMax() const { return mZ0yMax; }
/**
* Sets the maximum \a y coordinate for zoom level 0.
*
* This property is used when scaling raw vector tile coordinates.
*
* \see z0yMin()
*/
void setZ0yMax( double y ) { mZ0yMax = y; }
/**
* Returns the dimension (width or height, in map units) of the root tiles in zoom level 0.
*
* \see setZ0Dimension()
*/
double z0Dimension() const { return mZ0Dimension; }
/**
* Sets the \a dimension (width or height, in map units) of the root tiles in zoom level 0.
*
* \see z0Dimension()
*/
void setZ0Dimension( double dimension ) { mZ0Dimension = dimension;}
/**
* Returns the scale denominator corresponding to root tiles in zoom level 0.
*
* \see setZ0Dimension()
*/
double z0Scale() const { return mZ0Scale; }
/**
* Sets the \a scale denominator of the root tiles in zoom level 0.
*
* \see z0Scale()
*/
void setZ0Scale( double scale ) { mZ0Scale = scale;}
/**
* Returns the coordinate reference system associated with the tiles.
*
* \see setCrs()
*/
QgsCoordinateReferenceSystem crs() const { return mCrs; }
/**
* Sets the coordinate reference system associated with the tiles.
*
* \see crs()
*/
void setCrs( const QgsCoordinateReferenceSystem &crs ) { mCrs = crs; }
/**
* Returns the minimum zoom level for tiles (where negative = unconstrained).
*
* \see setMinimumZoom()
*/
int minimumZoom() const { return mMinZoom; }
/**
* Sets the minimum \a zoom level for tiles (where negative = unconstrained).
*
* \see minimumZoom()
*/
void setMinimumZoom( int zoom ) { mMinZoom = zoom; }
/**
* Returns the maximum zoom level for tiles (where negative = unconstrained).
*
* \see setMaximumZoom()
*/
int maximumZoom() const { return mMaxZoom; }
/**
* Sets the maximum \a zoom level for tiles (where negative = unconstrained).
*
* \see maximumZoom()
*/
void setMaximumZoom( int zoom ) { mMaxZoom = zoom; }
/**
* Returns the tile matrix corresponding to the specified \a zoom.
*/
QgsTileMatrix tileMatrix( int zoom ) const;
/**
* Finds zoom level given a map \a scale denominator.
*/
double scaleToZoom( double scale ) const;
/**
* Finds the best fitting (integer) zoom level given a map \a scale denominator.
*
* Values are constrained to the zoom levels between minimumZoom() and maximumZoom().
*/
int scaleToZoomLevel( double scale ) const;
private:
double mZ0xMin = 0;
double mZ0xMax = 0;
double mZ0yMin = 0;
double mZ0yMax = 0;
double mZ0Dimension = 0;
double mZ0Scale = 0;
int mMinZoom = -1;
int mMaxZoom = -1;
QgsCoordinateReferenceSystem mCrs;
};
#endif // QGSVECTORTILESTRUCTURE_H

View File

@ -48,7 +48,7 @@ QPolygon QgsVectorTileUtils::tilePolygon( QgsTileXYZ id, const QgsCoordinateTran
return path;
}
QgsFields QgsVectorTileUtils::makeQgisFields( QSet<QString> flds )
QgsFields QgsVectorTileUtils::makeQgisFields( const QSet<QString> &flds )
{
QgsFields fields;
QStringList fieldsSorted = qgis::setToList( flds );
@ -60,17 +60,17 @@ QgsFields QgsVectorTileUtils::makeQgisFields( QSet<QString> flds )
return fields;
}
double QgsVectorTileUtils::scaleToZoom( double mapScale )
double QgsVectorTileUtils::scaleToZoom( double mapScale, double z0Scale )
{
double s0 = 559082264.0287178; // scale denominator at zoom level 0 of GoogleCRS84Quad
double s0 = z0Scale;
double tileZoom2 = log( s0 / mapScale ) / log( 2 );
tileZoom2 -= 1; // TODO: it seems that map scale is double (is that because of high-dpi screen?)
return tileZoom2;
}
int QgsVectorTileUtils::scaleToZoomLevel( double mapScale, int sourceMinZoom, int sourceMaxZoom )
int QgsVectorTileUtils::scaleToZoomLevel( double mapScale, int sourceMinZoom, int sourceMaxZoom, double z0Scale )
{
int tileZoom = static_cast<int>( round( scaleToZoom( mapScale ) ) );
int tileZoom = static_cast<int>( round( scaleToZoom( mapScale, z0Scale ) ) );
if ( tileZoom < sourceMinZoom )
tileZoom = sourceMinZoom;
@ -82,7 +82,7 @@ int QgsVectorTileUtils::scaleToZoomLevel( double mapScale, int sourceMinZoom, in
QgsVectorLayer *QgsVectorTileUtils::makeVectorLayerForTile( QgsVectorTileLayer *mvt, QgsTileXYZ tileID, const QString &layerName )
{
QgsVectorTileMVTDecoder decoder;
QgsVectorTileMVTDecoder decoder( mvt->tileStructure() );
decoder.decode( tileID, mvt->getRawTile( tileID ) );
QSet<QString> fieldNames = qgis::listToSet( decoder.layerFieldNames( layerName ) );
fieldNames << QStringLiteral( "_geom_type" );
@ -112,7 +112,7 @@ QgsVectorLayer *QgsVectorTileUtils::makeVectorLayerForTile( QgsVectorTileLayer *
vl->dataProvider()->addAttributes( fields.toList() );
vl->updateFields();
bool res = vl->dataProvider()->addFeatures( featuresList );
Q_UNUSED( res );
Q_UNUSED( res )
Q_ASSERT( res );
Q_ASSERT( featuresList.count() == vl->featureCount() );
vl->updateExtents();
@ -149,7 +149,7 @@ bool QgsVectorTileUtils::checkXYZUrlTemplate( const QString &url )
struct LessThanTileRequest
{
QPointF center; //!< Center in tile matrix (!) coordinates
bool operator()( const QgsTileXYZ &req1, const QgsTileXYZ &req2 )
bool operator()( QgsTileXYZ req1, QgsTileXYZ req2 )
{
QPointF p1( req1.column() + 0.5, req1.row() + 0.5 );
QPointF p2( req2.column() + 0.5, req2.row() + 0.5 );
@ -160,7 +160,7 @@ struct LessThanTileRequest
}
};
QVector<QgsTileXYZ> QgsVectorTileUtils::tilesInRange( const QgsTileRange &range, int zoomLevel )
QVector<QgsTileXYZ> QgsVectorTileUtils::tilesInRange( QgsTileRange range, int zoomLevel )
{
QVector<QgsTileXYZ> tiles;
for ( int tileRow = range.startRow(); tileRow <= range.endRow(); ++tileRow )
@ -173,7 +173,7 @@ QVector<QgsTileXYZ> QgsVectorTileUtils::tilesInRange( const QgsTileRange &range,
return tiles;
}
void QgsVectorTileUtils::sortTilesByDistanceFromCenter( QVector<QgsTileXYZ> &tiles, const QPointF &center )
void QgsVectorTileUtils::sortTilesByDistanceFromCenter( QVector<QgsTileXYZ> &tiles, QPointF center )
{
LessThanTileRequest cmp;
cmp.center = center;

View File

@ -47,9 +47,9 @@ class CORE_EXPORT QgsVectorTileUtils
public:
//! Returns a list of tiles in the given tile range
static QVector<QgsTileXYZ> tilesInRange( const QgsTileRange &range, int zoomLevel );
static QVector<QgsTileXYZ> tilesInRange( QgsTileRange range, int zoomLevel );
//! Orders tile requests according to the distance from view center (given in tile matrix coords)
static void sortTilesByDistanceFromCenter( QVector<QgsTileXYZ> &tiles, const QPointF &center );
static void sortTilesByDistanceFromCenter( QVector<QgsTileXYZ> &tiles, QPointF center );
/**
* Returns polygon (made by four corners of the tile) in screen coordinates
@ -59,17 +59,25 @@ class CORE_EXPORT QgsVectorTileUtils
static QPolygon tilePolygon( QgsTileXYZ id, const QgsCoordinateTransform &ct, const QgsTileMatrix &tm, const QgsMapToPixel &mtp );
//! Returns QgsFields instance based on the set of field names
static QgsFields makeQgisFields( QSet<QString> flds );
static QgsFields makeQgisFields( const QSet<QString> &flds );
/**
* Finds zoom level (assuming GoogleCRS84Quad tile matrix set) given map scale denominator.
* Finds zoom level given map scale denominator.
*
* The \a z0Scale argument gives the scale denominator at zoom level 0, where the default
* value corresponds to GoogleCRS84Quad tile matrix set
*
* \since QGIS 3.16
*/
static double scaleToZoom( double mapScale );
static double scaleToZoom( double mapScale, double z0Scale = 559082264.0287178 );
//! Finds best fitting zoom level (assuming GoogleCRS84Quad tile matrix set) given map scale denominator and allowed zoom level range
static int scaleToZoomLevel( double mapScale, int sourceMinZoom, int sourceMaxZoom );
/**
* Finds the best fitting zoom level given a map scale denominator and allowed zoom level range.
*
* The \a z0Scale argument gives the scale denominator at zoom level 0, where the default
* value corresponds to GoogleCRS84Quad tile matrix set.
*/
static int scaleToZoomLevel( double mapScale, int sourceMinZoom, int sourceMaxZoom, double z0Scale = 559082264.0287178 );
//! Returns a temporary vector layer for given sub-layer of tile in vector tile layer
static QgsVectorLayer *makeVectorLayerForTile( QgsVectorTileLayer *mvt, QgsTileXYZ tileID, const QString &layerName );
//! Returns formatted tile URL string replacing {x}, {y}, {z} placeholders (or {-y} instead of {y} for TMS convention)

View File

@ -447,9 +447,9 @@ bool QgsMapToolIdentify::identifyVectorTileLayer( QList<QgsMapToolIdentify::Iden
}
}
int tileZoom = QgsVectorTileUtils::scaleToZoomLevel( mCanvas->scale(), layer->sourceMinZoom(), layer->sourceMaxZoom() );
const QgsTileMatrix tileMatrix = QgsTileMatrix::fromWebMercator( tileZoom );
QgsTileRange tileRange = tileMatrix.tileRangeFromExtent( r );
const int tileZoom = layer->tileStructure().scaleToZoomLevel( mCanvas->scale() );
const QgsTileMatrix tileMatrix = layer->tileStructure().tileMatrix( tileZoom );
const QgsTileRange tileRange = tileMatrix.tileRangeFromExtent( r );
for ( int row = tileRange.startRow(); row <= tileRange.endRow(); ++row )
{
@ -460,7 +460,7 @@ bool QgsMapToolIdentify::identifyVectorTileLayer( QList<QgsMapToolIdentify::Iden
if ( data.isEmpty() )
continue; // failed to get data
QgsVectorTileMVTDecoder decoder;
QgsVectorTileMVTDecoder decoder( layer->tileStructure() );
if ( !decoder.decode( tileID, data ) )
continue; // failed to decode

View File

@ -116,7 +116,7 @@ void TestQgsVectorTileWriter::test_basic()
QgsVectorTileLayer *vtLayer = new QgsVectorTileLayer( ds.encodedUri(), "output" );
const QByteArray tile0 = vtLayer->getRawTile( QgsTileXYZ( 0, 0, 0 ) );
QgsVectorTileMVTDecoder decoder;
QgsVectorTileMVTDecoder decoder( QgsVectorTileStructure::fromWebMercator() );
const bool resDecode0 = decoder.decode( QgsTileXYZ( 0, 0, 0 ), tile0 );
QVERIFY( resDecode0 );
const QStringList layerNames = decoder.layers();
@ -185,7 +185,7 @@ void TestQgsVectorTileWriter::test_mbtiles()
QgsVectorTileLayer *vtLayer = new QgsVectorTileLayer( ds.encodedUri(), "output" );
const QByteArray tile0 = vtLayer->getRawTile( QgsTileXYZ( 0, 0, 0 ) );
QgsVectorTileMVTDecoder decoder;
QgsVectorTileMVTDecoder decoder( QgsVectorTileStructure::fromWebMercator() );
const bool resDecode0 = decoder.decode( QgsTileXYZ( 0, 0, 0 ), tile0 );
QVERIFY( resDecode0 );
const QStringList layerNames = decoder.layers();
@ -301,7 +301,7 @@ void TestQgsVectorTileWriter::test_filtering()
QgsVectorTileLayer *vtLayer = new QgsVectorTileLayer( ds.encodedUri(), "output" );
const QByteArray tile0 = vtLayer->getRawTile( QgsTileXYZ( 0, 0, 0 ) );
QgsVectorTileMVTDecoder decoder;
QgsVectorTileMVTDecoder decoder( QgsVectorTileStructure::fromWebMercator() );
const bool resDecode0 = decoder.decode( QgsTileXYZ( 0, 0, 0 ), tile0 );
QVERIFY( resDecode0 );
const QStringList layerNames = decoder.layers();
@ -364,7 +364,7 @@ void TestQgsVectorTileWriter::test_z0TileMatrix3857()
QgsVectorTileLayer *vtLayer = new QgsVectorTileLayer( ds.encodedUri(), "output" );
const QByteArray tile0 = vtLayer->getRawTile( QgsTileXYZ( 0, 0, 0 ) );
QgsVectorTileMVTDecoder decoder;
QgsVectorTileMVTDecoder decoder( QgsVectorTileStructure::fromWebMercator() );
const bool resDecode0 = decoder.decode( QgsTileXYZ( 0, 0, 0 ), tile0 );
QVERIFY( resDecode0 );
const QStringList layerNames = decoder.layers();
@ -450,7 +450,7 @@ void TestQgsVectorTileWriter::test_z0TileMatrix2154()
QgsVectorTileLayer *vtLayer = new QgsVectorTileLayer( ds.encodedUri(), "output" );
const QByteArray tile0 = vtLayer->getRawTile( QgsTileXYZ( 0, 0, 0 ) );
QgsVectorTileMVTDecoder decoder;
QgsVectorTileMVTDecoder decoder( QgsVectorTileStructure::fromWebMercator() );
const bool resDecode0 = decoder.decode( QgsTileXYZ( 0, 0, 0 ), tile0 );
QVERIFY( resDecode0 );
const QStringList layerNames = decoder.layers();