[OGR provider] Make sure to use layername= syntax in URI of multilayer datasources (fixes #19885)

This commit is contained in:
Even Rouault 2018-09-21 15:32:19 +02:00
parent 9e9ddb587e
commit ea2cc365db
No known key found for this signature in database
GPG Key ID: 33EBBFC47B3DD87D
7 changed files with 107 additions and 18 deletions

View File

@ -21,7 +21,7 @@ The method createDataItem() is ever called only if capabilities() return non-zer
There are two occasions when createDataItem() is called: There are two occasions when createDataItem() is called:
1. to create root items (passed path is empty, parent item is null). 1. to create root items (passed path is empty, parent item is null).
2. to create items in directory structure. For this capabilities have to return at least 2. to create items in directory structure. For this capabilities have to return at least
of the following: QgsDataProider.Dir or QgsDataProvider.File. Passed path is the file of the following: QgsDataProvider.Dir or QgsDataProvider.File. Passed path is the file
or directory being inspected, parent item is a valid QgsDirectoryItem or directory being inspected, parent item is a valid QgsDirectoryItem
.. versionadded:: 2.10 .. versionadded:: 2.10

View File

@ -36,7 +36,7 @@ typedef bool handlesDirectoryPath_t( const QString &path ) SIP_SKIP;
* There are two occasions when createDataItem() is called: * There are two occasions when createDataItem() is called:
* 1. to create root items (passed path is empty, parent item is null). * 1. to create root items (passed path is empty, parent item is null).
* 2. to create items in directory structure. For this capabilities have to return at least * 2. to create items in directory structure. For this capabilities have to return at least
* of the following: QgsDataProider::Dir or QgsDataProvider::File. Passed path is the file * of the following: QgsDataProvider::Dir or QgsDataProvider::File. Passed path is the file
* or directory being inspected, parent item is a valid QgsDirectoryItem * or directory being inspected, parent item is a valid QgsDirectoryItem
* *
* \since QGIS 2.10 * \since QGIS 2.10

View File

@ -371,7 +371,10 @@ void QgsOgrLayerItem::deleteLayer()
// ------- // -------
static QgsOgrLayerItem *dataItemForLayer( QgsDataItem *parentItem, QString name, QString path, GDALDatasetH hDataSource, int layerId, bool isSubLayer = false ) static QgsOgrLayerItem *dataItemForLayer( QgsDataItem *parentItem, QString name,
QString path, GDALDatasetH hDataSource,
int layerId,
bool isSubLayer, bool uniqueNames )
{ {
OGRLayerH hLayer = GDALDatasetGetLayer( hDataSource, layerId ); OGRLayerH hLayer = GDALDatasetGetLayer( hDataSource, layerId );
OGRFeatureDefnH hDef = OGR_L_GetLayerDefn( hLayer ); OGRFeatureDefnH hDef = OGR_L_GetLayerDefn( hLayer );
@ -401,16 +404,22 @@ static QgsOgrLayerItem *dataItemForLayer( QgsDataItem *parentItem, QString name,
QString layerUri = path; QString layerUri = path;
if ( name.isEmpty() ) if ( isSubLayer )
{ {
// we are in a collection // we are in a collection
name = QString::fromUtf8( OGR_FD_GetName( hDef ) ); name = QString::fromUtf8( OGR_FD_GetName( hDef ) );
QgsDebugMsg( "OGR layer name : " + name ); QgsDebugMsg( "OGR layer name : " + name );
if ( !uniqueNames )
layerUri += "|layerid=" + QString::number( layerId ); {
layerUri += "|layerid=" + QString::number( layerId );
}
else
{
layerUri += "|layername=" + name;
}
path += '/' + name; path += '/' + name;
} }
Q_ASSERT( !name.isEmpty() );
QgsDebugMsgLevel( "OGR layer uri : " + layerUri, 2 ); QgsDebugMsgLevel( "OGR layer uri : " + layerUri, 2 );
@ -433,10 +442,26 @@ QVector<QgsDataItem *> QgsOgrDataCollectionItem::createChildren()
return children; return children;
int numLayers = GDALDatasetGetLayerCount( hDataSource.get() ); int numLayers = GDALDatasetGetLayerCount( hDataSource.get() );
// Check if layer names are unique, so we can use |layername= in URI
QMap< QString, int > mapLayerNameToCount;
bool uniqueNames = true;
for ( int i = 0; i < numLayers; ++i )
{
OGRLayerH hLayer = GDALDatasetGetLayer( hDataSource.get(), i );
OGRFeatureDefnH hDef = OGR_L_GetLayerDefn( hLayer );
QString layerName = QString::fromUtf8( OGR_FD_GetName( hDef ) );
++mapLayerNameToCount[layerName];
if ( mapLayerNameToCount[layerName] > 1 )
{
uniqueNames = false;
break;
}
}
children.reserve( numLayers ); children.reserve( numLayers );
for ( int i = 0; i < numLayers; ++i ) for ( int i = 0; i < numLayers; ++i )
{ {
QgsOgrLayerItem *item = dataItemForLayer( this, QString(), mPath, hDataSource.get(), i, true ); QgsOgrLayerItem *item = dataItemForLayer( this, QString(), mPath, hDataSource.get(), i, true, uniqueNames );
children.append( item ); children.append( item );
} }
@ -477,13 +502,10 @@ bool QgsOgrDataCollectionItem::createConnection( const QString &name, const QStr
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
QGISEXTERN int dataCapabilities()
{
return QgsDataProvider::File | QgsDataProvider::Dir;
}
QGISEXTERN QgsDataItem *dataItem( QString path, QgsDataItem *parentItem ) QgsDataItem *QgsOgrDataItemProvider::createDataItem( const QString &pathIn, QgsDataItem *parentItem )
{ {
QString path( pathIn );
if ( path.isEmpty() ) if ( path.isEmpty() )
return nullptr; return nullptr;
@ -607,7 +629,7 @@ QGISEXTERN QgsDataItem *dataItem( QString path, QgsDataItem *parentItem )
// class // class
// TODO: add more OGR supported multiple layers formats here! // TODO: add more OGR supported multiple layers formats here!
QStringList ogrSupportedDbLayersExtensions; QStringList ogrSupportedDbLayersExtensions;
ogrSupportedDbLayersExtensions << QStringLiteral( "gpkg" ) << QStringLiteral( "sqlite" ) << QStringLiteral( "db" ) << QStringLiteral( "gdb" ); ogrSupportedDbLayersExtensions << QStringLiteral( "gpkg" ) << QStringLiteral( "sqlite" ) << QStringLiteral( "db" ) << QStringLiteral( "gdb" ) << QStringLiteral( "kml" );
QStringList ogrSupportedDbDriverNames; QStringList ogrSupportedDbDriverNames;
ogrSupportedDbDriverNames << QStringLiteral( "GPKG" ) << QStringLiteral( "db" ) << QStringLiteral( "gdb" ); ogrSupportedDbDriverNames << QStringLiteral( "GPKG" ) << QStringLiteral( "db" ) << QStringLiteral( "gdb" );
@ -688,12 +710,12 @@ QGISEXTERN QgsDataItem *dataItem( QString path, QgsDataItem *parentItem )
} }
else else
{ {
item = dataItemForLayer( parentItem, name, path, hDS.get(), 0 ); item = dataItemForLayer( parentItem, name, path, hDS.get(), 0, false, true );
} }
return item; return item;
} }
QGISEXTERN bool handlesDirectoryPath( const QString &path ) bool QgsOgrDataItemProvider::handlesDirectoryPath( const QString &path )
{ {
QFileInfo info( path ); QFileInfo info( path );
QString suffix = info.suffix().toLower(); QString suffix = info.suffix().toLower();

View File

@ -102,5 +102,19 @@ class QgsOgrDataCollectionItem : public QgsDataCollectionItem
}; };
//! Provider for OGR root data item
class QgsOgrDataItemProvider : public QgsDataItemProvider
{
public:
QString name() override { return QStringLiteral( "OGR" ); }
int capabilities() override { return QgsDataProvider::File | QgsDataProvider::Dir; }
QgsDataItem *createDataItem( const QString &path, QgsDataItem *parentItem ) override;
bool handlesDirectoryPath( const QString &path ) override;
};
#endif // QGSOGRDATAITEMS_H #endif // QGSOGRDATAITEMS_H

View File

@ -3375,10 +3375,10 @@ QGISEXTERN bool createEmptyDataSource( const QString &uri,
return true; return true;
} }
QGISEXTERN QList< QgsDataItemProvider * > *dataItemProviders() QGISEXTERN QList< QgsDataItemProvider * > *dataItemProviders()
{ {
QList< QgsDataItemProvider * > *providers = new QList< QgsDataItemProvider * >(); QList< QgsDataItemProvider * > *providers = new QList< QgsDataItemProvider * >();
*providers << new QgsOgrDataItemProvider;
*providers << new QgsGeoPackageDataItemProvider; *providers << new QgsGeoPackageDataItemProvider;
return providers; return providers;
} }

View File

@ -19,7 +19,8 @@ import tempfile
from osgeo import gdal, ogr # NOQA from osgeo import gdal, ogr # NOQA
from qgis.PyQt.QtCore import QVariant from qgis.PyQt.QtCore import QVariant
from qgis.core import (QgsFeature, QgsFeatureRequest, QgsField, QgsSettings, QgsDataProvider, from qgis.core import (QgsApplication,
QgsFeature, QgsFeatureRequest, QgsField, QgsSettings, QgsDataProvider,
QgsVectorDataProvider, QgsVectorLayer, QgsWkbTypes, QgsNetworkAccessManager) QgsVectorDataProvider, QgsVectorLayer, QgsWkbTypes, QgsNetworkAccessManager)
from qgis.testing import start_app, unittest from qgis.testing import start_app, unittest
@ -404,6 +405,34 @@ class PyQgsOGRProvider(unittest.TestCase):
vl = QgsVectorLayer(datasource, 'test', 'ogr') vl = QgsVectorLayer(datasource, 'test', 'ogr')
self.assertEqual(len(vl.fields()), 2) self.assertEqual(len(vl.fields()), 2)
def testDataItems(self):
registry = QgsApplication.dataItemProviderRegistry()
ogrprovider = next(provider for provider in registry.providers() if provider.name() == 'OGR')
# Single layer
item = ogrprovider.createDataItem(os.path.join(TEST_DATA_DIR, 'lines.shp'), None)
self.assertTrue(item.uri().endswith('lines.shp'))
# Multiple layer
item = ogrprovider.createDataItem(os.path.join(TEST_DATA_DIR, 'multilayer.kml'), None)
children = item.createChildren()
self.assertEqual(len(children), 2)
self.assertIn('multilayer.kml|layername=Layer1', children[0].uri())
self.assertIn('multilayer.kml|layername=Layer2', children[1].uri())
# Multiple layer (geopackage)
tmpfile = os.path.join(self.basetestpath, 'testDataItems.gpkg')
ds = ogr.GetDriverByName('GPKG').CreateDataSource(tmpfile)
lyr = ds.CreateLayer('Layer1', geom_type=ogr.wkbPoint)
lyr = ds.CreateLayer('Layer2', geom_type=ogr.wkbPoint)
ds = None
item = ogrprovider.createDataItem(tmpfile, None)
children = item.createChildren()
self.assertEqual(len(children), 2)
self.assertIn('testDataItems.gpkg|layername=Layer1', children[0].uri())
self.assertIn('testDataItems.gpkg|layername=Layer2', children[1].uri())
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

24
tests/testdata/multilayer.kml vendored Normal file
View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:kml="http://www.opengis.net/kml/2.2">
<Document>
<name>Test</name>
<Folder id="Layer1">
<name>Layer1</name>
<Placemark>
<name>Placemark1</name>
<Point>
<coordinates>2,49,0</coordinates>
</Point>
</Placemark>
</Folder>
<Folder id="Layer2">
<name>Layer2</name>
<Placemark>
<name>Placemark2</name>
<Point>
<coordinates>3,50,0</coordinates>
</Point>
</Placemark>
</Folder>
</Document>
</kml>