Skip to content

Commit

Permalink
Add more descriptive error messages for raster calculation failure
Browse files Browse the repository at this point in the history
With unit tests
  • Loading branch information
nyalldawson committed Sep 25, 2018
1 parent f4bbb14 commit fd1d6c1
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 27 deletions.
21 changes: 17 additions & 4 deletions python/analysis/auto_generated/raster/qgsrastercalculator.sip.in
Expand Up @@ -18,9 +18,6 @@ Represents an individual raster layer/band number entry within a raster calculat

%TypeHeaderCode
#include "qgsrastercalculator.h"
%TypeHeaderCode
#include <qgsrastercalculator.h>
%End
%End
public:

Expand Down Expand Up @@ -50,6 +47,7 @@ Performs raster layer calculations.
Canceled,
ParserError,
MemoryError,
BandError,
};

QgsRasterCalculator( const QString &formulaString, const QString &outputFile, const QString &outputFormat,
Expand Down Expand Up @@ -83,7 +81,22 @@ QgsRasterCalculator constructor.
.. versionadded:: 2.10
%End

int processCalculation( QgsFeedback *feedback = 0 );
Result processCalculation( QgsFeedback *feedback = 0 );
%Docstring
Starts the calculation and writes a new raster.

The optional ``feedback`` argument can be used for progress reporting and cancelation support.

:return: QgsRasterCalculator.Success in case of success. If an error is encountered then
a description of the error can be obtained by calling lastError().
%End

QString lastError() const;
%Docstring
Returns a description of the last error encountered.

.. versionadded:: 3.4
%End

};

Expand Down
39 changes: 28 additions & 11 deletions src/analysis/raster/qgsrastercalculator.cpp
Expand Up @@ -58,15 +58,16 @@ QgsRasterCalculator::QgsRasterCalculator( const QString &formulaString, const QS
{
}

int QgsRasterCalculator::processCalculation( QgsFeedback *feedback )
QgsRasterCalculator::Result QgsRasterCalculator::processCalculation( QgsFeedback *feedback )
{
mLastError.clear();

//prepare search string / tree
QString errorString;
std::unique_ptr< QgsRasterCalcNode > calcNode( QgsRasterCalcNode::parseRasterCalcString( mFormulaString, errorString ) );
std::unique_ptr< QgsRasterCalcNode > calcNode( QgsRasterCalcNode::parseRasterCalcString( mFormulaString, mLastError ) );
if ( !calcNode )
{
//error
return static_cast<int>( ParserError );
return ParserError;
}

QMap< QString, QgsRasterBlock * > inputBlocks;
Expand All @@ -75,8 +76,16 @@ int QgsRasterCalculator::processCalculation( QgsFeedback *feedback )
{
if ( !it->raster ) // no raster layer in entry
{
mLastError = QObject::tr( "No raster layer for entry %1" ).arg( it->ref );
qDeleteAll( inputBlocks );
return InputLayerError;
}

if ( it->bandNumber <= 0 || it->bandNumber > it->raster->bandCount() )
{
mLastError = QObject::tr( "Band number %1 is not valid for entry %2" ).arg( it->bandNumber ).arg( it->ref );
qDeleteAll( inputBlocks );
return static_cast< int >( InputLayerError );
return BandError;
}

std::unique_ptr< QgsRasterBlock > block;
Expand All @@ -94,7 +103,7 @@ int QgsRasterCalculator::processCalculation( QgsFeedback *feedback )
if ( rasterBlockFeedback->isCanceled() )
{
qDeleteAll( inputBlocks );
return static_cast< int >( Canceled );
return Canceled;
}
}
else
Expand All @@ -103,8 +112,9 @@ int QgsRasterCalculator::processCalculation( QgsFeedback *feedback )
}
if ( block->isEmpty() )
{
mLastError = QObject::tr( "Could not allocate required memory for %1" ).arg( it->ref );
qDeleteAll( inputBlocks );
return static_cast<int>( MemoryError );
return MemoryError;
}
inputBlocks.insert( it->ref, block.release() );
}
Expand All @@ -113,13 +123,15 @@ int QgsRasterCalculator::processCalculation( QgsFeedback *feedback )
GDALDriverH outputDriver = openOutputDriver();
if ( !outputDriver )
{
return static_cast< int >( CreateOutputError );
mLastError = QObject::tr( "Could not obtain driver for %1" ).arg( mOutputFormat );
return CreateOutputError;
}

gdal::dataset_unique_ptr outputDataset( openOutputFile( outputDriver ) );
if ( !outputDataset )
{
return static_cast< int >( CreateOutputError );
mLastError = QObject::tr( "Could not create output %1" ).arg( mOutputFile );
return CreateOutputError;
}

