Skip to content

Commit

Permalink
Support TMS convention with vector tiles ({-y} instead of {y})
Browse files Browse the repository at this point in the history
XYZ convention has zero Y at the top and increasing downwards,
but various map servers use TMS convention which has zero Y at
the bottom and rising upwards (within tile matrix)
  • Loading branch information
wonder-sk committed May 12, 2020
1 parent ceea4f2 commit a516008
Show file tree
Hide file tree
Showing 10 changed files with 64 additions and 32 deletions.
15 changes: 15 additions & 0 deletions python/core/auto_generated/qgstiles.sip.in
Expand Up @@ -111,6 +111,11 @@ Please note that we follow the XYZ convention of X/Y axes, i.e. top-left tile ha
static QgsTileMatrix fromWebMercator( int mZoomLevel );
%Docstring
Returns a tile matrix for the usual web mercator
%End

int zoomLevel() const;
%Docstring
Returns zoom level of the tile matrix
%End

int matrixWidth() const;
Expand All @@ -121,6 +126,16 @@ Returns number of columns of the tile matrix
int matrixHeight() const;
%Docstring
Returns number of rows of the tile matrix
%End

QgsRectangle extent() const;
%Docstring
Returns extent of the tile matrix
%End

double scale() const;
%Docstring
Returns scale denominator of the tile matrix
%End

QgsRectangle tileExtent( QgsTileXYZ id ) const;
Expand Down
9 changes: 9 additions & 0 deletions src/core/qgstiles.h
Expand Up @@ -106,12 +106,21 @@ class CORE_EXPORT QgsTileMatrix
//! Returns a tile matrix for the usual web mercator
static QgsTileMatrix fromWebMercator( int mZoomLevel );

//! Returns zoom level of the tile matrix
int zoomLevel() const { return mZoomLevel; }

//! Returns number of columns of the tile matrix
int matrixWidth() const { return mMatrixWidth; }

//! Returns number of rows of the tile matrix
int matrixHeight() const { return mMatrixHeight; }

//! Returns extent of the tile matrix
QgsRectangle extent() const { return mExtent; }

//! Returns scale denominator of the tile matrix
double scale() const { return mScaleDenom; }

//! Returns extent of the given tile in this matrix
QgsRectangle tileExtent( QgsTileXYZ id ) const;

Expand Down
10 changes: 9 additions & 1 deletion src/core/vectortile/qgsvectortilelayer.cpp
Expand Up @@ -22,6 +22,7 @@
#include "qgsvectortilebasicrenderer.h"
#include "qgsvectortilelabeling.h"
#include "qgsvectortileloader.h"
#include "qgsvectortileutils.h"

#include "qgsdatasourceuri.h"

