Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Handle VTPK tile data with zero sizes by falling back to lower
zoom levels for the tile's data

It seems that every VTPK behaves a bit like an indexed VTPK
where higher zoom level tiles may be replaced with lower
zoom level tiles. This new special handling is required
for non-indexed VTPKs where the high zoom level tile data
is empty, but there's not explicit tilemap present to
advise of us this situation in advance.

Fixes #52872
  • Loading branch information
nyalldawson committed May 22, 2023
1 parent f5f772f commit 327a069
Show file tree
Hide file tree
Showing 4 changed files with 29 additions and 9 deletions.
5 changes: 5 additions & 0 deletions python/core/auto_generated/vectortile/qgsvtpktiles.sip.in
Expand Up @@ -94,6 +94,11 @@ Returns bounding box from metadata, given in the tiles :py:func:`~QgsVtpkTiles.c
QByteArray tileData( int z, int x, int y );
%Docstring
Returns the raw tile data for the matching tile.

Returns a null byte array if the requested tile does not exist.

Will return an empty byte array (as opposed to a null byte array) if the tile
exists but has a zero size.
%End

private:
Expand Down
7 changes: 6 additions & 1 deletion src/core/vectortile/qgsvtpktiles.cpp
Expand Up @@ -473,7 +473,12 @@ QByteArray QgsVtpkTiles::tileData( int z, int x, int y )
const std::size_t tileOffset = indexValue % ( 2ULL << 39 );
const std::size_t tileSize = static_cast< std::size_t>( std::floor( indexValue / ( 2ULL << 39 ) ) );
// bundle is a gzip file;
if ( tileSize > 0 && !QgsZipUtils::decodeGzip( buf.get() + tileOffset, tileSize, res ) )
if ( tileSize == 0 )
{
// construct a non-null bytearray
res = QByteArray( "" );
}
else if ( !QgsZipUtils::decodeGzip( buf.get() + tileOffset, tileSize, res ) )
{
QgsMessageLog::logMessage( QObject::tr( "Error extracting bundle contents as gzip: %1" ).arg( fileName ) );
}
Expand Down
5 changes: 5 additions & 0 deletions src/core/vectortile/qgsvtpktiles.h
Expand Up @@ -107,6 +107,11 @@ class CORE_EXPORT QgsVtpkTiles

/**
* Returns the raw tile data for the matching tile.
*
* Returns a null byte array if the requested tile does not exist.
*
* Will return an empty byte array (as opposed to a null byte array) if the tile
* exists but has a zero size.
*/
QByteArray tileData( int z, int x, int y );

Expand Down
21 changes: 13 additions & 8 deletions src/core/vectortile/qgsvtpkvectortiledataprovider.cpp
Expand Up @@ -198,7 +198,9 @@ QList<QgsVectorTileRawData> QgsVtpkVectorTileDataProvider::readTiles( const QgsT
reader.open();

QList<QgsVectorTileRawData> rawTiles;
QSet< QgsTileXYZ > fetchedTiles;
rawTiles.reserve( tiles.size() );
fetchedTiles.reserve( tiles.size() );
for ( QgsTileXYZ id : std::as_const( tiles ) )
{
if ( feedback && feedback->isCanceled() )
Expand All @@ -208,6 +210,7 @@ QList<QgsVectorTileRawData> QgsVtpkVectorTileDataProvider::readTiles( const QgsT
if ( !rawData.data.isEmpty() )
{
rawTiles.append( rawData );
fetchedTiles.insert( rawData.id );
}
}
return rawTiles;
Expand All @@ -232,20 +235,22 @@ QString QgsVtpkVectorTileDataProvider::htmlMetadata() const

QgsVectorTileRawData QgsVtpkVectorTileDataProvider::loadFromVtpk( QgsVtpkTiles &vtpkTileReader, const QgsTileXYZ &id, QgsFeedback * )
{
const QByteArray tileData = vtpkTileReader.tileData( id.zoomLevel(), id.column(), id.row() );
if ( tileData.isEmpty() )
QgsTileXYZ requestedTile = id;
QByteArray tileData = vtpkTileReader.tileData( requestedTile.zoomLevel(), requestedTile.column(), requestedTile.row() );
// I **think** here ESRI software will detect a zero size tile and automatically fallback to lower zoom level tiles
// I.e. they treat EVERY vtpk a bit like an indexed VTPK, but without the up-front tilemap information.
// See https://github.com/qgis/QGIS/issues/52872
while ( !tileData.isNull() && tileData.size() == 0 && requestedTile.zoomLevel() > vtpkTileReader.matrixSet().minimumZoom() )
{
// TODO -- I think here ESRI software will detect a zero size tile and automatically fallback to lower zoom level tiles
// I.e. they treat EVERY vtpk a bit like an indexed VTPK, but without the up-front tilemap information.
// See https://github.com/qgis/QGIS/issues/52872

return QByteArray();
requestedTile = QgsTileXYZ( requestedTile.column() / 2, requestedTile.row() / 2, requestedTile.zoomLevel() - 1 );
tileData = vtpkTileReader.tileData( requestedTile.zoomLevel(), requestedTile.column(), requestedTile.row() );
}

if ( tileData.isEmpty() )
if ( tileData.isNull() )
return QgsVectorTileRawData();

QgsVectorTileRawData res( id, tileData );
res.tileGeometryId = requestedTile;
return res;
}

Expand Down

0 comments on commit 327a069

Please sign in to comment.