Skip to content

Commit

Permalink
GDAL provider: restore ability to access subdatasets of compressed da…
Browse files Browse the repository at this point in the history
…ta formats

A clean up of sublayer handling code (#44085) broke a group of special
data formats that were already handled by GDAL directly (e.g. SENTINEL 2 SAFE).
This PR reorders priorities of handling compressed dataset by first trying
a direct GDAL approach and only if it fails looking inside a compressed file.
  • Loading branch information
marisn authored and nyalldawson committed May 24, 2022
1 parent 77c926f commit d62f0fd
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 49 deletions.
107 changes: 58 additions & 49 deletions src/core/providers/gdal/qgsgdalprovider.cpp
Expand Up @@ -1805,7 +1805,9 @@ QList<QgsProviderSublayerDetails> QgsGdalProvider::sublayerDetails( GDALDatasetH
// update original uri parts with this layername and path -- this ensures that other uri components
// like open options are preserved for the sublayer uris
uriParts.insert( QStringLiteral( "layerName" ), layerUriParts.value( QStringLiteral( "layerName" ) ) );
uriParts.insert( QStringLiteral( "vsiPrefix" ), layerUriParts.value( QStringLiteral( "vsiPrefix" ) ) );
uriParts.insert( QStringLiteral( "path" ), layerUriParts.value( QStringLiteral( "path" ) ) );
uriParts.insert( QStringLiteral( "vsiSuffix" ), layerUriParts.value( QStringLiteral( "vsiSuffix" ) ) );
details.setUri( encodeGdalUri( uriParts ) );

res << details;
Expand Down Expand Up @@ -3689,6 +3691,7 @@ QList<QgsProviderSublayerDetails> QgsGdalProviderMetadata::querySublayers( const
QgsGdalProviderBase::registerGdalDrivers();

QString gdalUri = QgsGdalProvider::expandAuthConfig( uri );
QString npGdalUri = gdalUri;

QVariantMap uriParts = decodeUri( gdalUri );

Expand Down Expand Up @@ -3776,6 +3779,60 @@ QList<QgsProviderSublayerDetails> QgsGdalProviderMetadata::querySublayers( const
return {details};
}

// GDAL is able to handle some compressed datasets directly
// by exposing compressed files as subdatasets (e.g. SENTINEL 2 SAFE)
// thus at first lets try to look for subdatasets and only then for
// individual files inside a compressed file.
CPLPushErrorHandler( CPLQuietErrorHandler );
CPLErrorReset();
dataset.reset( QgsGdalProviderBase::gdalOpen( npGdalUri, GDAL_OF_READONLY ) );
CPLPopErrorHandler();

if ( !dataset )
{
CPLPushErrorHandler( CPLQuietErrorHandler );
CPLErrorReset();
dataset.reset( QgsGdalProviderBase::gdalOpen( gdalUri, GDAL_OF_READONLY ) );
CPLPopErrorHandler();
}
if ( dataset )
{
const QList< QgsProviderSublayerDetails > res = QgsGdalProvider::sublayerDetails( dataset.get(), uri );
if ( res.empty() )
{
// may not have multiple sublayers, but may still be a single-raster layer dataset
if ( GDALGetRasterCount( dataset.get() ) != 0 )
{
QgsProviderSublayerDetails details;
details.setProviderKey( PROVIDER_KEY );
details.setType( QgsMapLayerType::RasterLayer );
details.setUri( uri );
details.setLayerNumber( 1 );
GDALDriverH hDriver = GDALGetDatasetDriver( dataset.get() );
details.setDriverName( GDALGetDriverShortName( hDriver ) );

QString name;
const QVariantMap parts = decodeUri( uri );
if ( !parts.value( QStringLiteral( "vsiSuffix" ) ).toString().isEmpty() )
{
name = parts.value( QStringLiteral( "vsiSuffix" ) ).toString();
if ( name.startsWith( '/' ) )
name = name.mid( 1 );
}
else if ( parts.contains( QStringLiteral( "path" ) ) )
{
name = QgsProviderUtils::suggestLayerNameFromFilePath( parts.value( QStringLiteral( "path" ) ).toString() );
}
details.setName( name.isEmpty() ? uri : name );
return {details};
}
}
else
{
return res;
}
}

if ( !uriParts.value( QStringLiteral( "vsiPrefix" ) ).toString().isEmpty()
&& uriParts.value( QStringLiteral( "vsiSuffix" ) ).toString().isEmpty() )
{
Expand Down Expand Up @@ -3808,55 +3865,7 @@ QList<QgsProviderSublayerDetails> QgsGdalProviderMetadata::querySublayers( const
return res;
}
}

CPLPushErrorHandler( CPLQuietErrorHandler );
CPLErrorReset();
dataset.reset( QgsGdalProviderBase::gdalOpen( gdalUri, GDAL_OF_READONLY ) );
CPLPopErrorHandler();

if ( !dataset )
{
return {};
}

const QList< QgsProviderSublayerDetails > res = QgsGdalProvider::sublayerDetails( dataset.get(), uri );
if ( res.empty() )
{
// may not have multiple sublayers, but may still be a single-raster layer dataset
if ( GDALGetRasterCount( dataset.get() ) != 0 )
{
QgsProviderSublayerDetails details;
details.setProviderKey( PROVIDER_KEY );
details.setType( QgsMapLayerType::RasterLayer );
details.setUri( uri );
details.setLayerNumber( 1 );
GDALDriverH hDriver = GDALGetDatasetDriver( dataset.get() );
details.setDriverName( GDALGetDriverShortName( hDriver ) );

QString name;
const QVariantMap parts = decodeUri( uri );
if ( !parts.value( QStringLiteral( "vsiSuffix" ) ).toString().isEmpty() )
{
name = parts.value( QStringLiteral( "vsiSuffix" ) ).toString();
if ( name.startsWith( '/' ) )
name = name.mid( 1 );
}
else if ( parts.contains( QStringLiteral( "path" ) ) )
{
name = QgsProviderUtils::suggestLayerNameFromFilePath( parts.value( QStringLiteral( "path" ) ).toString() );
}
details.setName( name.isEmpty() ? uri : name );
return {details};
}
else
{
return {};
}
}
else
{
return res;
}
return {};
}

QStringList QgsGdalProviderMetadata::sidecarFilesForUri( const QString &uri ) const
Expand Down
13 changes: 13 additions & 0 deletions tests/src/core/testqgsgdalprovider.cpp
Expand Up @@ -626,6 +626,19 @@ void TestQgsGdalProvider::testGdalProviderQuerySublayers()
// metadata.xml file next to tdenv?.adf file -- this is a subcomponent of an ESRI tin layer, should not be exposed
res = gdalMetadata->querySublayers( QStringLiteral( TEST_DATA_DIR ) + "/esri_tin/metadata.xml" );
QVERIFY( res.empty() );

// SAFE format zip file
res = gdalMetadata->querySublayers( QStringLiteral( TEST_DATA_DIR ) + "/zip/S2A_MSIL2A_0000.zip" );
QCOMPARE( res.count(), 4 );
QCOMPARE( res.at( 0 ).layerNumber(), 1 );
QCOMPARE( res.at( 0 ).name(), QStringLiteral( "SENTINEL2_L2A:/vsizip/%1/zip/S2A_MSIL2A_0000.zip/S2A_MSIL2A_0000.SAFE/MTD_MSIL2A.xml:10m:EPSG_32634" ).arg( QStringLiteral( TEST_DATA_DIR ) ) );
QCOMPARE( res.at( 0 ).description(), QString( "Bands B2, B3, B4, B8 with 10m resolution, UTM 34N" ) );
QCOMPARE( res.at( 0 ).uri(), QStringLiteral( "SENTINEL2_L2A:/vsizip/%1/zip/S2A_MSIL2A_0000.zip/S2A_MSIL2A_0000.SAFE/MTD_MSIL2A.xml:10m:EPSG_32634" ).arg( QStringLiteral( TEST_DATA_DIR ) ) );
QCOMPARE( res.at( 0 ).providerKey(), QStringLiteral( "gdal" ) );
QCOMPARE( res.at( 0 ).type(), QgsMapLayerType::RasterLayer );
QCOMPARE( res.at( 0 ).driverName(), QStringLiteral( "SENTINEL2" ) );
rl.reset( qgis::down_cast< QgsRasterLayer * >( res.at( 0 ).toLayer( options ) ) );
QVERIFY( rl->isValid() );
}

void TestQgsGdalProvider::testGdalProviderQuerySublayersFastScan()
Expand Down
Binary file added tests/testdata/zip/S2A_MSIL2A_0000.zip
Binary file not shown.

0 comments on commit d62f0fd

Please sign in to comment.