Skip to content

Commit

Permalink
Merge pull request #5839 from alexbruy/raster-extensions
Browse files Browse the repository at this point in the history
Add methods to get supported raster formats and extensions to QgsRasterFileWriter
  • Loading branch information
alexbruy committed Dec 11, 2017
2 parents 61db97f + 33fdf8f commit 6b73f78
Show file tree
Hide file tree
Showing 8 changed files with 238 additions and 36 deletions.
50 changes: 50 additions & 0 deletions python/core/raster/qgsrasterfilewriter.sip
Expand Up @@ -35,6 +35,13 @@ class QgsRasterFileWriter
WriteCanceled,
};

enum RasterFormatOption
{
SortRecommended,
};
typedef QFlags<QgsRasterFileWriter::RasterFormatOption> RasterFormatOptions;


QgsRasterFileWriter( const QString &outputUrl );

QgsRasterDataProvider *createOneBandRaster( Qgis::DataType dataType,
Expand Down Expand Up @@ -156,6 +163,49 @@ class QgsRasterFileWriter
:rtype: list of str
%End

static QString filterForDriver( const QString &driverName );
%Docstring
Creates a filter for an GDAL driver key
:rtype: str
%End

struct FilterFormatDetails
{
QString driverName;
%Docstring
Unique driver name
%End

QString filterString;
%Docstring
Filter string for file picker dialogs
%End
};

static QList< QgsRasterFileWriter::FilterFormatDetails > supportedFiltersAndFormats( RasterFormatOptions options = SortRecommended );
%Docstring
Returns a list or pairs, with format filter string as first element and GDAL format key as second element.
Relies on GDAL_DMD_EXTENSIONS metadata, if it is empty corresponding driver will be skipped even if supported.

The ``options`` argument can be used to control the sorting and filtering of
returned formats.

.. seealso:: :py:func:`supportedOutputRasterLayerExtensions()`
:rtype: list of QgsRasterFileWriter.FilterFormatDetails
%End

static QStringList supportedFormatExtensions( RasterFormatOptions 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:: :py:func:`supportedFiltersAndFormats()`
:rtype: list of str
%End

static QString driverForExtension( const QString &extension );
%Docstring
Returns the GDAL driver name for a specified file ``extension``. E.g. the
Expand Down
5 changes: 3 additions & 2 deletions python/plugins/processing/core/ProcessingConfig.py
Expand Up @@ -31,7 +31,8 @@
from qgis.core import (NULL,
QgsApplication,
QgsSettings,
QgsVectorFileWriter)
QgsVectorFileWriter,
QgsRasterFileWriter)
from processing.tools.system import defaultOutputFolder
import processing.tools.dataobjects

Expand Down Expand Up @@ -170,7 +171,7 @@ def initialize():
valuetype=Setting.SELECTION,
options=extensions))