GDALSetProjection( outputDataset.get(), mOutputCrs.toWkt().toLocal8Bit().data() );
Expand Down Expand Up @@ -179,9 +191,9 @@ int QgsRasterCalculator::processCalculation( QgsFeedback *feedback )
{
//delete the dataset without closing (because it is faster)
gdal::fast_delete_and_close( outputDataset, outputDriver, mOutputFile );
return static_cast< int >( Canceled );
return Canceled;
}
return static_cast< int >( Success );
return Success;
}

GDALDriverH QgsRasterCalculator::openOutputDriver()
Expand Down Expand Up @@ -229,3 +241,8 @@ void QgsRasterCalculator::outputGeoTransform( double *transform ) const
transform[4] = 0;
transform[5] = -mOutputRectangle.height() / mNumOutputRows;
}

QString QgsRasterCalculator::lastError() const
{
return mLastError;
}
21 changes: 13 additions & 8 deletions src/analysis/raster/qgsrastercalculator.h
Expand Up @@ -36,11 +36,6 @@ class QgsFeedback;
*/
class ANALYSIS_EXPORT QgsRasterCalculatorEntry
{
#ifdef SIP_RUN
% TypeHeaderCode
#include <qgsrastercalculator.h>
% End
#endif

public:

Expand Down Expand Up @@ -77,6 +72,7 @@ class ANALYSIS_EXPORT QgsRasterCalculator
Canceled = 3, //!< User canceled calculation
ParserError = 4, //!< Error parsing formula
MemoryError = 5, //!< Error allocating memory for result
BandError = 6, //!< Invalid band number for input
};

/**
Expand Down Expand Up @@ -111,10 +107,17 @@ class ANALYSIS_EXPORT QgsRasterCalculator
* Starts the calculation and writes a new raster.
*
* The optional \a feedback argument can be used for progress reporting and cancelation support.
* \returns 0 in case of success
*
* \returns QgsRasterCalculator::Success in case of success. If an error is encountered then
* a description of the error can be obtained by calling lastError().
*/
//TODO QGIS 3.0 - return QgsRasterCalculator::Result
int processCalculation( QgsFeedback *feedback = nullptr );
Result processCalculation( QgsFeedback *feedback = nullptr );

/**
* Returns a description of the last error encountered.
* \since QGIS 3.4
*/
QString lastError() const;

private:
//default constructor forbidden. We need formula, output file, output format and output raster resolution obligatory
Expand Down Expand Up @@ -148,6 +151,8 @@ class ANALYSIS_EXPORT QgsRasterCalculator
//! Number of output rows
int mNumOutputRows = 0;

QString mLastError;

/***/
QVector<QgsRasterCalculatorEntry> mRasterEntries;
};
Expand Down
5 changes: 5 additions & 0 deletions src/app/qgisapp.cpp
Expand Up @@ -5593,6 +5593,11 @@ void QgisApp::showRasterCalculator()
Qgis::Critical );
break;

case QgsRasterCalculator::BandError:
messageBar()->pushMessage( tr( "Raster calculator" ),
tr( "Invalid band number for input layer." ),
Qgis::Critical );
break;
}
}
}
Expand Down
90 changes: 86 additions & 4 deletions tests/src/analysis/testqgsrastercalculator.cpp
Expand Up @@ -54,6 +54,8 @@ class TestQgsRasterCalculator : public QObject
void calcWithLayers();
void calcWithReprojectedLayers();

void errors();

private:

QgsRasterLayer *mpLandsatRasterLayer = nullptr;
Expand Down Expand Up @@ -445,7 +447,7 @@ void TestQgsRasterCalculator::calcWithLayers()
tmpName,
QStringLiteral( "GTiff" ),
extent, crs, 2, 3, entries );
QCOMPARE( rc.processCalculation(), 0 );
QCOMPARE( static_cast< int >( rc.processCalculation() ), 0 );

//open output file and check results
QgsRasterLayer *result = new QgsRasterLayer( tmpName, QStringLiteral( "result" ) );
Expand All @@ -466,7 +468,7 @@ void TestQgsRasterCalculator::calcWithLayers()
tmpName,
QStringLiteral( "GTiff" ),
extent, crs, 2, 3, entries );
QCOMPARE( rc2.processCalculation(), 0 );
QCOMPARE( static_cast< int >( rc2.processCalculation() ), 0 );

//open output file and check results
result = new QgsRasterLayer( tmpName, QStringLiteral( "result" ) );
Expand Down Expand Up @@ -503,15 +505,15 @@ void TestQgsRasterCalculator::calcWithReprojectedLayers()
QgsRectangle extent( 783235, 3348110, 783350, 3347960 );

