Skip to content

Commit

Permalink
Automatically restore layer metadata on loading a gpkg if present
Browse files Browse the repository at this point in the history
in the gpkg_metadata table
  • Loading branch information
nyalldawson committed May 3, 2021
1 parent d589690 commit 207ed26
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 42 deletions.
113 changes: 71 additions & 42 deletions src/core/providers/ogr/qgsogrprovider.cpp
Expand Up @@ -617,48 +617,7 @@ QgsOgrProvider::QgsOgrProvider( QString const &uri, const ProviderOptions &optio
bool supportsBoolean = false;

// layer metadata
if ( mOgrOrigLayer )
{
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
QMutex *mutex = nullptr;
#else
QRecursiveMutex *mutex = nullptr;
#endif
OGRLayerH layer = mOgrOrigLayer->getHandleAndMutex( mutex );
QMutexLocker locker( mutex );
if ( ( mGDALDriverName == QLatin1String( "FileGDB" ) || mGDALDriverName == QLatin1String( "OpenFileGDB" ) ) )
{
// read layer metadata

// important -- this ONLY works if the layer name is NOT quoted!!
QByteArray sql = "GetLayerMetadata " + mOgrOrigLayer->name();
if ( QgsOgrLayerUniquePtr l = mOgrOrigLayer->ExecuteSQL( sql ) )
{
gdal::ogr_feature_unique_ptr f( l->GetNextFeature() );
if ( f )
{
bool ok = false;
QVariant res = QgsOgrUtils::getOgrFeatureAttribute( f.get(), QgsField( QString(), QVariant::String ), 0, textEncoding(), &ok );
if ( ok )
{
QDomDocument metadataDoc;
metadataDoc.setContent( res.toString() );
mLayerMetadata = QgsMetadataUtils::convertFromEsri( metadataDoc );
}
}
}
}
else
{
const QString identifier = GDALGetMetadataItem( layer, "IDENTIFIER", nullptr );
if ( !identifier.isEmpty() )
mLayerMetadata.setTitle( identifier ); // see geopackage specs -- "'identifier' is analogous to 'title'"
const QString abstract = GDALGetMetadataItem( layer, "DESCRIPTION", nullptr );
if ( !abstract.isEmpty() )
mLayerMetadata.setAbstract( abstract );
}
}
mLayerMetadata.setType( QStringLiteral( "dataset" ) );
loadMetadata();

if ( mOgrOrigLayer )
{
Expand Down Expand Up @@ -1461,6 +1420,76 @@ void QgsOgrProvider::loadFields()
}
}

