Skip to content

Commit

Permalink
[FEATURE] More output format choices in raster save as dialog
Browse files Browse the repository at this point in the history
Previously only geotiff format was available, even though the
underlying QgsRasterFileWriter/GDAL libraries support other
formats.

This commit exposes those other formats to the dialog so that
users can directly save rasters to them (including everyone's
new BFF, geopackage).
  • Loading branch information
nyalldawson committed Dec 2, 2017
1 parent 6b23e1f commit 9e4518f
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 15 deletions.
11 changes: 11 additions & 0 deletions python/core/raster/qgsrasterfilewriter.sip
Expand Up @@ -165,6 +165,17 @@ class QgsRasterFileWriter
:rtype: str
%End

static QStringList extensionsForFormat( const QString &format );
%Docstring
Returns a list of known file extensions for the given GDAL driver ``format``.
E.g. returns "tif", "tiff" for the format "GTiff".

If no matching format driver is found an empty list will be returned.

.. versionadded:: 3.0
:rtype: list of str
%End

};

/************************************************************************
Expand Down
4 changes: 4 additions & 0 deletions src/app/qgisapp.cpp
Expand Up @@ -6750,6 +6750,10 @@ void QgisApp::saveAsRasterFile( QgsRasterLayer *rasterLayer )
fileWriter.setMaxTileWidth( d.maximumTileSizeX() );
fileWriter.setMaxTileHeight( d.maximumTileSizeY() );
}
else
{
fileWriter.setOutputFormat( d.outputFormat() );
}

// TODO: show error dialogs
// TODO: this code should go somewhere else, but probably not into QgsRasterFileWriter
Expand Down
14 changes: 14 additions & 0 deletions src/core/raster/qgsrasterfilewriter.cpp
Expand Up @@ -1009,3 +1009,17 @@ QString QgsRasterFileWriter::driverForExtension( const QString &extension )
}
return QString();
}

QStringList QgsRasterFileWriter::extensionsForFormat( const QString &format )
{
GDALDriverH drv = GDALGetDriverByName( format.toLocal8Bit().data() );
if ( drv )
{
char **driverMetadata = GDALGetMetadata( drv, nullptr );
if ( CSLFetchBoolean( driverMetadata, GDAL_DCAP_CREATE, false ) && CSLFetchBoolean( driverMetadata, GDAL_DCAP_RASTER, false ) )
{
return QString( GDALGetMetadataItem( drv, GDAL_DMD_EXTENSIONS, nullptr ) ).split( ' ' );
}
}
return QStringList();
}
10 changes: 10 additions & 0 deletions src/core/raster/qgsrasterfilewriter.h
Expand Up @@ -142,6 +142,16 @@ class CORE_EXPORT QgsRasterFileWriter
*/
static QString driverForExtension( const QString &extension );

/**
* Returns a list of known file extensions for the given GDAL driver \a format.
* E.g. returns "tif", "tiff" for the format "GTiff".
*
* If no matching format driver is found an empty list will be returned.
*
* \since QGIS 3.0
*/
static QStringList extensionsForFormat( const QString &format );