extensions = processing.tools.dataobjects.getSupportedOutputRasterLayerExtensions()
extensions = QgsRasterFileWriter.supportedFormatExtensions()
ProcessingConfig.addSetting(Setting(
ProcessingConfig.tr('General'),
ProcessingConfig.DEFAULT_OUTPUT_RASTER_LAYER_EXT,
Expand Down
12 changes: 9 additions & 3 deletions python/plugins/processing/gui/ParameterGuiUtils.py
Expand Up @@ -29,7 +29,8 @@
from qgis.core import (QgsProcessing,
QgsProviderRegistry,
QgsProcessingFeatureSourceDefinition,
QgsVectorFileWriter)
QgsVectorFileWriter,
QgsRasterFileWriter)
from qgis.PyQt.QtCore import QCoreApplication
from processing.tools import dataobjects

Expand All @@ -48,7 +49,7 @@ def getFileFilter(param):
"""
if param.type() == 'multilayer':
if param.layerType() == QgsProcessing.TypeRaster:
exts = dataobjects.getSupportedOutputRasterLayerExtensions()
exts = QgsRasterFileWriter.supportedFormatExtensions()
elif param.layerType() == QgsProcessing.TypeFile:
return tr('All files (*.*)', 'QgsProcessingParameterMultipleLayers')
else:
Expand All @@ -59,7 +60,12 @@ def getFileFilter(param):
elif param.type() == 'raster':
return QgsProviderRegistry.instance().fileRasterFilters()
elif param.type() == 'rasterDestination':
exts = dataobjects.getSupportedOutputRasterFilters()
if param.provider() is not None:
exts = param.provider().supportedOutputRasterLayerExtensions()
else:
exts = QgsRasterFileWriter.supportedFormatExtensions()
for i in range(len(exts)):
exts[i] = tr('{0} files (*.{1})', 'ParameterRaster').format(exts[i].upper(), exts[i].lower())
return ';;'.join(exts) + ';;' + tr('All files (*.*)')
elif param.type() in ('sink', 'vectorDestination'):
if param.provider() is not None:
Expand Down
29 changes: 0 additions & 29 deletions python/plugins/processing/tools/dataobjects.py
Expand Up @@ -105,35 +105,6 @@ def createExpressionContext():
return context


def getSupportedOutputRasterLayerExtensions():
allexts = []
for exts in list(GdalUtils.getSupportedRasters().values()):
for ext in exts:
if ext != 'tif' and ext not in allexts:
allexts.append(ext)
allexts.sort()
allexts.insert(0, 'tif') # tif is the default, should be the first
return allexts


def getSupportedOutputRasterFilters():
"""
Return a list of file filters for supported raster formats.
Supported formats come from Gdal.
:return: a list of strings for Qt file filters.
"""
allFilters = []
supported = GdalUtils.getSupportedOutputRasters()
formatList = sorted(supported.keys())
# Place GTiff as the first format
if 'GTiff' in formatList:
formatList.pop(formatList.index('GTiff'))
formatList.insert(0, 'GTiff')
for f in formatList:
allFilters.append('{0} files (*.{1})'.format(f, ' *.'.join(supported[f])))
return allFilters


def load(fileName, name=None, crs=None, style=None, isRaster=False):
"""Loads a layer/table into the current project, given its file.
"""
Expand Down
4 changes: 2 additions & 2 deletions src/core/processing/qgsprocessingprovider.cpp
Expand Up @@ -18,6 +18,7 @@
#include "qgsprocessingprovider.h"
#include "qgsapplication.h"
#include "qgsvectorfilewriter.h"
#include "qgsrasterfilewriter.h"
#include "qgssettings.h"

QgsProcessingProvider::QgsProcessingProvider( QObject *parent SIP_TRANSFERTHIS )
Expand Down Expand Up @@ -47,7 +48,7 @@ QString QgsProcessingProvider::longName() const

QStringList QgsProcessingProvider::supportedOutputRasterLayerExtensions() const
{
return QStringList() << QStringLiteral( "tif" );
return QgsRasterFileWriter::supportedFormatExtensions();
}

void QgsProcessingProvider::refreshAlgorithms()
Expand Down Expand Up @@ -136,4 +137,3 @@ QString QgsProcessingProvider::defaultRasterFileExtension() const
return defaultExtension;
}
}

100 changes: 100 additions & 0 deletions src/core/raster/qgsrasterfilewriter.cpp
Expand Up @@ -1023,3 +1023,103 @@ QStringList QgsRasterFileWriter::extensionsForFormat( const QString &format )
}
return QStringList();
}

QString QgsRasterFileWriter::filterForDriver( const QString &driverName )
{
GDALDriverH drv = GDALGetDriverByName( driverName.toLocal8Bit().data() );
if ( drv )
{
QString drvName = GDALGetDriverLongName( drv );
QString extensionsString = QString( GDALGetMetadataItem( drv, GDAL_DMD_EXTENSIONS, nullptr ) );
if ( extensionsString.isEmpty() )
{
return QString();
}
QStringList extensions = extensionsString.split( ' ' );
QString filter = drvName + " (";
for ( const QString &ext : extensions )
{
filter.append( QStringLiteral( "*.%1 *.%2 " ).arg( ext.toLower(), ext.toUpper() ) );
}
filter = filter.trimmed().append( QStringLiteral( ")" ) );
return filter;
}

return QString();
}

QList< QgsRasterFileWriter::FilterFormatDetails > QgsRasterFileWriter::supportedFiltersAndFormats( RasterFormatOptions options )
{
QList< FilterFormatDetails > results;

GDALAllRegister();
int const drvCount = GDALGetDriverCount();

FilterFormatDetails tifFormat;

for ( int i = 0; i < drvCount; ++i )
{
GDALDriverH drv = GDALGetDriver( i );
if ( drv )
{
QString drvName = GDALGetDriverShortName( drv );
char **driverMetadata = GDALGetMetadata( drv, nullptr );
if ( CSLFetchBoolean( driverMetadata, GDAL_DCAP_CREATE, false ) && CSLFetchBoolean( driverMetadata, GDAL_DCAP_RASTER, false ) )
{
QString filterString = filterForDriver( drvName );
if ( filterString.isEmpty() )
continue;

FilterFormatDetails details;
details.driverName = drvName;
details.filterString = filterString;

if ( options & SortRecommended )
{
if ( drvName == QLatin1String( "GTiff" ) )
{
tifFormat = details;
continue;
}
}

results << details;
}
}
}

std::sort( results.begin(), results.end(), []( const FilterFormatDetails & a, const FilterFormatDetails & b ) -> bool
{
return a.driverName < b.driverName;
} );

if ( options & SortRecommended )
{
if ( !tifFormat.filterString.isEmpty() )
{
results.insert( 0, tifFormat );
}
}

return results;
}

QStringList QgsRasterFileWriter::supportedFormatExtensions( const RasterFormatOptions options )
{
const auto formats = supportedFiltersAndFormats( options );
QStringList extensions;

QRegularExpression rx( QStringLiteral( "\\*\\.([a-zA-Z0-9]*)" ) );

for ( const FilterFormatDetails &format : formats )
{
QString ext = format.filterString;
QRegularExpressionMatch match = rx.match( ext );
if ( !match.hasMatch() )
continue;

QString matched = match.captured( 1 );
extensions << matched;
}
return extensions;
}
48 changes: 48 additions & 0 deletions src/core/raster/qgsrasterfilewriter.h
Expand Up @@ -54,6 +54,16 @@ class CORE_EXPORT QgsRasterFileWriter
WriteCanceled = 6, //!< Writing was manually canceled
};

/**
* Options for sorting and filtering raster formats.
* \since QGIS 3.0
*/
enum RasterFormatOption
{
SortRecommended = 1 << 1, //!< Use recommended sort order, with extremely commonly used formats listed first
};
Q_DECLARE_FLAGS( RasterFormatOptions, RasterFormatOption )

QgsRasterFileWriter( const QString &outputUrl );

/**
Expand Down Expand Up @@ -134,6 +144,44 @@ class CORE_EXPORT QgsRasterFileWriter
void setPyramidsConfigOptions( const QStringList &list ) { mPyramidsConfigOptions = list; }
QStringList pyramidsConfigOptions() const { return mPyramidsConfigOptions; }

//! Creates a filter for an GDAL driver key
static QString filterForDriver( const QString &driverName );

/**
* 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 GDAL format key as second element.
* Relies on GDAL_DMD_EXTENSIONS metadata, if it is empty corresponding driver will be skipped even if supported.
*
* The \a options argument can be used to control the sorting and filtering of
* returned formats.
*
* \see supportedOutputRasterLayerExtensions()
*/
static QList< QgsRasterFileWriter::FilterFormatDetails > supportedFiltersAndFormats( RasterFormatOptions 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( RasterFormatOptions options = SortRecommended );

/**
* Returns the GDAL driver name for a specified file \a extension. E.g. the
* driver name for the ".tif" extension is "GTiff".
Expand Down
26 changes: 26 additions & 0 deletions tests/src/python/test_qgsrasterfilewriter.py
Expand Up @@ -114,6 +114,32 @@ def testExtensionsForFormat(self):
self.assertCountEqual(QgsRasterFileWriter.extensionsForFormat('GTiff'), ['tiff', 'tif'])
self.assertCountEqual(QgsRasterFileWriter.extensionsForFormat('GPKG'), ['gpkg'])

def testSupportedFiltersAndFormat(self):
# test with formats in recommended order
formats = QgsRasterFileWriter.supportedFiltersAndFormats(QgsRasterFileWriter.SortRecommended)
self.assertEqual(formats[0].filterString, 'GeoTIFF (*.tif *.TIF *.tiff *.TIFF)')
self.assertEqual(formats[0].driverName, 'GTiff')
self.assertTrue('netCDF' in [f.driverName for f in formats])

# alphabetical sorting
formats2 = QgsRasterFileWriter.supportedFiltersAndFormats(QgsRasterFileWriter.RasterFormatOptions())
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, 'GTiff')

def testSupportedFormatExtensions(self):
formats = QgsRasterFileWriter.supportedFormatExtensions()
self.assertTrue('tif' in formats)
self.assertFalse('exe' in formats)
self.assertEqual(formats[0], 'tif')
self.assertTrue('nc' in formats)

# alphabetical sorting
formats2 = QgsRasterFileWriter.supportedFormatExtensions(QgsRasterFileWriter.RasterFormatOptions())
self.assertTrue(formats2[1] < formats2[2])
self.assertCountEqual(formats, formats2)
self.assertNotEqual(formats2[0], 'tif')

def testImportIntoGpkg(self):
# init target file
test_gpkg = tempfile.mktemp(suffix='.gpkg', dir=self.testDataDir)
Expand Down

0 comments on commit 6b73f78

Please sign in to comment.