void QgsOgrProvider::loadMetadata()
{
if ( mOgrOrigLayer )
{
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
QMutex *mutex = nullptr;
#else
QRecursiveMutex *mutex = nullptr;
#endif
OGRLayerH layer = mOgrOrigLayer->getHandleAndMutex( mutex );
QMutexLocker locker( mutex );

const QString identifier = GDALGetMetadataItem( layer, "IDENTIFIER", nullptr );
if ( !identifier.isEmpty() )
mLayerMetadata.setTitle( identifier ); // see geopackage specs -- "'identifier' is analogous to 'title'"
const QString abstract = GDALGetMetadataItem( layer, "DESCRIPTION", nullptr );
if ( !abstract.isEmpty() )
mLayerMetadata.setAbstract( abstract );

if ( mGDALDriverName == QLatin1String( "GPKG" ) )
{
// read Geopackage layer metadata - scan gpkg_metadata table for QGIS metadata
const QString sql = QStringLiteral( "SELECT metadata from gpkg_metadata LEFT JOIN gpkg_metadata_reference ON "
"(gpkg_metadata_reference.table_name = %1 AND gpkg_metadata.id = gpkg_metadata_reference.md_file_id) "
"WHERE md_standard_uri = %2 and reference_scope = %3" ).arg(
QgsSqliteUtils::quotedString( mOgrOrigLayer->name() ),
QgsSqliteUtils::quotedString( QStringLiteral( "http://mrcc.com/qgis.dtd" ) ),
QgsSqliteUtils::quotedString( QStringLiteral( "table" ) ) ); ;

if ( QgsOgrLayerUniquePtr l = mOgrOrigLayer->ExecuteSQL( sql.toLocal8Bit().constData() ) )
{
gdal::ogr_feature_unique_ptr f( l->GetNextFeature() );
if ( f )
{
bool ok = false;
QVariant res = QgsOgrUtils::getOgrFeatureAttribute( f.get(), QgsField( QString(), QVariant::String ), 0, nullptr, &ok );
if ( ok )
{
QDomDocument doc;
doc.setContent( res.toString() );
mLayerMetadata.readMetadataXml( doc.documentElement() );
}
}
}
}
else if ( ( mGDALDriverName == QLatin1String( "FileGDB" ) || mGDALDriverName == QLatin1String( "OpenFileGDB" ) ) )
{
// read ESRI FileGeodatabase layer metadata

// important -- this ONLY works if the layer name is NOT quoted!!
QByteArray sql = "GetLayerMetadata " + mOgrOrigLayer->name();
if ( QgsOgrLayerUniquePtr l = mOgrOrigLayer->ExecuteSQL( sql ) )
{
gdal::ogr_feature_unique_ptr f( l->GetNextFeature() );
if ( f )
{
bool ok = false;
QVariant res = QgsOgrUtils::getOgrFeatureAttribute( f.get(), QgsField( QString(), QVariant::String ), 0, textEncoding(), &ok );
if ( ok )
{
QDomDocument metadataDoc;
metadataDoc.setContent( res.toString() );
mLayerMetadata = QgsMetadataUtils::convertFromEsri( metadataDoc );
}
}
}
}
}
mLayerMetadata.setType( QStringLiteral( "dataset" ) );
}

QString QgsOgrProvider::storageType() const
{
Expand Down
3 changes: 3 additions & 0 deletions src/core/providers/ogr/qgsogrprovider.h
Expand Up @@ -181,6 +181,9 @@ class QgsOgrProvider final: public QgsVectorDataProvider
//! Loads fields from input file to member attributeFields
void loadFields();

//! Loads metadata for the layer
void loadMetadata();

//! Find out the number of features of the whole layer
void recalculateFeatureCount() const;

Expand Down
27 changes: 27 additions & 0 deletions tests/src/python/test_provider_ogr_gpkg.py
Expand Up @@ -1690,6 +1690,33 @@ def testGeopackageSaveMetadata(self):
self.assertEqual(metadata3.licenses(), ['l1', 'l2'])
self.assertEqual(metadata3.history(), ['h1', 'h2'])

def testGeopackageRestoreMetadata(self):
"""
Test that metadata saved to gpkg_metadata is automatically restored on layer load
"""
tmpfile = os.path.join(self.basetestpath, 'testGeopackageRestoreMetadata.gpkg')
ds = ogr.GetDriverByName('GPKG').CreateDataSource(tmpfile)
lyr = ds.CreateLayer('test', geom_type=ogr.wkbPolygon)
lyr.CreateField(ogr.FieldDefn('str_field', ogr.OFTString))
lyr.CreateField(ogr.FieldDefn('str_field2', ogr.OFTString))
f = None
ds = None

# now save some metadata
metadata = QgsLayerMetadata()
metadata.setAbstract('my abstract')
metadata.setIdentifier('my identifier')
metadata.setLicenses(['l1', 'l2'])
ok, err = QgsProviderRegistry.instance().saveLayerMetadata('ogr', QgsProviderRegistry.instance().encodeUri('ogr', {'path': tmpfile, 'layerName': 'test'}), metadata)
self.assertTrue(ok)

vl = QgsVectorLayer(tmpfile, 'test')
self.assertTrue(vl.isValid())
metadata2 = vl.metadata()
self.assertEqual(metadata2.abstract(), 'my abstract')
self.assertEqual(metadata2.identifier(), 'my identifier')
self.assertEqual(metadata2.licenses(), ['l1', 'l2'])

def testUniqueValuesOnFidColumn(self):
"""Test regression #21311 OGR provider returns an empty set for GPKG uniqueValues"""

Expand Down

0 comments on commit 207ed26

Please sign in to comment.