Expand All @@ -48,6 +49,12 @@ bool QgsVectorTileLayer::loadDataSource()
mSourcePath = dsUri.param( QStringLiteral( "url" ) );
if ( mSourceType == QStringLiteral( "xyz" ) )
{
if ( !QgsVectorTileUtils::checkXYZUrlTemplate( mSourcePath ) )
{
QgsDebugMsg( QStringLiteral( "Invaid format of URL for XYZ source: " ) + mSourcePath );
return false;
}

// online tiles
mSourceMinZoom = 0;
mSourceMaxZoom = 14;
Expand Down Expand Up @@ -275,8 +282,9 @@ QString QgsVectorTileLayer::decodedSource( const QString &source, const QString

QByteArray QgsVectorTileLayer::getRawTile( QgsTileXYZ tileID )
{
QgsTileMatrix tileMatrix = QgsTileMatrix::fromWebMercator( tileID.zoomLevel() );
QgsTileRange tileRange( tileID.column(), tileID.column(), tileID.row(), tileID.row() );
QList<QgsVectorTileRawData> rawTiles = QgsVectorTileLoader::blockingFetchTileRawData( mSourceType, mSourcePath, tileID.zoomLevel(), QPointF(), tileRange );
QList<QgsVectorTileRawData> rawTiles = QgsVectorTileLoader::blockingFetchTileRawData( mSourceType, mSourcePath, tileMatrix, QPointF(), tileRange );
if ( rawTiles.isEmpty() )
return QByteArray();
return rawTiles.first().data;
Expand Down
4 changes: 2 additions & 2 deletions src/core/vectortile/qgsvectortilelayerrenderer.cpp
Expand Up @@ -95,13 +95,13 @@ bool QgsVectorTileLayerRenderer::render()
{
QElapsedTimer tFetch;
tFetch.start();
rawTiles = QgsVectorTileLoader::blockingFetchTileRawData( mSourceType, mSourcePath, mTileZoom, viewCenter, mTileRange );
rawTiles = QgsVectorTileLoader::blockingFetchTileRawData( mSourceType, mSourcePath, mTileMatrix, viewCenter, mTileRange );
QgsDebugMsgLevel( QStringLiteral( "Tile fetching time: %1" ).arg( tFetch.elapsed() / 1000. ), 2 );
QgsDebugMsgLevel( QStringLiteral( "Fetched tiles: %1" ).arg( rawTiles.count() ), 2 );
}
else
{
asyncLoader.reset( new QgsVectorTileLoader( mSourcePath, mTileZoom, mTileRange, viewCenter, mFeedback.get() ) );
asyncLoader.reset( new QgsVectorTileLoader( mSourcePath, mTileMatrix, mTileRange, viewCenter, mFeedback.get() ) );
QObject::connect( asyncLoader.get(), &QgsVectorTileLoader::tileRequestFinished, [this]( const QgsVectorTileRawData & rawTile )
{
QgsDebugMsgLevel( QStringLiteral( "Got tile asynchronously: " ) + rawTile.id.toString(), 2 );
Expand Down
20 changes: 10 additions & 10 deletions src/core/vectortile/qgsvectortileloader.cpp
Expand Up @@ -23,7 +23,7 @@
#include "qgsnetworkaccessmanager.h"
#include "qgsvectortileutils.h"

QgsVectorTileLoader::QgsVectorTileLoader( const QString &uri, int zoomLevel, const QgsTileRange &range, const QPointF &viewCenter, QgsFeedback *feedback )
QgsVectorTileLoader::QgsVectorTileLoader( const QString &uri, const QgsTileMatrix &tileMatrix, const QgsTileRange &range, const QPointF &viewCenter, QgsFeedback *feedback )
: mEventLoop( new QEventLoop )
, mFeedback( feedback )
{
Expand All @@ -38,11 +38,11 @@ QgsVectorTileLoader::QgsVectorTileLoader( const QString &uri, int zoomLevel, con
}

QgsDebugMsgLevel( QStringLiteral( "Starting network loader" ), 2 );
QVector<QgsTileXYZ> tiles = QgsVectorTileUtils::tilesInRange( range, zoomLevel );
QVector<QgsTileXYZ> tiles = QgsVectorTileUtils::tilesInRange( range, tileMatrix.zoomLevel() );
QgsVectorTileUtils::sortTilesByDistanceFromCenter( tiles, viewCenter );
for ( QgsTileXYZ id : qgis::as_const( tiles ) )
{
loadFromNetworkAsync( id, uri );
loadFromNetworkAsync( id, tileMatrix, uri );
}
}

Expand Down Expand Up @@ -75,9 +75,9 @@ void QgsVectorTileLoader::downloadBlocking()
Q_ASSERT( mReplies.isEmpty() );
}

void QgsVectorTileLoader::loadFromNetworkAsync( const QgsTileXYZ &id, const QString &requestUrl )
void QgsVectorTileLoader::loadFromNetworkAsync( const QgsTileXYZ &id, const QgsTileMatrix &tileMatrix, const QString &requestUrl )
{
QString url = QgsVectorTileUtils::formatXYZUrlTemplate( requestUrl, id );
QString url = QgsVectorTileUtils::formatXYZUrlTemplate( requestUrl, id, tileMatrix );
QNetworkRequest request( url );
QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsVectorTileLoader" ) );
QgsSetRequestInitiatorId( request, id.toString() );
Expand Down Expand Up @@ -143,7 +143,7 @@ void QgsVectorTileLoader::canceled()

//////

QList<QgsVectorTileRawData> QgsVectorTileLoader::blockingFetchTileRawData( const QString &sourceType, const QString &sourcePath, int zoomLevel, const QPointF &viewCenter, const QgsTileRange &range )
QList<QgsVectorTileRawData> QgsVectorTileLoader::blockingFetchTileRawData( const QString &sourceType, const QString &sourcePath, const QgsTileMatrix &tileMatrix, const QPointF &viewCenter, const QgsTileRange &range )
{
QList<QgsVectorTileRawData> rawTiles;

Expand All @@ -155,11 +155,11 @@ QList<QgsVectorTileRawData> QgsVectorTileLoader::blockingFetchTileRawData( const
Q_ASSERT( res );
}

QVector<QgsTileXYZ> tiles = QgsVectorTileUtils::tilesInRange( range, zoomLevel );
QVector<QgsTileXYZ> tiles = QgsVectorTileUtils::tilesInRange( range, tileMatrix.zoomLevel() );
QgsVectorTileUtils::sortTilesByDistanceFromCenter( tiles, viewCenter );
for ( QgsTileXYZ id : qgis::as_const( tiles ) )
{
QByteArray rawData = isUrl ? loadFromNetwork( id, sourcePath ) : loadFromMBTiles( id, mbReader );
QByteArray rawData = isUrl ? loadFromNetwork( id, tileMatrix, sourcePath ) : loadFromMBTiles( id, mbReader );
if ( !rawData.isEmpty() )
{
rawTiles.append( QgsVectorTileRawData( id, rawData ) );
Expand All @@ -168,9 +168,9 @@ QList<QgsVectorTileRawData> QgsVectorTileLoader::blockingFetchTileRawData( const
return rawTiles;
}

QByteArray QgsVectorTileLoader::loadFromNetwork( const QgsTileXYZ &id, const QString &requestUrl )
QByteArray QgsVectorTileLoader::loadFromNetwork( const QgsTileXYZ &id, const QgsTileMatrix &tileMatrix, const QString &requestUrl )
{
QString url = QgsVectorTileUtils::formatXYZUrlTemplate( requestUrl, id );
QString url = QgsVectorTileUtils::formatXYZUrlTemplate( requestUrl, id, tileMatrix );
QNetworkRequest nr;
nr.setUrl( QUrl( url ) );
QgsBlockingNetworkRequest req;
Expand Down
8 changes: 4 additions & 4 deletions src/core/vectortile/qgsvectortileloader.h
Expand Up @@ -59,10 +59,10 @@ class QgsVectorTileLoader : public QObject
public:

//! Returns raw tile data for the specified range of tiles. Blocks the caller until all tiles are fetched.
static QList<QgsVectorTileRawData> blockingFetchTileRawData( const QString &sourceType, const QString &sourcePath, int zoomLevel, const QPointF &viewCenter, const QgsTileRange &range );
static QList<QgsVectorTileRawData> blockingFetchTileRawData( const QString &sourceType, const QString &sourcePath, const QgsTileMatrix &tileMatrix, const QPointF &viewCenter, const QgsTileRange &range );

//! Returns raw tile data for a single tile, doing a HTTP request. Block the caller until tile data are downloaded.
static QByteArray loadFromNetwork( const QgsTileXYZ &id, const QString &requestUrl );
static QByteArray loadFromNetwork( const QgsTileXYZ &id, const QgsTileMatrix &tileMatrix, const QString &requestUrl );
//! Returns raw tile data for a single tile loaded from MBTiles file
static QByteArray loadFromMBTiles( const QgsTileXYZ &id, QgsMbTiles &mbTileReader );

Expand All @@ -71,14 +71,14 @@ class QgsVectorTileLoader : public QObject
//

//! Constructs tile loader for doing asynchronous requests and starts network requests
QgsVectorTileLoader( const QString &uri, int zoomLevel, const QgsTileRange &range, const QPointF &viewCenter, QgsFeedback *feedback );
QgsVectorTileLoader( const QString &uri, const QgsTileMatrix &tileMatrix, const QgsTileRange &range, const QPointF &viewCenter, QgsFeedback *feedback );
~QgsVectorTileLoader();

//! Blocks the caller until all asynchronous requests are finished (with a success or a failure)
void downloadBlocking();

private:
void loadFromNetworkAsync( const QgsTileXYZ &id, const QString &requestUrl );
void loadFromNetworkAsync( const QgsTileXYZ &id, const QgsTileMatrix &tileMatrix, const QString &requestUrl );

private slots:
void tileReplyFinished();
Expand Down
15 changes: 7 additions & 8 deletions src/core/vectortile/qgsvectortileutils.cpp
Expand Up @@ -116,17 +116,16 @@ QgsVectorLayer *QgsVectorTileUtils::makeVectorLayerForTile( QgsVectorTileLayer *
}


QString QgsVectorTileUtils::formatXYZUrlTemplate( const QString &url, QgsTileXYZ tile )
QString QgsVectorTileUtils::formatXYZUrlTemplate( const QString &url, QgsTileXYZ tile, const QgsTileMatrix &tileMatrix )
{
QString turl( url );

turl.replace( QLatin1String( "{x}" ), QString::number( tile.column() ), Qt::CaseInsensitive );
// TODO: inverted Y axis
// if ( turl.contains( QLatin1String( "{-y}" ) ) )
// {
// turl.replace( QLatin1String( "{-y}" ), QString::number( tm.matrixHeight - tile.tileRow - 1 ), Qt::CaseInsensitive );
// }
// else
if ( turl.contains( QLatin1String( "{-y}" ) ) )
{
turl.replace( QLatin1String( "{-y}" ), QString::number( tileMatrix.matrixHeight() - tile.row() - 1 ), Qt::CaseInsensitive );
}
else
{
turl.replace( QLatin1String( "{y}" ), QString::number( tile.row() ), Qt::CaseInsensitive );
}
Expand All @@ -137,7 +136,7 @@ QString QgsVectorTileUtils::formatXYZUrlTemplate( const QString &url, QgsTileXYZ
bool QgsVectorTileUtils::checkXYZUrlTemplate( const QString &url )
{
return url.contains( QStringLiteral( "{x}" ) ) &&
url.contains( QStringLiteral( "{y}" ) ) &&
( url.contains( QStringLiteral( "{y}" ) ) || url.contains( QStringLiteral( "{-y}" ) ) ) &&
url.contains( QStringLiteral( "{z}" ) );
}

Expand Down
6 changes: 3 additions & 3 deletions src/core/vectortile/qgsvectortileutils.h
Expand Up @@ -59,9 +59,9 @@ class CORE_EXPORT QgsVectorTileUtils
static int scaleToZoomLevel( double mapScale, int sourceMinZoom, int sourceMaxZoom );
//! 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
static QString formatXYZUrlTemplate( const QString &url, QgsTileXYZ tile );
//! Checks whether the URL template string is correct (contains {x}, {y}, {z} placeholders)
//! Returns formatted tile URL string replacing {x}, {y}, {z} placeholders (or {-y} instead of {y} for TMS convention)
static QString formatXYZUrlTemplate( const QString &url, QgsTileXYZ tile, const QgsTileMatrix &tileMatrix );
//! Checks whether the URL template string is correct (contains {x}, {y} / {-y}, {z} placeholders)
static bool checkXYZUrlTemplate( const QString &url );
};

Expand Down
6 changes: 3 additions & 3 deletions src/core/vectortile/qgsvectortilewriter.cpp
Expand Up @@ -196,7 +196,7 @@ bool QgsVectorTileWriter::writeTiles( QgsFeedback *feedback )

if ( sourceType == QStringLiteral( "xyz" ) )
{
if ( !writeTileFileXYZ( sourcePath, tileID, tileData ) )
if ( !writeTileFileXYZ( sourcePath, tileID, tileMatrix, tileData ) )
return false; // error message already set
}
else // mbtiles
Expand Down Expand Up @@ -235,9 +235,9 @@ QgsRectangle QgsVectorTileWriter::fullExtent() const
return extent;
}

bool QgsVectorTileWriter::writeTileFileXYZ( const QString &sourcePath, QgsTileXYZ tileID, const QByteArray &tileData )
bool QgsVectorTileWriter::writeTileFileXYZ( const QString &sourcePath, QgsTileXYZ tileID, const QgsTileMatrix &tileMatrix, const QByteArray &tileData )
{
QString filePath = QgsVectorTileUtils::formatXYZUrlTemplate( sourcePath, tileID );
QString filePath = QgsVectorTileUtils::formatXYZUrlTemplate( sourcePath, tileID, tileMatrix );

// make dirs if needed
QFileInfo fi( filePath );
Expand Down
3 changes: 2 additions & 1 deletion src/core/vectortile/qgsvectortilewriter.h
Expand Up @@ -21,6 +21,7 @@
#include "qgscoordinatetransformcontext.h"

class QgsFeedback;
class QgsTileMatrix;
class QgsTileXYZ;
class QgsVectorLayer;

Expand Down Expand Up @@ -163,7 +164,7 @@ class CORE_EXPORT QgsVectorTileWriter
QgsRectangle fullExtent() const;

private:
bool writeTileFileXYZ( const QString &sourcePath, QgsTileXYZ tileID, const QByteArray &tileData );
bool writeTileFileXYZ( const QString &sourcePath, QgsTileXYZ tileID, const QgsTileMatrix &tileMatrix, const QByteArray &tileData );
QString mbtilesJsonSchema();

private:
Expand Down

0 comments on commit a516008

Please sign in to comment.