private:
QgsRasterFileWriter(); //forbidden
WriterError writeDataRaster( const QgsRasterPipe *pipe, QgsRasterIterator *iter, int nCols, int nRows, const QgsRectangle &outputExtent,
Expand Down
101 changes: 86 additions & 15 deletions src/gui/qgsrasterlayersaveasdialog.cpp
Expand Up @@ -23,7 +23,8 @@
#include "qgsrastertransparency.h"
#include "qgsprojectionselectiondialog.h"
#include "qgssettings.h"

#include "qgsrasterfilewriter.h"
#include "cpl_string.h"
#include <gdal.h>

#include <QFileDialog>
Expand Down Expand Up @@ -74,13 +75,7 @@ QgsRasterLayerSaveAsDialog::QgsRasterLayerSaveAsDialog( QgsRasterLayer *rasterLa

toggleResolutionSize();

//only one hardcoded format at the moment
QStringList myFormats;
myFormats << QStringLiteral( "GTiff" );
Q_FOREACH ( const QString &myFormat, myFormats )
{
mFormatComboBox->addItem( myFormat );
}
insertAvailableOutputFormats();

//fill reasonable default values depending on the provider
if ( mDataProvider )
Expand All @@ -104,7 +99,7 @@ QgsRasterLayerSaveAsDialog::QgsRasterLayerSaveAsDialog( QgsRasterLayer *rasterLa
mCreateOptionsWidget->setProvider( mDataProvider->name() );
if ( mDataProvider->name() == QLatin1String( "gdal" ) )
{
mCreateOptionsWidget->setFormat( myFormats[0] );
mCreateOptionsWidget->setFormat( mFormatComboBox->currentData().toString() );
}
mCreateOptionsWidget->setRasterLayer( mRasterLayer );
mCreateOptionsWidget->update();
Expand Down Expand Up @@ -165,6 +160,68 @@ QgsRasterLayerSaveAsDialog::QgsRasterLayerSaveAsDialog( QgsRasterLayer *rasterLa
recalcResolutionSize();
}

void QgsRasterLayerSaveAsDialog::insertAvailableOutputFormats()
{
GDALAllRegister();

int nDrivers = GDALGetDriverCount();
QMap< int, QPair< QString, QString > > topPriorityDrivers;
QMap< QString, QString > lowPriorityDrivers;

for ( int i = 0; i < nDrivers; ++i )
{
GDALDriverH driver = GDALGetDriver( i );
if ( driver )
{
char **driverMetadata = GDALGetMetadata( driver, nullptr );

if ( CSLFetchBoolean( driverMetadata, GDAL_DCAP_CREATE, false ) && CSLFetchBoolean( driverMetadata, GDAL_DCAP_RASTER, false ) )
{
QString driverShortName = GDALGetDriverShortName( driver );
QString driverLongName = GDALGetDriverLongName( driver );
if ( driverShortName == QLatin1String( "MEM" ) )
{
// in memory rasters are not (yet) supported because the GDAL dataset handle
// would need to be passed directly to QgsRasterLayer (it is not possible to
// close it in raster calculator and reopen the dataset again in raster layer)
continue;
}
else if ( driverShortName == QLatin1String( "VRT" ) )
{
// skip GDAL vrt driver, since we handle that format manually
continue;
}
else if ( driverShortName == QStringLiteral( "GTiff" ) )
{
// always list geotiff first
topPriorityDrivers.insert( 1, qMakePair( driverLongName, driverShortName ) );
}
else if ( driverShortName == QStringLiteral( "GPKG" ) )
{
// and gpkg second
topPriorityDrivers.insert( 2, qMakePair( driverLongName, driverShortName ) );
}
else
{
lowPriorityDrivers.insert( driverLongName, driverShortName );
}
}
}
}

// will be sorted by priority, so that geotiff and geopackage are listed first
for ( auto priorityDriversIt = topPriorityDrivers.constBegin(); priorityDriversIt != topPriorityDrivers.constEnd(); ++priorityDriversIt )
{
mFormatComboBox->addItem( priorityDriversIt.value().first, priorityDriversIt.value().second );
}
// will be sorted by driver name
for ( auto lowPriorityDriversIt = lowPriorityDrivers.constBegin(); lowPriorityDriversIt != lowPriorityDrivers.constEnd(); ++lowPriorityDriversIt )
{
mFormatComboBox->addItem( lowPriorityDriversIt.key(), lowPriorityDriversIt.value() );
}

}

void QgsRasterLayerSaveAsDialog::setValidators()
{
mXResolutionLineEdit->setValidator( new QDoubleValidator( this ) );
Expand Down Expand Up @@ -212,12 +269,26 @@ void QgsRasterLayerSaveAsDialog::mBrowseButton_clicked()
}
else
{
fileName = QFileDialog::getSaveFileName( this, tr( "Select output file" ), dirName, tr( "GeoTIFF" ) + " (*.tif *.tiff *.TIF *.TIFF)" );
QStringList extensions = QgsRasterFileWriter::extensionsForFormat( outputFormat() );
QString filter;
QString defaultExt;
if ( extensions.empty() )
filter = tr( "All files (*.*)" );
else
{
filter = QStringLiteral( "%1 (*.%2);;%3" ).arg( mFormatComboBox->currentText(),
extensions.join( QStringLiteral( " *." ) ),
tr( "All files (*.*)" ) );
defaultExt = extensions.at( 0 );
}

fileName = QFileDialog::getSaveFileName( this, tr( "Select output file" ), dirName, filter );

// ensure the user never omits the extension from the file name
if ( !fileName.isEmpty() && !fileName.endsWith( QLatin1String( ".tif" ), Qt::CaseInsensitive ) && !fileName.endsWith( QLatin1String( ".tiff" ), Qt::CaseInsensitive ) )
QFileInfo fi( fileName );
if ( !fileName.isEmpty() && fi.suffix().isEmpty() )
{
fileName += QLatin1String( ".tif" );
fileName += '.' + defaultExt;
}
}

Expand All @@ -239,12 +310,12 @@ void QgsRasterLayerSaveAsDialog::mSaveAsLineEdit_textChanged( const QString &tex
}


void QgsRasterLayerSaveAsDialog::mFormatComboBox_currentIndexChanged( const QString &text )
void QgsRasterLayerSaveAsDialog::mFormatComboBox_currentIndexChanged( const QString & )
{
//gdal-specific
if ( mDataProvider && mDataProvider->name() == QLatin1String( "gdal" ) )
{
mCreateOptionsWidget->setFormat( text );
mCreateOptionsWidget->setFormat( outputFormat() );
mCreateOptionsWidget->update();
}
}
Expand Down Expand Up @@ -296,7 +367,7 @@ QString QgsRasterLayerSaveAsDialog::outputFileName() const

QString QgsRasterLayerSaveAsDialog::outputFormat() const
{
return mFormatComboBox->currentText();
return mFormatComboBox->currentData().toString();
}

QStringList QgsRasterLayerSaveAsDialog::createOptions() const
Expand Down
1 change: 1 addition & 0 deletions src/gui/qgsrasterlayersaveasdialog.h
Expand Up @@ -139,6 +139,7 @@ class GUI_EXPORT QgsRasterLayerSaveAsDialog: public QDialog, private Ui::QgsRast
void adjustNoDataCellWidth( int row, int column );
bool validate() const;

void insertAvailableOutputFormats();
};


Expand Down
5 changes: 5 additions & 0 deletions tests/src/python/test_qgsrasterfilewriter.py
Expand Up @@ -109,6 +109,11 @@ def testDriverForExtension(self):
self.assertEqual(QgsRasterFileWriter.driverForExtension('not a format'), '')
self.assertEqual(QgsRasterFileWriter.driverForExtension(''), '')

def testExtensionsForFormat(self):
self.assertCountEqual(QgsRasterFileWriter.extensionsForFormat('not format'), [])
self.assertCountEqual(QgsRasterFileWriter.extensionsForFormat('GTiff'), ['tiff', 'tif'])
self.assertCountEqual(QgsRasterFileWriter.extensionsForFormat('GPKG'), ['gpkg'])

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

0 comments on commit 9e4518f

Please sign in to comment.