Skip to content

Commit

Permalink
Fix gdal querySublayers handling of vsi archive uris
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Jul 12, 2021
1 parent c94d4b7 commit 6bf2f71
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 4 deletions.
48 changes: 45 additions & 3 deletions src/core/providers/gdal/qgsgdalprovider.cpp
Expand Up @@ -3557,7 +3557,7 @@ QgsProviderMetadata::ProviderCapabilities QgsGdalProviderMetadata::providerCapab
return FileBasedUris;
}

QList<QgsProviderSublayerDetails> QgsGdalProviderMetadata::querySublayers( const QString &uri, Qgis::SublayerQueryFlags flags, QgsFeedback * ) const
QList<QgsProviderSublayerDetails> QgsGdalProviderMetadata::querySublayers( const QString &uri, Qgis::SublayerQueryFlags flags, QgsFeedback *feedback ) const
{
gdal::dataset_unique_ptr dataset;

Expand All @@ -3567,18 +3567,23 @@ QList<QgsProviderSublayerDetails> QgsGdalProviderMetadata::querySublayers( const

QString gdalUri = uri;

QVariantMap uriParts = decodeUri( gdalUri );

// Try to open using VSIFileHandler
QString vsiPrefix = QgsZipItem::vsiPrefix( gdalUri );
if ( !vsiPrefix.isEmpty() )
{
if ( !gdalUri.startsWith( vsiPrefix ) )
{
gdalUri = vsiPrefix + gdalUri;
uriParts = decodeUri( gdalUri );
}
}

if ( flags & Qgis::SublayerQueryFlag::FastScan )
{
// filter based on extension
const QVariantMap uriParts = decodeUri( uri );
const QVariantMap uriParts = decodeUri( gdalUri );
const QString path = uriParts.value( QStringLiteral( "path" ) ).toString();
QFileInfo info( path );
if ( info.isFile() )
Expand Down Expand Up @@ -3619,6 +3624,39 @@ QList<QgsProviderSublayerDetails> QgsGdalProviderMetadata::querySublayers( const
}
}

if ( !uriParts.value( QStringLiteral( "vsiPrefix" ) ).toString().isEmpty()
&& uriParts.value( QStringLiteral( "vsiSuffix" ) ).toString().isEmpty() )
{
// get list of files inside archive file
QgsDebugMsgLevel( QStringLiteral( "Open file %1 with gdal vsi" ).arg( vsiPrefix + uriParts.value( QStringLiteral( "path" ) ).toString() ), 3 );
char **papszSiblingFiles = VSIReadDirRecursive( QString( vsiPrefix + uriParts.value( QStringLiteral( "path" ) ).toString() ).toLocal8Bit().constData() );
if ( papszSiblingFiles )
{
QList<QgsProviderSublayerDetails> res;

QStringList files;
for ( int i = 0; papszSiblingFiles[i]; i++ )
{
files << papszSiblingFiles[i];
}

for ( const QString &file : std::as_const( files ) )
{
if ( feedback && feedback->isCanceled() )
break;

// skip directories (files ending with /)
if ( file.right( 1 ) != QLatin1String( "/" ) )
{
uriParts.insert( QStringLiteral( "vsiSuffix" ), QStringLiteral( "/%1" ).arg( file ) );
res << querySublayers( encodeUri( uriParts ), flags, feedback );
}
}
CSLDestroy( papszSiblingFiles );
return res;
}
}

dataset.reset( QgsGdalProviderBase::gdalOpen( gdalUri, GDAL_OF_READONLY ) );
if ( !dataset )
{
Expand All @@ -3641,7 +3679,11 @@ QList<QgsProviderSublayerDetails> QgsGdalProviderMetadata::querySublayers( const

QString name;
const QVariantMap parts = decodeUri( uri );
if ( parts.contains( QStringLiteral( "path" ) ) )
if ( !parts.value( QStringLiteral( "vsiSuffix" ) ).toString().isEmpty() )
{
name = QgsProviderUtils::suggestLayerNameFromFilePath( parts.value( QStringLiteral( "vsiSuffix" ) ).toString() );
}
else if ( parts.contains( QStringLiteral( "path" ) ) )
{
name = QgsProviderUtils::suggestLayerNameFromFilePath( parts.value( QStringLiteral( "path" ) ).toString() );
}
Expand Down
62 changes: 62 additions & 0 deletions tests/src/core/testqgsgdalprovider.cpp
Expand Up @@ -546,6 +546,68 @@ void TestQgsGdalProvider::testGdalProviderQuerySublayers()
QCOMPARE( res.at( 0 ).type(), QgsMapLayerType::RasterLayer );
rl.reset( qgis::down_cast< QgsRasterLayer * >( res.at( 0 ).toLayer( options ) ) );
QVERIFY( rl->isValid() );

// zip archive, only 1 file
res = gdalMetadata->querySublayers( QStringLiteral( TEST_DATA_DIR ) + "/zip/landsat_b1.zip" );
QCOMPARE( res.count(), 1 );
QCOMPARE( res.at( 0 ).layerNumber(), 1 );
QCOMPARE( res.at( 0 ).name(), QStringLiteral( "landsat_b1" ) );
QCOMPARE( res.at( 0 ).description(), QString() );
QCOMPARE( res.at( 0 ).uri(), QStringLiteral( "/vsizip/%1/zip/landsat_b1.zip/landsat_b1.tif" ).arg( QStringLiteral( TEST_DATA_DIR ) ) );
QCOMPARE( res.at( 0 ).providerKey(), QStringLiteral( "gdal" ) );
QCOMPARE( res.at( 0 ).type(), QgsMapLayerType::RasterLayer );
rl.reset( qgis::down_cast< QgsRasterLayer * >( res.at( 0 ).toLayer( options ) ) );
QVERIFY( rl->isValid() );

// multi-layer archive
res = gdalMetadata->querySublayers( QStringLiteral( TEST_DATA_DIR ) + "/zip/testtar.tgz" );
QCOMPARE( res.count(), 3 );
QCOMPARE( res.at( 0 ).layerNumber(), 1 );
QCOMPARE( res.at( 0 ).name(), QStringLiteral( "landsat_b2" ) );
QCOMPARE( res.at( 0 ).description(), QString() );
QCOMPARE( res.at( 0 ).uri(), QStringLiteral( "/vsitar/%1/zip/testtar.tgz/folder/folder2/landsat_b2.tif" ).arg( QStringLiteral( TEST_DATA_DIR ) ) );
QCOMPARE( res.at( 0 ).providerKey(), QStringLiteral( "gdal" ) );
QCOMPARE( res.at( 0 ).type(), QgsMapLayerType::RasterLayer );
rl.reset( qgis::down_cast< QgsRasterLayer * >( res.at( 0 ).toLayer( options ) ) );
QVERIFY( rl->isValid() );
QCOMPARE( res.at( 1 ).layerNumber(), 1 );
QCOMPARE( res.at( 1 ).name(), QStringLiteral( "landsat_b1" ) );
QCOMPARE( res.at( 1 ).description(), QString() );
QCOMPARE( res.at( 1 ).uri(), QStringLiteral( "/vsitar/%1/zip/testtar.tgz/landsat_b1.tif" ).arg( QStringLiteral( TEST_DATA_DIR ) ) );
QCOMPARE( res.at( 1 ).providerKey(), QStringLiteral( "gdal" ) );
QCOMPARE( res.at( 1 ).type(), QgsMapLayerType::RasterLayer );
rl.reset( qgis::down_cast< QgsRasterLayer * >( res.at( 1 ).toLayer( options ) ) );
QVERIFY( rl->isValid() );
QCOMPARE( res.at( 2 ).layerNumber(), 1 );
QCOMPARE( res.at( 2 ).name(), QStringLiteral( "landsat_b1" ) );
QCOMPARE( res.at( 2 ).description(), QString() );
QCOMPARE( res.at( 2 ).uri(), QStringLiteral( "/vsitar/%1/zip/testtar.tgz/landsat_b1.vrt" ).arg( QStringLiteral( TEST_DATA_DIR ) ) );
QCOMPARE( res.at( 2 ).providerKey(), QStringLiteral( "gdal" ) );
QCOMPARE( res.at( 2 ).type(), QgsMapLayerType::RasterLayer );
rl.reset( qgis::down_cast< QgsRasterLayer * >( res.at( 2 ).toLayer( options ) ) );
QVERIFY( rl->isValid() );

// multi-layer archive, but with specific suffix specified
res = gdalMetadata->querySublayers( QStringLiteral( "/vsitar/" ) + QStringLiteral( TEST_DATA_DIR ) + "/zip/testtar.tgz/folder/folder2/landsat_b2.tif" );
QCOMPARE( res.count(), 1 );
QCOMPARE( res.at( 0 ).layerNumber(), 1 );
QCOMPARE( res.at( 0 ).name(), QStringLiteral( "landsat_b2" ) );
QCOMPARE( res.at( 0 ).description(), QString() );
QCOMPARE( res.at( 0 ).uri(), QStringLiteral( "/vsitar/%1/zip/testtar.tgz/folder/folder2/landsat_b2.tif" ).arg( QStringLiteral( TEST_DATA_DIR ) ) );
QCOMPARE( res.at( 0 ).providerKey(), QStringLiteral( "gdal" ) );
QCOMPARE( res.at( 0 ).type(), QgsMapLayerType::RasterLayer );
rl.reset( qgis::down_cast< QgsRasterLayer * >( res.at( 0 ).toLayer( options ) ) );
QVERIFY( rl->isValid() );
res = gdalMetadata->querySublayers( QStringLiteral( "/vsitar/" ) + QStringLiteral( TEST_DATA_DIR ) + "/zip/testtar.tgz/landsat_b1.tif" );
QCOMPARE( res.count(), 1 );
QCOMPARE( res.at( 0 ).layerNumber(), 1 );
QCOMPARE( res.at( 0 ).name(), QStringLiteral( "landsat_b1" ) );
QCOMPARE( res.at( 0 ).description(), QString() );
QCOMPARE( res.at( 0 ).uri(), QStringLiteral( "/vsitar/%1/zip/testtar.tgz/landsat_b1.tif" ).arg( QStringLiteral( TEST_DATA_DIR ) ) );
QCOMPARE( res.at( 0 ).providerKey(), QStringLiteral( "gdal" ) );
QCOMPARE( res.at( 0 ).type(), QgsMapLayerType::RasterLayer );
rl.reset( qgis::down_cast< QgsRasterLayer * >( res.at( 0 ).toLayer( options ) ) );
QVERIFY( rl->isValid() );
}

QGSTEST_MAIN( TestQgsGdalProvider )
Expand Down
2 changes: 1 addition & 1 deletion tests/src/python/test_provider_ogr.py
Expand Up @@ -1574,7 +1574,7 @@ def test_provider_sublayer_details(self):
self.assertTrue(vl.isValid())
self.assertEqual(vl.wkbType(), QgsWkbTypes.Point)

# multi-layer archive, but with specific layer name specified
# multi-layer archive, but with specific suffix specified
res = metadata.querySublayers('/vsitar/' + os.path.join(TEST_DATA_DIR, 'zip', 'testtar.tgz') + '/folder/points.geojson')
self.assertEqual(len(res), 1)
self.assertEqual(res[0].layerNumber(), 0)
Expand Down

0 comments on commit 6bf2f71

Please sign in to comment.