Skip to content

Commit

Permalink
Move methods for handling raster mbtile files into QgsWmsProviderMeta…
Browse files Browse the repository at this point in the history
…data

Implement all the reuiqred methods for delegating this handling
to the provider
  • Loading branch information
nyalldawson committed Apr 4, 2023
1 parent 4810e0c commit 3c98201
Show file tree
Hide file tree
Showing 5 changed files with 209 additions and 10 deletions.
6 changes: 4 additions & 2 deletions src/core/providers/qgsproviderutils.cpp
Expand Up @@ -38,9 +38,11 @@ bool QgsProviderUtils::sublayerDetailsAreIncomplete( const QList<QgsProviderSubl
break;

case Qgis::LayerType::VectorTile:
return sublayer.skippedContainerScan();

case Qgis::LayerType::Raster:
if ( sublayer.skippedContainerScan() )
return true;
break;

case Qgis::LayerType::Plugin:
case Qgis::LayerType::Mesh:
case Qgis::LayerType::Annotation:
Expand Down
100 changes: 98 additions & 2 deletions src/providers/wms/qgswmsprovider.cpp
Expand Up @@ -52,6 +52,8 @@
#include "qgsproviderregistry.h"
#include "qgsruntimeprofiler.h"
#include "qgstiledownloadmanager.h"
#include "qgsproviderutils.h"
#include "qgsprovidersublayerdetails.h"

#include <QNetworkRequest>
#include <QNetworkReply>
Expand Down Expand Up @@ -4418,6 +4420,11 @@ QgsWmsProvider *QgsWmsProviderMetadata::createProvider( const QString &uri, cons
return new QgsWmsProvider( uri, options );
}

QgsProviderMetadata::ProviderCapabilities QgsWmsProviderMetadata::providerCapabilities() const
{
return FileBasedUris;
}

// -----------------

QgsWmsImageDownloadHandler::QgsWmsImageDownloadHandler( const QString &providerUri, const QUrl &url, const QgsWmsAuthorization &auth, QImage *image, QgsRasterBlockFeedback *feedback )
Expand Down Expand Up @@ -5160,6 +5167,13 @@ QIcon QgsWmsProviderMetadata::icon() const
return QgsApplication::getThemeIcon( QStringLiteral( "mIconWms.svg" ) );
}

QgsProviderMetadata::ProviderMetadataCapabilities QgsWmsProviderMetadata::capabilities() const
{
return ProviderMetadataCapability::LayerTypesForUri
| ProviderMetadataCapability::PriorityForUri
| ProviderMetadataCapability::QuerySublayers;
}