QTemporaryFile tmpFile;
tmpFile.open(); // fileName is no avialable until open
tmpFile.open(); // fileName is not available until open
QString tmpName = tmpFile.fileName();
tmpFile.close();

QgsRasterCalculator rc( QStringLiteral( "\"landsat@1\" + \"landsat_4326@2\"" ),
tmpName,
QStringLiteral( "GTiff" ),
extent, crs, 2, 3, entries );
QCOMPARE( rc.processCalculation(), 0 );
QCOMPARE( static_cast< int >( rc.processCalculation() ), 0 );

//open output file and check results
QgsRasterLayer *result = new QgsRasterLayer( tmpName, QStringLiteral( "result" ) );
Expand All @@ -528,5 +530,85 @@ void TestQgsRasterCalculator::calcWithReprojectedLayers()
delete block;
}

void TestQgsRasterCalculator::errors()
{
QgsRasterCalculatorEntry entry1;
entry1.bandNumber = 0; // bad band
entry1.raster = mpLandsatRasterLayer;
entry1.ref = QStringLiteral( "landsat@0" );

QVector<QgsRasterCalculatorEntry> entries;
entries << entry1;

QgsCoordinateReferenceSystem crs;
crs.createFromId( 32633, QgsCoordinateReferenceSystem::EpsgCrsId );
QgsRectangle extent( 783235, 3348110, 783350, 3347960 );

QTemporaryFile tmpFile;
tmpFile.open(); // fileName is not available until open
QString tmpName = tmpFile.fileName();
tmpFile.close();

QgsRasterCalculator rc( QStringLiteral( "\"landsat@0\"" ),
tmpName,
QStringLiteral( "GTiff" ),
extent, crs, 2, 3, entries );
QCOMPARE( static_cast< int >( rc.processCalculation() ), 6 );
QCOMPARE( rc.lastError(), QStringLiteral( "Band number 0 is not valid for entry landsat@0" ) );

entry1.bandNumber = 10; // bad band
entries.clear();
entries << entry1;
rc = QgsRasterCalculator( QStringLiteral( "\"landsat@0\"" ),
tmpName,
QStringLiteral( "GTiff" ),
extent, crs, 2, 3, entries );
QCOMPARE( static_cast< int >( rc.processCalculation() ), 6 );
QCOMPARE( rc.lastError(), QStringLiteral( "Band number 10 is not valid for entry landsat@0" ) );


// no raster
entry1.raster = nullptr;
entry1.bandNumber = 1;
entries.clear();
entries << entry1;
rc = QgsRasterCalculator( QStringLiteral( "\"landsat@0\"" ),
tmpName,
QStringLiteral( "GTiff" ),
extent, crs, 2, 3, entries );
QCOMPARE( static_cast< int >( rc.processCalculation() ), 2 );
QCOMPARE( rc.lastError(), QStringLiteral( "No raster layer for entry landsat@0" ) );

// bad driver
entry1.raster = mpLandsatRasterLayer;
entry1.bandNumber = 1;
entries.clear();
entries << entry1;
rc = QgsRasterCalculator( QStringLiteral( "\"landsat@0\"" ),
tmpName,
QStringLiteral( "xxxxx" ),
extent, crs, 2, 3, entries );
QCOMPARE( static_cast< int >( rc.processCalculation() ), 1 );
QCOMPARE( rc.lastError(), QStringLiteral( "Could not obtain driver for xxxxx" ) );

// bad filename
rc = QgsRasterCalculator( QStringLiteral( "\"landsat@0\"" ),
QStringLiteral( "/goodluckwritinghere/blah/blah.tif" ),
QStringLiteral( "GTiff" ),
extent, crs, 2, 3, entries );
QCOMPARE( static_cast< int >( rc.processCalculation() ), 1 );
QCOMPARE( rc.lastError(), QStringLiteral( "Could not create output /goodluckwritinghere/blah/blah.tif" ) );

// cancelled
QgsFeedback feedback;
feedback.cancel();
rc = QgsRasterCalculator( QStringLiteral( "\"landsat@0\"" ),
tmpName,
QStringLiteral( "GTiff" ),
extent, crs, 2, 3, entries );
QCOMPARE( static_cast< int >( rc.processCalculation( &feedback ) ), 3 );
QVERIFY( rc.lastError().isEmpty() );
}

QGSTEST_MAIN( TestQgsRasterCalculator )
#include "testqgsrastercalculator.moc"

0 comments on commit fd1d6c1

Please sign in to comment.