Merge pull request #5534 from nyalldawson/format

Add flags to QgsVectorFileWriter methods which return lists of drivers
This commit is contained in:
Nyall Dawson 2017-11-06 08:56:17 +10:00 committed by GitHub
commit 31f98dac03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 310 additions and 79 deletions

View File

@ -2672,6 +2672,8 @@ for consistency with other parts of the QGIS API.
- The addFeature which takes a renderer argument was renamed to addFeatureWithStyle.
- static `writeAsVectorFormat` calls no longer take a errorMessage argument in
python and instead return a `(errorCode, errorMessage)` tuple.
- ogrDriverList now returns a list of QgsVectorFileWriter.DriverDetails structs, instead of a map
- supportedFiltersAndFormats now returns a list of QgsVectorFileWriter.FilterFormatDetails structs, instead of a map
QgsWMSLegendNode {#qgis_api_break_3_0_QgsWMSLegendNode}

View File

@ -10,8 +10,6 @@
class QgsVectorFileWriter : QgsFeatureSink
{
%Docstring
@ -145,6 +143,15 @@ Some formats require a compulsory encoding, typically UTF-8. If no compulsory en
SymbolLayerSymbology
};
enum VectorFormatOption
{
SortRecommended,
SkipNonSpatialFormats,
};
typedef QFlags<QgsVectorFileWriter::VectorFormatOption> VectorFormatOptions;
class FieldValueConverter
{
%Docstring
@ -460,27 +467,67 @@ Create a new vector file writer
static QMap< QString, QString> supportedFiltersAndFormats();
struct FilterFormatDetails
{
QString driverName;
%Docstring
Returns a map with format filter string as key and OGR format key as value.
.. seealso:: supportedOutputVectorLayerExtensions()
:rtype: QMap< str, QString>
Unique driver name
%End
static QStringList supportedFormatExtensions();
QString filterString;
%Docstring
Filter string for file picker dialogs
%End
};
static QList< QgsVectorFileWriter::FilterFormatDetails > supportedFiltersAndFormats( VectorFormatOptions options = SortRecommended );
%Docstring
Returns a list or pairs, with format filter string as first element and OGR format key as second element.
The ``options`` argument can be used to control the sorting and filtering of
returned formats.
.. seealso:: supportedOutputVectorLayerExtensions()
:rtype: list of QgsVectorFileWriter.FilterFormatDetails
%End
static QStringList supportedFormatExtensions( VectorFormatOptions options = SortRecommended );
%Docstring
Returns a list of file extensions for supported formats.
The ``options`` argument can be used to control the sorting and filtering of
returned formats.
.. versionadded:: 3.0
.. seealso:: supportedFiltersAndFormats()
:rtype: list of str
%End
static QMap< QString, QString> ogrDriverList();
struct DriverDetails
{
QString longName;
%Docstring
Returns driver list that can be used for dialogs. It contains all OGR drivers
+ some additional internal QGIS driver names to distinguish between more
supported formats of the same OGR driver
:rtype: QMap< str, QString>
Descriptive, user friendly name for the driver
%End
QString driverName;
%Docstring
Unique driver name
%End
};
static QList< QgsVectorFileWriter::DriverDetails > ogrDriverList( VectorFormatOptions options = SortRecommended );
%Docstring
Returns the driver list that can be used for dialogs. It contains all OGR drivers
plus some additional internal QGIS driver names to distinguish between more
supported formats of the same OGR driver.
The returned list consists of structs containing the driver long name (e.g. user-friendly
display name for the format) and internal driver short name.
The ``options`` argument can be used to control the sorting and filtering of
returned drivers.
:rtype: list of QgsVectorFileWriter.DriverDetails
%End
static QString driverForExtension( const QString &extension );
@ -492,9 +539,12 @@ Create a new vector file writer
:rtype: str
%End
static QString fileFilterString();
static QString fileFilterString( VectorFormatOptions options = SortRecommended );
%Docstring
Returns filter string that can be used for dialogs
Returns filter string that can be used for dialogs.
The ``options`` argument can be used to control the sorting and filtering of
returned drivers.
:rtype: str
%End
@ -639,6 +689,8 @@ Close opened shapefile for writing
QFlags<QgsVectorFileWriter::EditionCapability> operator|(QgsVectorFileWriter::EditionCapability f1, QFlags<QgsVectorFileWriter::EditionCapability> f2);
QFlags<QgsVectorFileWriter::VectorFormatOption> operator|(QgsVectorFileWriter::VectorFormatOption f1, QFlags<QgsVectorFileWriter::VectorFormatOption> f2);
/************************************************************************

View File

@ -174,9 +174,9 @@ class GdalUtils(object):
return 'ESRI Shapefile'
formats = QgsVectorFileWriter.supportedFiltersAndFormats()
for k, v in list(formats.items()):
if ext in k:
return v
for format in formats:
if ext in format.filterString:
return format.driverName
return 'ESRI Shapefile'
@staticmethod

View File

@ -2681,13 +2681,16 @@ void QgsVectorFileWriter::setSymbologyScale( double d )
mRenderContext.setRendererScale( mSymbologyScale );
}
QMap< QString, QString> QgsVectorFileWriter::supportedFiltersAndFormats()
QList< QgsVectorFileWriter::FilterFormatDetails > QgsVectorFileWriter::supportedFiltersAndFormats( const VectorFormatOptions options )
{
QMap<QString, QString> resultMap;
QList< FilterFormatDetails > results;
QgsApplication::registerOgrDrivers();
int const drvCount = OGRGetDriverCount();
FilterFormatDetails shapeFormat;
FilterFormatDetails gpkgFormat;
for ( int i = 0; i < drvCount; ++i )
{
OGRSFDriverH drv = OGRGetDriver( i );
@ -2696,58 +2699,87 @@ QMap< QString, QString> QgsVectorFileWriter::supportedFiltersAndFormats()
QString drvName = OGR_Dr_GetName( drv );
if ( OGR_Dr_TestCapability( drv, "CreateDataSource" ) != 0 )
{
if ( options & SkipNonSpatialFormats )
{
// skip non-spatial formats
// TODO - use GDAL metadata to determine this, when support exists in GDAL
if ( drvName == QLatin1String( "ODS" ) || drvName == QLatin1String( "XLSX" ) || drvName == QLatin1String( "XLS" ) )
continue;
}
QString filterString = filterForDriver( drvName );
if ( filterString.isEmpty() )
continue;
resultMap.insert( filterString, drvName );
FilterFormatDetails details;
details.driverName = drvName;
details.filterString = filterString;
if ( options & SortRecommended )
{
if ( drvName == QLatin1String( "ESRI Shapefile" ) )
{
shapeFormat = details;
continue;
}
else if ( drvName == QLatin1String( "GPKG" ) )
{
gpkgFormat = details;
continue;
}
}
results << details;
}
}
}
return resultMap;
std::sort( results.begin(), results.end(), []( const FilterFormatDetails & a, const FilterFormatDetails & b ) -> bool
{
return a.driverName < b.driverName;
} );
if ( options & SortRecommended )
{
if ( !shapeFormat.filterString.isEmpty() )
{
results.insert( 0, shapeFormat );
}
if ( !gpkgFormat.filterString.isEmpty() )
{
results.insert( 0, gpkgFormat );
}
}
return results;
}
QStringList QgsVectorFileWriter::supportedFormatExtensions()
QStringList QgsVectorFileWriter::supportedFormatExtensions( const VectorFormatOptions options )
{
QgsStringMap formats = supportedFiltersAndFormats();
const auto formats = supportedFiltersAndFormats( options );
QStringList extensions;
QRegularExpression rx( QStringLiteral( "\\*\\.([a-zA-Z0-9]*)" ) );
QgsStringMap::const_iterator formatIt = formats.constBegin();
for ( ; formatIt != formats.constEnd(); ++formatIt )
for ( const FilterFormatDetails &format : formats )
{
QString ext = formatIt.key();
QString ext = format.filterString;
QRegularExpressionMatch match = rx.match( ext );
if ( !match.hasMatch() )
continue;
QString matched = match.captured( 1 );
// special handling for the two main contenders for glory
if ( matched.compare( QStringLiteral( "gpkg" ), Qt::CaseInsensitive ) == 0 )
continue;
if ( matched.compare( QStringLiteral( "shp" ), Qt::CaseInsensitive ) == 0 )
continue;
extensions << matched;
}
std::sort( extensions.begin(), extensions.end() );
// Make https://twitter.com/shapefiIe a sad little fellow
extensions.insert( 0, QStringLiteral( "gpkg" ) );
extensions.insert( 1, QStringLiteral( "shp" ) );
return extensions;
}
QMap<QString, QString> QgsVectorFileWriter::ogrDriverList()
QList< QgsVectorFileWriter::DriverDetails > QgsVectorFileWriter::ogrDriverList( const VectorFormatOptions options )
{
QMap<QString, QString> resultMap;
QList< QgsVectorFileWriter::DriverDetails > results;
QgsApplication::registerOgrDrivers();
int const drvCount = OGRGetDriverCount();
const int drvCount = OGRGetDriverCount();
QStringList writableDrivers;
for ( int i = 0; i < drvCount; ++i )
@ -2756,6 +2788,19 @@ QMap<QString, QString> QgsVectorFileWriter::ogrDriverList()
if ( drv )
{
QString drvName = OGR_Dr_GetName( drv );
if ( options & SkipNonSpatialFormats )
{
// skip non-spatial formats
// TODO - use GDAL metadata to determine this, when support exists in GDAL
if ( drvName == QLatin1String( "ODS" ) || drvName == QLatin1String( "XLSX" ) || drvName == QLatin1String( "XLS" ) )
continue;
}
if ( drvName == QLatin1String( "ESRI Shapefile" ) )
{
writableDrivers << QStringLiteral( "DBF file" );
}
if ( OGR_Dr_TestCapability( drv, "CreateDataSource" ) != 0 )
{
// Add separate format for Mapinfo MIF (MITAB is OGR default)
@ -2786,25 +2831,39 @@ QMap<QString, QString> QgsVectorFileWriter::ogrDriverList()
}
CPLFree( options[0] );
}
else if ( drvName == QLatin1String( "ESRI Shapefile" ) )
{
writableDrivers << QStringLiteral( "DBF file" );
}
writableDrivers << drvName;
}
}
}
std::sort( writableDrivers.begin(), writableDrivers.end() );
if ( options & SortRecommended )
{
// recommended order sorting, so we shift certain formats to the top
if ( writableDrivers.contains( QStringLiteral( "ESRI Shapefile" ) ) )
{
writableDrivers.removeAll( QStringLiteral( "ESRI Shapefile" ) );
writableDrivers.insert( 0, QStringLiteral( "ESRI Shapefile" ) );
}
if ( writableDrivers.contains( QStringLiteral( "GPKG" ) ) )
{
// Make https://twitter.com/shapefiIe a sad little fellow
writableDrivers.removeAll( QStringLiteral( "GPKG" ) );
writableDrivers.insert( 0, QStringLiteral( "GPKG" ) );
}
}
Q_FOREACH ( const QString &drvName, writableDrivers )
for ( const QString &drvName : qgis::as_const( writableDrivers ) )
{
MetaData metadata;
if ( driverMetadata( drvName, metadata ) && !metadata.trLongName.isEmpty() )
{
resultMap.insert( metadata.trLongName, drvName );
DriverDetails details;
details.driverName = drvName;
details.longName = metadata.trLongName;
results << details;
}
}
return resultMap;
return results;
}
QString QgsVectorFileWriter::driverForExtension( const QString &extension )
@ -2841,17 +2900,16 @@ QString QgsVectorFileWriter::driverForExtension( const QString &extension )
return QString();
}
QString QgsVectorFileWriter::fileFilterString()
QString QgsVectorFileWriter::fileFilterString( const VectorFormatOptions options )
{
QString filterString;
QMap< QString, QString> driverFormatMap = supportedFiltersAndFormats();
QMap< QString, QString>::const_iterator it = driverFormatMap.constBegin();
for ( ; it != driverFormatMap.constEnd(); ++it )
const auto driverFormats = supportedFiltersAndFormats( options );
for ( const FilterFormatDetails &details : driverFormats )
{
if ( !filterString.isEmpty() )
filterString += QLatin1String( ";;" );
filterString += it.key();
filterString += details.filterString;
}
return filterString;
}
@ -2860,9 +2918,11 @@ QString QgsVectorFileWriter::filterForDriver( const QString &driverName )
{
MetaData metadata;
if ( !driverMetadata( driverName, metadata ) || metadata.trLongName.isEmpty() || metadata.glob.isEmpty() )
return QLatin1String( "" );
return QString();
return metadata.trLongName + " [OGR] (" + metadata.glob.toLower() + ' ' + metadata.glob.toUpper() + ')';
return QStringLiteral( "%1 (%2 %3)" ).arg( metadata.trLongName,
metadata.glob.toLower(),
metadata.glob.toUpper() );
}
QString QgsVectorFileWriter::convertCodecNameForEncodingOption( const QString &codecName )

View File

@ -29,9 +29,6 @@
#include "qgsogrutils.h"
#include <ogr_api.h>
#include <QPair>
class QgsSymbolLayer;
class QTextCodec;
class QgsFeatureIterator;
@ -186,6 +183,18 @@ class CORE_EXPORT QgsVectorFileWriter : public QgsFeatureSink
SymbolLayerSymbology //Exports one feature per symbol layer (considering symbol levels)
};
/**
* Options for sorting and filtering vector formats.
* \since QGIS 3.0
*/
enum VectorFormatOption
{
SortRecommended = 1 << 1, //!< Use recommended sort order, with extremely commonly used formats listed first
SkipNonSpatialFormats = 1 << 2, //!< Filter out any formats which do not have spatial support (e.g. those which cannot save geometries)
};
Q_DECLARE_FLAGS( VectorFormatOptions, VectorFormatOption )
/**
* \ingroup core
* Interface to convert raw field values to their user-friendly value.
@ -496,24 +505,64 @@ class CORE_EXPORT QgsVectorFileWriter : public QgsFeatureSink
QgsVectorFileWriter &operator=( const QgsVectorFileWriter &rh ) = delete;
/**
* Returns a map with format filter string as key and OGR format key as value.
* Details of available filters and formats.
* \since QGIS 3.0
*/
struct FilterFormatDetails
{
//! Unique driver name
QString driverName;
//! Filter string for file picker dialogs
QString filterString;
};
/**
* Returns a list or pairs, with format filter string as first element and OGR format key as second element.
*
* The \a options argument can be used to control the sorting and filtering of
* returned formats.
*
* \see supportedOutputVectorLayerExtensions()
*/
static QMap< QString, QString> supportedFiltersAndFormats();
static QList< QgsVectorFileWriter::FilterFormatDetails > supportedFiltersAndFormats( VectorFormatOptions options = SortRecommended );
/**
* Returns a list of file extensions for supported formats.
*
* The \a options argument can be used to control the sorting and filtering of
* returned formats.
*
* \since QGIS 3.0
* \see supportedFiltersAndFormats()
*/
static QStringList supportedFormatExtensions();
static QStringList supportedFormatExtensions( VectorFormatOptions options = SortRecommended );
/**
* Returns driver list that can be used for dialogs. It contains all OGR drivers
* + some additional internal QGIS driver names to distinguish between more
* supported formats of the same OGR driver
* Details of available driver formats.
* \since QGIS 3.0
*/
static QMap< QString, QString> ogrDriverList();
struct DriverDetails
{
//! Descriptive, user friendly name for the driver
QString longName;
//! Unique driver name
QString driverName;
};
/**
* Returns the driver list that can be used for dialogs. It contains all OGR drivers
* plus some additional internal QGIS driver names to distinguish between more
* supported formats of the same OGR driver.
*
* The returned list consists of structs containing the driver long name (e.g. user-friendly
* display name for the format) and internal driver short name.
*
* The \a options argument can be used to control the sorting and filtering of
* returned drivers.
*/
static QList< QgsVectorFileWriter::DriverDetails > ogrDriverList( VectorFormatOptions options = SortRecommended );
/**
* Returns the OGR driver name for a specified file \a extension. E.g. the
@ -523,8 +572,13 @@ class CORE_EXPORT QgsVectorFileWriter : public QgsFeatureSink
*/
static QString driverForExtension( const QString &extension );
//! Returns filter string that can be used for dialogs
static QString fileFilterString();
/**
* Returns filter string that can be used for dialogs.
*
* The \a options argument can be used to control the sorting and filtering of
* returned drivers.
*/
static QString fileFilterString( VectorFormatOptions options = SortRecommended );
//! Creates a filter for an OGR driver key
static QString filterForDriver( const QString &driverName );
@ -701,6 +755,7 @@ class CORE_EXPORT QgsVectorFileWriter : public QgsFeatureSink
};
Q_DECLARE_OPERATORS_FOR_FLAGS( QgsVectorFileWriter::EditionCapabilities )
Q_DECLARE_OPERATORS_FOR_FLAGS( QgsVectorFileWriter::VectorFormatOptions )
// clazy:excludeall=qstring-allocations

View File

@ -87,11 +87,11 @@ void QgsVectorLayerSaveAsDialog::setup()
QgsSettings settings;
restoreGeometry( settings.value( QStringLiteral( "Windows/VectorLayerSaveAs/geometry" ) ).toByteArray() );
QMap<QString, QString> map = QgsVectorFileWriter::ogrDriverList();
const QList< QgsVectorFileWriter::DriverDetails > drivers = QgsVectorFileWriter::ogrDriverList();
mFormatComboBox->blockSignals( true );
for ( QMap< QString, QString>::const_iterator it = map.constBegin(); it != map.constEnd(); ++it )
for ( const QgsVectorFileWriter::DriverDetails &driver : drivers )
{
mFormatComboBox->addItem( it.key(), it.value() );
mFormatComboBox->addItem( driver.longName, driver.driverName );
}
QString format = settings.value( QStringLiteral( "UI/lastVectorFormat" ), "GPKG" ).toString();

View File

@ -54,10 +54,10 @@ QgsGeometryCheckerSetupTab::QgsGeometryCheckerSetupTab( QgisInterface *iface, QD
mAbortButton = new QPushButton( tr( "Abort" ) );
mRunButton->setEnabled( false );
QMap<QString, QString> filterFormatMap = QgsVectorFileWriter::supportedFiltersAndFormats();
for ( const QString &filter : filterFormatMap.keys() )
const auto filterFormatMap = QgsVectorFileWriter::supportedFiltersAndFormats( QgsVectorFileWriter::SortRecommended | QgsVectorFileWriter::SkipNonSpatialFormats );
for ( const QgsVectorFileWriter::FilterFormatDetails &filter : filterFormatMap )
{
QString driverName = filterFormatMap.value( filter );
QString driverName = filter.driverName;
ui.comboBoxOutputFormat->addItem( driverName );
if ( driverName == QLatin1String( "ESRI Shapefile" ) )
{
@ -216,13 +216,13 @@ void QgsGeometryCheckerSetupTab::validateInput()
void QgsGeometryCheckerSetupTab::selectOutputDirectory()
{
QString filterString = QgsVectorFileWriter::filterForDriver( QStringLiteral( "GPKG" ) );
QMap<QString, QString> filterFormatMap = QgsVectorFileWriter::supportedFiltersAndFormats();
for ( const QString &filter : filterFormatMap.keys() )
const auto filterFormatMap = QgsVectorFileWriter::supportedFiltersAndFormats( QgsVectorFileWriter::SortRecommended | QgsVectorFileWriter::SkipNonSpatialFormats );
for ( const QgsVectorFileWriter::FilterFormatDetails &filter : filterFormatMap )
{
QString driverName = filterFormatMap.value( filter );
QString driverName = filter.driverName;
if ( driverName != QLatin1String( "ESRI Shapefile" ) ) // Default entry, first in list (see above)
{
filterString += ";;" + filter;
filterString += ";;" + filter.filterString;
}
}
QString initialdir = ui.lineEditOutputDirectory->text();

View File

@ -723,12 +723,74 @@ class TestQgsVectorFileWriter(unittest.TestCase):
gdal.Unlink(filename)
def testSupportedFiltersAndFormat(self):
# test with formats in recommended order
formats = QgsVectorFileWriter.supportedFiltersAndFormats(QgsVectorFileWriter.SortRecommended)
self.assertEqual(formats[0].filterString, 'GeoPackage (*.gpkg *.GPKG)')
self.assertEqual(formats[0].driverName, 'GPKG')
self.assertEqual(formats[1].filterString, 'ESRI Shapefile (*.shp *.SHP)')
self.assertEqual(formats[1].driverName, 'ESRI Shapefile')
self.assertTrue('ODS' in [f.driverName for f in formats])
# alphabetical sorting
formats2 = QgsVectorFileWriter.supportedFiltersAndFormats(QgsVectorFileWriter.VectorFormatOptions())
self.assertTrue(formats2[0].driverName < formats2[1].driverName)
self.assertCountEqual([f.driverName for f in formats], [f.driverName for f in formats2])
self.assertNotEqual(formats2[0].driverName, 'GeoPackage')
# skip non-spatial
formats = QgsVectorFileWriter.supportedFiltersAndFormats(QgsVectorFileWriter.SkipNonSpatialFormats)
self.assertFalse('ODS' in [f.driverName for f in formats])
def testOgrDriverList(self):
# test with drivers in recommended order
drivers = QgsVectorFileWriter.ogrDriverList(QgsVectorFileWriter.SortRecommended)
self.assertEqual(drivers[0].longName, 'GeoPackage')
self.assertEqual(drivers[0].driverName, 'GPKG')
self.assertEqual(drivers[1].longName, 'ESRI Shapefile')
self.assertEqual(drivers[1].driverName, 'ESRI Shapefile')
self.assertTrue('ODS' in [f.driverName for f in drivers])
# alphabetical sorting
drivers2 = QgsVectorFileWriter.ogrDriverList(QgsVectorFileWriter.VectorFormatOptions())
self.assertTrue(drivers2[0].longName < drivers2[1].longName)
self.assertCountEqual([d.driverName for d in drivers], [d.driverName for d in drivers2])
self.assertNotEqual(drivers2[0].driverName, 'GPKG')
# skip non-spatial
formats = QgsVectorFileWriter.ogrDriverList(QgsVectorFileWriter.SkipNonSpatialFormats)
self.assertFalse('ODS' in [f.driverName for f in formats])
def testSupportedFormatExtensions(self):
formats = QgsVectorFileWriter.supportedFormatExtensions()
self.assertTrue('gpkg' in formats)
self.assertFalse('exe' in formats)
self.assertEqual(formats[0], 'gpkg')
self.assertEqual(formats[1], 'shp')
self.assertTrue('ods' in formats)
# alphabetical sorting
formats2 = QgsVectorFileWriter.supportedFormatExtensions(QgsVectorFileWriter.VectorFormatOptions())
self.assertTrue(formats2[0] < formats2[1])
self.assertCountEqual(formats, formats2)
self.assertNotEqual(formats2[0], 'gpkg')
formats = QgsVectorFileWriter.supportedFormatExtensions(QgsVectorFileWriter.SkipNonSpatialFormats)
self.assertFalse('ods' in formats)
def testFileFilterString(self):
formats = QgsVectorFileWriter.fileFilterString()
self.assertTrue('gpkg' in formats)
self.assertTrue('shp' in formats)
self.assertTrue(formats.index('gpkg') < formats.index('shp'))
self.assertTrue('ods' in formats)
# alphabetical sorting
formats2 = QgsVectorFileWriter.fileFilterString(QgsVectorFileWriter.VectorFormatOptions())
self.assertNotEqual(formats.index('gpkg'), formats2.index('gpkg'))
# hide non spatial
formats = QgsVectorFileWriter.fileFilterString(QgsVectorFileWriter.SkipNonSpatialFormats)
self.assertFalse('ods' in formats)
def testDriverForExtension(self):
self.assertEqual(QgsVectorFileWriter.driverForExtension('shp'), 'ESRI Shapefile')