QList<QgsDataItemProvider *> QgsWmsProviderMetadata::dataItemProviders() const
{
QList<QgsDataItemProvider *> providers;
Expand All @@ -5174,9 +5188,9 @@ QList<QgsDataItemProvider *> QgsWmsProviderMetadata::dataItemProviders() const
QVariantMap QgsWmsProviderMetadata::decodeUri( const QString &uri ) const
{
const QUrlQuery query { uri };
const auto constItems { query.queryItems() };
const QList<QPair<QString, QString> > constItems { query.queryItems() };
QVariantMap decoded;
for ( const auto &item : constItems )
for ( const QPair<QString, QString> &item : constItems )
{
if ( item.first == QLatin1String( "url" ) )
{
Expand All @@ -5185,6 +5199,10 @@ QVariantMap QgsWmsProviderMetadata::decodeUri( const QString &uri ) const
{
decoded[ QStringLiteral( "path" ) ] = url.toLocalFile();
}
else if ( QFileInfo( item.second ).isFile() )
{
decoded[ QStringLiteral( "path" ) ] = item.second;
}
else
{
decoded[ item.first ] = item.second;
Expand Down Expand Up @@ -5217,6 +5235,84 @@ QString QgsWmsProviderMetadata::encodeUri( const QVariantMap &parts ) const
return query.toString();
}

QList<QgsProviderSublayerDetails> QgsWmsProviderMetadata::querySublayers( const QString &uri, Qgis::SublayerQueryFlags flags, QgsFeedback * ) const
{
QString fileName;
const QFileInfo fi( uri );
if ( fi.isFile() )
{
fileName = uri;
}
else
{
const QVariantMap parts = decodeUri( uri );
fileName = parts.value( QStringLiteral( "path" ) ).toString();
}

if ( fileName.isEmpty() )
return {};

if ( QFileInfo( fileName ).suffix().compare( QLatin1String( "mbtiles" ), Qt::CaseInsensitive ) == 0 )
{
QVariantMap parts;
parts.insert( QStringLiteral( "path" ), fileName );
parts.insert( QStringLiteral( "type" ), QStringLiteral( "mbtiles" ) );

if ( flags & Qgis::SublayerQueryFlag::FastScan )
{
// fast scan -- assume raster tiles are available
QgsProviderSublayerDetails details;
details.setUri( encodeUri( parts ) );
details.setProviderKey( key() );
details.setType( Qgis::LayerType::Raster );
details.setName( QgsProviderUtils::suggestLayerNameFromFilePath( fileName ) );
details.setSkippedContainerScan( true );
return {details};
}
else
{
// slower scan, check actual mbtiles format
QgsMbTiles reader( fileName );
if ( reader.open() )
{
if ( reader.metadataValue( "format" ) != QLatin1String( "pbf" ) )
{
QgsProviderSublayerDetails details;
details.setUri( encodeUri( parts ) );
details.setProviderKey( key() );
details.setType( Qgis::LayerType::Raster );
details.setName( QgsProviderUtils::suggestLayerNameFromFilePath( fileName ) );
return {details};
}
}
}
}
return {};
}

int QgsWmsProviderMetadata::priorityForUri( const QString &uri ) const
{
if ( validLayerTypesForUri( uri ).contains( Qgis::LayerType::Raster ) )
return 100;

return 0;
}

QList<Qgis::LayerType> QgsWmsProviderMetadata::validLayerTypesForUri( const QString &uri ) const
{
const QFileInfo fi( uri );
if ( fi.isFile() && fi.suffix().compare( QLatin1String( "mbtiles" ), Qt::CaseInsensitive ) == 0 )
{
return { Qgis::LayerType::Raster };
}

const QVariantMap parts = decodeUri( uri );
if ( parts.value( QStringLiteral( "path" ) ).toString().endsWith( ".mbtiles", Qt::CaseSensitivity::CaseInsensitive ) )
return { Qgis::LayerType::Raster };

return {};
}

QString QgsWmsProviderMetadata::absoluteToRelativeUri( const QString &src, const QgsReadWriteContext &context ) const
{
// handle relative paths to XYZ tiles
Expand Down
9 changes: 9 additions & 0 deletions src/providers/wms/qgswmsprovider.h
Expand Up @@ -729,10 +729,19 @@ class QgsWmsProviderMetadata final: public QgsProviderMetadata
public:
QgsWmsProviderMetadata();
QIcon icon() const override;
QgsProviderMetadata::ProviderMetadataCapabilities capabilities() const override;

QgsWmsProvider *createProvider( const QString &uri, const QgsDataProvider::ProviderOptions &options, QgsDataProvider::ReadFlags flags = QgsDataProvider::ReadFlags() ) override;
ProviderCapabilities providerCapabilities() const override;

QList<QgsDataItemProvider *> dataItemProviders() const override;
QVariantMap decodeUri( const QString &uri ) const override;
QString encodeUri( const QVariantMap &parts ) const override;

QList< QgsProviderSublayerDetails > querySublayers( const QString &uri, Qgis::SublayerQueryFlags flags = Qgis::SublayerQueryFlags(), QgsFeedback *feedback = nullptr ) const override;
int priorityForUri( const QString &uri ) const override;
QList< Qgis::LayerType > validLayerTypesForUri( const QString &uri ) const override;

QString absoluteToRelativeUri( const QString &uri, const QgsReadWriteContext &context ) const override;
QString relativeToAbsoluteUri( const QString &uri, const QgsReadWriteContext &context ) const override;
QList< Qgis::LayerType > supportedLayerTypes() const override;
Expand Down
16 changes: 10 additions & 6 deletions tests/src/core/testqgsvectortilelayer.cpp
Expand Up @@ -290,14 +290,18 @@ void TestQgsVectorTileLayer::testMbtilesProviderMetadata()

// test that mbtilesvectortiles provider is the preferred provider for vector tile mbtiles files
QList<QgsProviderRegistry::ProviderCandidateDetails> candidates = QgsProviderRegistry::instance()->preferredProvidersForUri( QStringLiteral( "type=mbtiles&url=%1/vector_tile/mbtiles_vt.mbtiles" ).arg( TEST_DATA_DIR ) );
QCOMPARE( candidates.size(), 1 );
QCOMPARE( candidates.at( 0 ).metadata()->key(), QStringLiteral( "mbtilesvectortiles" ) );
QCOMPARE( candidates.at( 0 ).layerTypes(), QList< Qgis::LayerType >() << Qgis::LayerType::VectorTile );
// wms provider also reports handling this url
int vtProviderIndex = candidates.at( 0 ).metadata()->key() == QLatin1String( "mbtilesvectortiles" ) ? 0 : 1;
QCOMPARE( candidates.size(), 2 );
QCOMPARE( candidates.at( vtProviderIndex ).metadata()->key(), QStringLiteral( "mbtilesvectortiles" ) );
QCOMPARE( candidates.at( vtProviderIndex ).layerTypes(), QList< Qgis::LayerType >() << Qgis::LayerType::VectorTile );

candidates = QgsProviderRegistry::instance()->preferredProvidersForUri( QStringLiteral( "%1/vector_tile/mbtiles_vt.mbtiles" ).arg( TEST_DATA_DIR ) );
QCOMPARE( candidates.size(), 1 );
QCOMPARE( candidates.at( 0 ).metadata()->key(), QStringLiteral( "mbtilesvectortiles" ) );
QCOMPARE( candidates.at( 0 ).layerTypes(), QList< Qgis::LayerType >() << Qgis::LayerType::VectorTile );
// wms provider also reports handling this url
QCOMPARE( candidates.size(), 2 );
vtProviderIndex = candidates.at( 0 ).metadata()->key() == QLatin1String( "mbtilesvectortiles" ) ? 0 : 1;
QCOMPARE( candidates.at( vtProviderIndex ).metadata()->key(), QStringLiteral( "mbtilesvectortiles" ) );
QCOMPARE( candidates.at( vtProviderIndex ).layerTypes(), QList< Qgis::LayerType >() << Qgis::LayerType::VectorTile );

QCOMPARE( vectorTileMetadata->filters( Qgis::FileFilterType::VectorTile ), QStringLiteral( "Mbtiles Vector Tiles (*.mbtiles *.MBTILES)" ) );
QCOMPARE( vectorTileMetadata->filters( Qgis::FileFilterType::PointCloud ), QString() );
Expand Down
88 changes: 88 additions & 0 deletions tests/src/providers/testqgswmsprovider.cpp
Expand Up @@ -29,6 +29,8 @@
#include "qgssinglebandgrayrenderer.h"
#include "qgsrasterlayer.h"
#include "qgshillshaderenderer.h"
#include "qgsproviderutils.h"
#include "qgsprovidersublayerdetails.h"

/**
* \ingroup UnitTests
Expand Down Expand Up @@ -184,6 +186,92 @@ class TestQgsWmsProvider: public QgsTest
QCOMPARE( value, 1167617.375 );
}

void testMbtilesProviderMetadata()
{
QgsProviderMetadata *wmsMetadata = QgsProviderRegistry::instance()->providerMetadata( "wms" );
QVERIFY( wmsMetadata );

// not mbtile uris
QCOMPARE( wmsMetadata->priorityForUri( QString() ), 0 );
QCOMPARE( wmsMetadata->validLayerTypesForUri( QString() ), {} );

QCOMPARE( wmsMetadata->priorityForUri( QStringLiteral( TEST_DATA_DIR ) + QStringLiteral( "/points.shp" ) ), 0 );
QVERIFY( wmsMetadata->validLayerTypesForUri( QStringLiteral( TEST_DATA_DIR ) + QStringLiteral( "/points.shp" ) ).isEmpty() );
QVERIFY( wmsMetadata->querySublayers( QStringLiteral( TEST_DATA_DIR ) + QStringLiteral( "/points.shp" ) ).isEmpty() );

QCOMPARE( wmsMetadata->priorityForUri( QStringLiteral( "type=mbtiles&url=%1/points.shp" ).arg( TEST_DATA_DIR ) ), 0 );
QVERIFY( wmsMetadata->validLayerTypesForUri( QStringLiteral( "type=mbtiles&url=%1/points.shp" ).arg( TEST_DATA_DIR ) ).isEmpty() );
QVERIFY( wmsMetadata->querySublayers( QStringLiteral( "type=mbtiles&url=%1/points.shp" ).arg( TEST_DATA_DIR ) ).isEmpty() );

// mbtile uris
QCOMPARE( wmsMetadata->priorityForUri( QStringLiteral( "%1/isle_of_man.mbtiles" ).arg( TEST_DATA_DIR ) ), 100 );
QCOMPARE( wmsMetadata->validLayerTypesForUri( QStringLiteral( "%1/isle_of_man.mbtiles" ).arg( TEST_DATA_DIR ) ), {Qgis::LayerType::Raster} );

QCOMPARE( wmsMetadata->priorityForUri( QStringLiteral( "type=mbtiles&url=%1/isle_of_man.mbtiles" ).arg( TEST_DATA_DIR ) ), 100 );
QCOMPARE( wmsMetadata->validLayerTypesForUri( QStringLiteral( "type=mbtiles&url=%1/isle_of_man.mbtiles" ).arg( TEST_DATA_DIR ) ), {Qgis::LayerType::Raster} );

// query sublayers
QList< QgsProviderSublayerDetails > sublayers = wmsMetadata->querySublayers( QStringLiteral( "%1/isle_of_man.mbtiles" ).arg( TEST_DATA_DIR ) );
QCOMPARE( sublayers.size(), 1 );
QCOMPARE( sublayers.at( 0 ).providerKey(), QStringLiteral( "wms" ) );
QCOMPARE( sublayers.at( 0 ).name(), QStringLiteral( "isle_of_man" ) );
QCOMPARE( sublayers.at( 0 ).uri(), QStringLiteral( "url=file://%1/isle_of_man.mbtiles&type=mbtiles" ).arg( TEST_DATA_DIR ) );
QCOMPARE( sublayers.at( 0 ).type(), Qgis::LayerType::Raster );
QVERIFY( !sublayers.at( 0 ).skippedContainerScan() );
QVERIFY( !QgsProviderUtils::sublayerDetailsAreIncomplete( sublayers ) );

sublayers = wmsMetadata->querySublayers( QStringLiteral( "type=mbtiles&url=file://%1/isle_of_man.mbtiles" ).arg( TEST_DATA_DIR ) );
QCOMPARE( sublayers.size(), 1 );
QCOMPARE( sublayers.at( 0 ).providerKey(), QStringLiteral( "wms" ) );
QCOMPARE( sublayers.at( 0 ).name(), QStringLiteral( "isle_of_man" ) );
QCOMPARE( sublayers.at( 0 ).uri(), QStringLiteral( "url=file://%1/isle_of_man.mbtiles&type=mbtiles" ).arg( TEST_DATA_DIR ) );
QCOMPARE( sublayers.at( 0 ).type(), Qgis::LayerType::Raster );
QVERIFY( !sublayers.at( 0 ).skippedContainerScan() );

// fast scan flag
sublayers = wmsMetadata->querySublayers( QStringLiteral( "%1/isle_of_man.mbtiles" ).arg( TEST_DATA_DIR ), Qgis::SublayerQueryFlag::FastScan );
QCOMPARE( sublayers.size(), 1 );
QCOMPARE( sublayers.at( 0 ).providerKey(), QStringLiteral( "wms" ) );
QCOMPARE( sublayers.at( 0 ).name(), QStringLiteral( "isle_of_man" ) );
QCOMPARE( sublayers.at( 0 ).uri(), QStringLiteral( "url=file://%1/isle_of_man.mbtiles&type=mbtiles" ).arg( TEST_DATA_DIR ) );
QCOMPARE( sublayers.at( 0 ).type(), Qgis::LayerType::Raster );
QVERIFY( sublayers.at( 0 ).skippedContainerScan() );
QVERIFY( QgsProviderUtils::sublayerDetailsAreIncomplete( sublayers ) );

sublayers = wmsMetadata->querySublayers( QStringLiteral( "type=mbtiles&url=%1/isle_of_man.mbtiles" ).arg( TEST_DATA_DIR ), Qgis::SublayerQueryFlag::FastScan );
QCOMPARE( sublayers.size(), 1 );
QCOMPARE( sublayers.at( 0 ).providerKey(), QStringLiteral( "wms" ) );
QCOMPARE( sublayers.at( 0 ).name(), QStringLiteral( "isle_of_man" ) );
QCOMPARE( sublayers.at( 0 ).uri(), QStringLiteral( "url=file://%1/isle_of_man.mbtiles&type=mbtiles" ).arg( TEST_DATA_DIR ) );
QCOMPARE( sublayers.at( 0 ).type(), Qgis::LayerType::Raster );
QVERIFY( sublayers.at( 0 ).skippedContainerScan() );

// fast scan mode means that any mbtile file will be reported, including those with only vector tiles
// (we are skipping a potentially expensive db open and format check)
sublayers = wmsMetadata->querySublayers( QStringLiteral( "%1/vector_tile/mbtiles_vt.mbtiles" ).arg( TEST_DATA_DIR ), Qgis::SublayerQueryFlag::FastScan );
QCOMPARE( sublayers.size(), 1 );
QCOMPARE( sublayers.at( 0 ).providerKey(), QStringLiteral( "wms" ) );
QCOMPARE( sublayers.at( 0 ).name(), QStringLiteral( "mbtiles_vt" ) );
QCOMPARE( sublayers.at( 0 ).uri(), QStringLiteral( "url=file://%1/vector_tile/mbtiles_vt.mbtiles&type=mbtiles" ).arg( TEST_DATA_DIR ) );
QCOMPARE( sublayers.at( 0 ).type(), Qgis::LayerType::Raster );
QVERIFY( sublayers.at( 0 ).skippedContainerScan() );

// test that wms provider is the preferred provider for raster mbtiles files
QList<QgsProviderRegistry::ProviderCandidateDetails> candidates = QgsProviderRegistry::instance()->preferredProvidersForUri( QStringLiteral( "type=mbtiles&url=%1/isle_of_man.mbtiles" ).arg( TEST_DATA_DIR ) );
QCOMPARE( candidates.size(), 2 );

int candidateIndex = candidates.at( 0 ).metadata()->key() == QLatin1String( "wms" ) ? 0 : 1;
QCOMPARE( candidates.at( candidateIndex ).metadata()->key(), QStringLiteral( "wms" ) );
QCOMPARE( candidates.at( candidateIndex ).layerTypes(), QList< Qgis::LayerType >() << Qgis::LayerType::Raster );

candidates = QgsProviderRegistry::instance()->preferredProvidersForUri( QStringLiteral( "%1/isle_of_man.mbtiles" ).arg( TEST_DATA_DIR ) );
// mbtiles vector tile provider also reports handling this url
QCOMPARE( candidates.size(), 2 );
candidateIndex = candidates.at( 0 ).metadata()->key() == QLatin1String( "wms" ) ? 0 : 1;
QCOMPARE( candidates.at( candidateIndex ).metadata()->key(), QStringLiteral( "wms" ) );
QCOMPARE( candidates.at( candidateIndex ).layerTypes(), QList< Qgis::LayerType >() << Qgis::LayerType::Raster );
}

void testDpiDependentData()
{
QString dataDir( TEST_DATA_DIR );
Expand Down

0 comments on commit 3c98201

Please sign in to comment.