Skip to content

Commit

Permalink
Use GDAL API to determine multi-layer extensions on GDAL 3.4+
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Aug 7, 2021
1 parent bdc5b84 commit c986c1a
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 27 deletions.
2 changes: 1 addition & 1 deletion src/core/providers/gdal/qgsgdalprovider.cpp
Expand Up @@ -3611,7 +3611,7 @@ QList<QgsProviderSublayerDetails> QgsGdalProviderMetadata::querySublayers( const
details.setProviderKey( QStringLiteral( "gdal" ) );
details.setUri( uri );
details.setName( QgsProviderUtils::suggestLayerNameFromFilePath( path ) );
if ( QgsGdalUtils::SUPPORTED_DB_LAYERS_EXTENSIONS.contains( suffix ) )
if ( QgsGdalUtils::multiLayerFileExtensions().contains( suffix ) )
{
// uri may contain sublayers, but query flags prevent us from examining them
details.setSkippedContainerScan( true );
Expand Down
2 changes: 1 addition & 1 deletion src/core/providers/ogr/qgsogrprovidermetadata.cpp
Expand Up @@ -1146,7 +1146,7 @@ QList<QgsProviderSublayerDetails> QgsOgrProviderMetadata::querySublayers( const
details.setProviderKey( QStringLiteral( "ogr" ) );
details.setUri( uri );
details.setName( QgsProviderUtils::suggestLayerNameFromFilePath( path ) );
if ( QgsGdalUtils::SUPPORTED_DB_LAYERS_EXTENSIONS.contains( suffix ) )
if ( QgsGdalUtils::multiLayerFileExtensions().contains( suffix ) )
{
// uri may contain sublayers, but query flags prevent us from examining them
details.setSkippedContainerScan( true );
Expand Down
107 changes: 86 additions & 21 deletions src/core/qgsgdalutils.cpp
Expand Up @@ -22,32 +22,13 @@
#include "gdal.h"
#include "gdalwarper.h"
#include "cpl_string.h"
#include "qgsapplication.h"

#include <QNetworkProxy>
#include <QString>
#include <QImage>
#include <QFileInfo>

// File extensions for formats supported by GDAL which may contain multiple layers
// and should be treated as a potential layer container
const QStringList QgsGdalUtils::SUPPORTED_DB_LAYERS_EXTENSIONS
{
QStringLiteral( "gpkg" ),
QStringLiteral( "sqlite" ),
QStringLiteral( "db" ),
QStringLiteral( "gdb" ),
QStringLiteral( "kml" ),
QStringLiteral( "kmz" ),
QStringLiteral( "osm" ),
QStringLiteral( "mdb" ),
QStringLiteral( "accdb" ),
QStringLiteral( "xls" ),
QStringLiteral( "xlsx" ),
QStringLiteral( "gpx" ),
QStringLiteral( "pdf" ),
QStringLiteral( "pbf" ),
QStringLiteral( "nc" ),
QStringLiteral( "shp.zip" ) };
#include <mutex>

bool QgsGdalUtils::supportsRasterCreate( GDALDriverH driver )
{
Expand Down Expand Up @@ -564,6 +545,90 @@ bool QgsGdalUtils::pathIsCheapToOpen( const QString &path, int smallFileSizeLimi
return false;
}

QStringList QgsGdalUtils::multiLayerFileExtensions()
{
#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,4,0)
// get supported extensions
static std::once_flag initialized;
static QStringList SUPPORTED_DB_LAYERS_EXTENSIONS;
std::call_once( initialized, [ = ]
{
// iterate through all of the supported drivers, adding the corresponding file extensions for
// types which advertise multilayer support
GDALDriverH driver = nullptr;

QSet< QString > extensions;

for ( int i = 0; i < GDALGetDriverCount(); ++i )
{
driver = GDALGetDriver( i );
if ( !driver )
{
QgsLogger::warning( "unable to get driver " + QString::number( i ) );
continue;
}

bool isMultiLayer = false;
if ( QString( GDALGetMetadataItem( driver, GDAL_DCAP_RASTER, nullptr ) ) == QLatin1String( "YES" ) )
{
if ( GDALGetMetadataItem( driver, GDAL_DMD_SUBDATASETS, nullptr ) != nullptr )
{
isMultiLayer = true;
}
}
if ( !isMultiLayer && QString( GDALGetMetadataItem( driver, GDAL_DCAP_VECTOR, nullptr ) ) == QLatin1String( "YES" ) )
{
if ( GDALGetMetadataItem( driver, GDAL_DCAP_MULTIPLE_VECTOR_LAYERS, nullptr ) != nullptr )
{
isMultiLayer = true;
}
}

if ( !isMultiLayer )
continue;

const QString driverExtensions = GDALGetMetadataItem( driver, GDAL_DMD_EXTENSIONS, "" );
if ( driverExtensions.isEmpty() )
continue;

#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
const QStringList splitExtensions = driverExtensions.split( ' ', QString::SkipEmptyParts );
#else
const QStringList splitExtensions = driverExtensions.split( ' ', Qt::SkipEmptyParts );
#endif

for ( const QString &ext : splitExtensions )
extensions.insert( ext );
}

SUPPORTED_DB_LAYERS_EXTENSIONS = qgis::setToList( extensions );
} );
return SUPPORTED_DB_LAYERS_EXTENSIONS;

#else
static const QStringList SUPPORTED_DB_LAYERS_EXTENSIONS
{
QStringLiteral( "gpkg" ),
QStringLiteral( "sqlite" ),
QStringLiteral( "db" ),
QStringLiteral( "gdb" ),
QStringLiteral( "kml" ),
QStringLiteral( "kmz" ),
QStringLiteral( "osm" ),
QStringLiteral( "mdb" ),
QStringLiteral( "accdb" ),
QStringLiteral( "xls" ),
QStringLiteral( "xlsx" ),
QStringLiteral( "ods" ),
QStringLiteral( "gpx" ),
QStringLiteral( "pdf" ),
QStringLiteral( "pbf" ),
QStringLiteral( "nc" ),
QStringLiteral( "shp.zip" ) };
return SUPPORTED_DB_LAYERS_EXTENSIONS;
#endif
}

bool QgsGdalUtils::vrtMatchesLayerType( const QString &vrtPath, QgsMapLayerType type )
{
CPLPushErrorHandler( CPLQuietErrorHandler );
Expand Down
8 changes: 4 additions & 4 deletions src/core/qgsgdalutils.h
Expand Up @@ -148,13 +148,13 @@ class CORE_EXPORT QgsGdalUtils
*/
static bool pathIsCheapToOpen( const QString &path, int smallFileSizeLimit = 50000 );


/**
* File extensions for formats supported by GDAL which may contain multiple layers
* and should be treated as a potential layer container.
* Returns a list of file extensions which potentially contain multiple layers representing
* GDAL raster or vector layers.
*
* \since QGIS 3.22
*/
static const QStringList SUPPORTED_DB_LAYERS_EXTENSIONS;
static QStringList multiLayerFileExtensions();

/**
* Returns TRUE if the VRT file at the specified path is a VRT matching
Expand Down
19 changes: 19 additions & 0 deletions tests/src/core/testqgsgdalutils.cpp
Expand Up @@ -43,6 +43,7 @@ class TestQgsGdalUtils: public QObject
void testResampleImageToImage();
void testPathIsCheapToOpen();
void testVrtMatchesLayerType();
void testMultilayerExtensions();

private:

Expand Down Expand Up @@ -294,6 +295,24 @@ void TestQgsGdalUtils::testVrtMatchesLayerType()
QVERIFY( QgsGdalUtils::vrtMatchesLayerType( QStringLiteral( TEST_DATA_DIR ) + "/vector_vrt.vrt", QgsMapLayerType::VectorLayer ) );
}

void TestQgsGdalUtils::testMultilayerExtensions()
{
const QStringList extensions = QgsGdalUtils::multiLayerFileExtensions();
QVERIFY( extensions.contains( QStringLiteral( "gpkg" ) ) );
QVERIFY( extensions.contains( QStringLiteral( "sqlite" ) ) );
QVERIFY( extensions.contains( QStringLiteral( "db" ) ) );
QVERIFY( extensions.contains( QStringLiteral( "kml" ) ) );
QVERIFY( extensions.contains( QStringLiteral( "ods" ) ) );
QVERIFY( extensions.contains( QStringLiteral( "osm" ) ) );
QVERIFY( extensions.contains( QStringLiteral( "mdb" ) ) );
QVERIFY( extensions.contains( QStringLiteral( "xls" ) ) );
QVERIFY( extensions.contains( QStringLiteral( "xlsx" ) ) );
QVERIFY( extensions.contains( QStringLiteral( "gpx" ) ) );
QVERIFY( extensions.contains( QStringLiteral( "pdf" ) ) );
QVERIFY( extensions.contains( QStringLiteral( "nc" ) ) );
QVERIFY( extensions.contains( QStringLiteral( "gdb" ) ) );
}

double TestQgsGdalUtils::identify( GDALDatasetH dataset, int band, int px, int py )
{
GDALRasterBandH hBand = GDALGetRasterBand( dataset, band );
Expand Down

0 comments on commit c986c1a

Please sign in to comment.