Skip to content

Commit 559d7bb

Browse files
committed
[rastercalc] Rework raster calculator to use QGIS raster classes
...rather than reading input layers directly through GDAL. Benefits include more robust handling of nodata/data type conversions, less code duplication, also being able to take advantage of features in QGIS raster code like handling gain/offset in rasters. (fix #12450) Also, add a choice of output projection to the raster calculator. Previously the output CRS would be taken from the first raster, with no guarantees that the output extent matched the output CRS. This resulted in empty/misplaced rasters. (fix #3649)
1 parent e1f7d33 commit 559d7bb

14 files changed

+486
-232
lines changed

python/analysis/raster/qgsrastercalcnode.sip

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,21 @@ class QgsRasterCalcNode
5454
void setRight( QgsRasterCalcNode* right );
5555

5656
/**Calculates result (might be real matrix or single number)*/
57-
bool calculate( QMap<QString, QgsRasterMatrix*>& rasterData, QgsRasterMatrix& result ) const;
57+
// bool calculate( QMap<QString, QgsRasterCalculateInputMatrix*>& rasterData, QgsRasterMatrix& result ) const;
58+
59+
/**Calculates result of raster calculation (might be real matrix or single number).
60+
* @param rasterData input raster data references, map of raster name to raster data block
61+
* @param result destination raster matrix for calculation results
62+
* @param row optional row number to calculate for calculating result by rows, or -1 to
63+
* calculate entire result
64+
* @note added in QGIS 2.10
65+
* @note not available in Python bindings
66+
*/
67+
//bool calculate( QMap<QString, QgsRasterBlock* >& rasterData, QgsRasterMatrix& result, int row = -1 ) const;
68+
69+
/**@deprecated use method which accepts QgsRasterBlocks instead
70+
*/
71+
// bool calculate( QMap<QString, QgsRasterMatrix*>& rasterData, QgsRasterMatrix& result ) const /Deprecated/;
5872

5973
static QgsRasterCalcNode* parseRasterCalcString( const QString& str, QString& parserErrorMsg ) /Factory/;
6074
};

python/analysis/raster/qgsrastercalculator.sip

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,33 @@ class QgsRasterCalculator
1717
%End
1818

1919
public:
20+
21+
/** QgsRasterCalculator constructor.
22+
* @param formulaString formula for raster calculation
23+
* @param outputFile output file path
24+
* @param outputFormat output file format
25+
* @param outputExtent output extent. CRS for output is taken from first entry in rasterEntries.
26+
* @param nOutputColumns number of columns in output raster
27+
* @param nOutputRows number of rows in output raster
28+
* @param rasterEntries list of referenced raster layers
29+
*/
2030
QgsRasterCalculator( const QString& formulaString, const QString& outputFile, const QString& outputFormat,
2131
const QgsRectangle& outputExtent, int nOutputColumns, int nOutputRows, const QVector<QgsRasterCalculatorEntry>& rasterEntries );
32+
33+
/** QgsRasterCalculator constructor.
34+
* @param formulaString formula for raster calculation
35+
* @param outputFile output file path
36+
* @param outputFormat output file format
37+
* @param outputExtent output extent, CRS is specifed by outputCrs parameter
38+
* @param outputCrs destination CRS for output raster
39+
* @param nOutputColumns number of columns in output raster
40+
* @param nOutputRows number of rows in output raster
41+
* @param rasterEntries list of referenced raster layers
42+
* @note added in QGIS 2.10
43+
*/
44+
QgsRasterCalculator( const QString& formulaString, const QString& outputFile, const QString& outputFormat,
45+
const QgsRectangle& outputExtent, const QgsCoordinateReferenceSystem& outputCrs, int nOutputColumns, int nOutputRows, const QVector<QgsRasterCalculatorEntry>& rasterEntries );
46+
2247
~QgsRasterCalculator();
2348

2449
/**Starts the calculation and writes new raster

python/core/raster/qgsrasterblock.sip

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,5 +266,17 @@ class QgsRasterBlock
266266
* @return the rectangle covered by sub extent
267267
*/
268268
static QRect subRect( const QgsRectangle &theExtent, int theWidth, int theHeight, const QgsRectangle &theSubExtent );
269+
270+
/** Returns the width (number of columns) of the raster block.
271+
* @see height
272+
* @note added in QGIS 2.10
273+
*/
274+
int width() const;
275+
276+
/** Returns the height (number of rows) of the raster block.
277+
* @see width
278+
* @note added in QGIS 2.10
279+
*/
280+
int height() const;
269281
};
270282

src/analysis/raster/qgsrastercalcnode.cpp

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
* *
1414
***************************************************************************/
1515
#include "qgsrastercalcnode.h"
16+
#include "qgsrasterblock.h"
1617
#include <cfloat>
1718

1819
QgsRasterCalcNode::QgsRasterCalcNode()
@@ -82,26 +83,58 @@ QgsRasterCalcNode::~QgsRasterCalcNode()
8283
}
8384

8485
bool QgsRasterCalcNode::calculate( QMap<QString, QgsRasterMatrix*>& rasterData, QgsRasterMatrix& result ) const
86+
{
87+
//deprecated method
88+
//convert QgsRasterMatrix to QgsRasterBlock and call replacement method
89+
QMap<QString, QgsRasterBlock* > rasterBlockData;
90+
QMap<QString, QgsRasterMatrix*>::const_iterator it = rasterData.constBegin();
91+
for ( ; it != rasterData.constEnd(); ++it )
92+
{
93+
QgsRasterBlock* block = new QgsRasterBlock( QGis::Float32, it.value()->nColumns(), it.value()->nRows(), it.value()->nodataValue() );
94+
for ( int row = 0; row < it.value()->nRows(); ++row )
95+
{
96+
for ( int col = 0; col < it.value()->nColumns(); ++col )
97+
{
98+
block->setValue( row, col, it.value()->data()[ row * it.value()->nColumns() + col ] );
99+
}
100+
}
101+
rasterBlockData.insert( it.key(), block );
102+
}
103+
104+
return calculate( rasterBlockData, result );
105+
}
106+
107+
bool QgsRasterCalcNode::calculate( QMap<QString, QgsRasterBlock* >& rasterData, QgsRasterMatrix& result, int row ) const
85108
{
86109
//if type is raster ref: return a copy of the corresponding matrix
87110

88111
//if type is operator, call the proper matrix operations
89112
if ( mType == tRasterRef )
90113
{
91-
QMap<QString, QgsRasterMatrix*>::iterator it = rasterData.find( mRasterName );
114+
QMap<QString, QgsRasterBlock*>::iterator it = rasterData.find( mRasterName );
92115
if ( it == rasterData.end() )
93116
{
94117
return false;
95118
}
96119

97-
int nEntries = ( *it )->nColumns() * ( *it )->nRows();
120+
int nRows = ( row >= 0 ? 1 : ( *it )->height() );
121+
int startRow = ( row >= 0 ? row : 0 );
122+
int endRow = startRow + nRows;
123+
int nCols = ( *it )->width();
124+
int nEntries = nCols * nRows;
98125
double* data = new double[nEntries];
99126

100-
for ( int i = 0; i < nEntries; ++i )
127+
//convert input raster values to double, also convert input no data to result no data
128+
129+
int outRow = 0;
130+
for ( int dataRow = startRow; dataRow < endRow ; ++dataRow, ++outRow )
101131
{
102-
data[i] = ( *it )->data()[i] == ( *it )->nodataValue() ? result.nodataValue() : ( *it )->data()[i];
132+
for ( int dataCol = 0; dataCol < nCols; ++dataCol )
133+
{
134+
data[ dataCol + nCols * outRow] = ( *it )->isNoData( dataRow , dataCol ) ? result.nodataValue() : ( *it )->value( dataRow, dataCol );
135+
}
103136
}
104-
result.setData(( *it )->nColumns(), ( *it )->nRows(), data, result.nodataValue() );
137+
result.setData( nCols, nRows, data, result.nodataValue() );
105138
return true;
106139
}
107140
else if ( mType == tOperator )
@@ -110,11 +143,11 @@ bool QgsRasterCalcNode::calculate( QMap<QString, QgsRasterMatrix*>& rasterData,
110143
leftMatrix.setNodataValue( result.nodataValue() );
111144
rightMatrix.setNodataValue( result.nodataValue() );
112145

113-
if ( !mLeft || !mLeft->calculate( rasterData, leftMatrix ) )
146+
if ( !mLeft || !mLeft->calculate( rasterData, leftMatrix, row ) )
114147
{
115148
return false;
116149
}
117-
if ( mRight && !mRight->calculate( rasterData, rightMatrix ) )
150+
if ( mRight && !mRight->calculate( rasterData, rightMatrix, row ) )
118151
{
119152
return false;
120153
}

src/analysis/raster/qgsrastercalcnode.h

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
#include <QMap>
2424
#include <QString>
2525

26+
class QgsRasterBlock;
27+
2628
class ANALYSIS_EXPORT QgsRasterCalcNode
2729
{
2830
public:
@@ -77,8 +79,19 @@ class ANALYSIS_EXPORT QgsRasterCalcNode
7779
void setLeft( QgsRasterCalcNode* left ) { delete mLeft; mLeft = left; }
7880
void setRight( QgsRasterCalcNode* right ) { delete mRight; mRight = right; }
7981

80-
/**Calculates result (might be real matrix or single number)*/
81-
bool calculate( QMap<QString, QgsRasterMatrix*>& rasterData, QgsRasterMatrix& result ) const;
82+
/**Calculates result of raster calculation (might be real matrix or single number).
83+
* @param rasterData input raster data references, map of raster name to raster data block
84+
* @param result destination raster matrix for calculation results
85+
* @param row optional row number to calculate for calculating result by rows, or -1 to
86+
* calculate entire result
87+
* @note added in QGIS 2.10
88+
* @note not available in Python bindings
89+
*/
90+
bool calculate( QMap<QString, QgsRasterBlock* >& rasterData, QgsRasterMatrix& result, int row = -1 ) const;
91+
92+
/**@deprecated use method which accepts QgsRasterBlocks instead
93+
*/
94+
Q_DECL_DEPRECATED bool calculate( QMap<QString, QgsRasterMatrix*>& rasterData, QgsRasterMatrix& result ) const;
8295

8396
static QgsRasterCalcNode* parseRasterCalcString( const QString& str, QString& parserErrorMsg );
8497

src/analysis/raster/qgsrastercalculator.cpp

Lines changed: 35 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,24 @@ QgsRasterCalculator::QgsRasterCalculator( const QString& formulaString, const QS
4545
, mNumOutputRows( nOutputRows )
4646
, mRasterEntries( rasterEntries )
4747
{
48+
//default to first layer's crs
49+
mOutputCrs = mRasterEntries.at( 0 ).raster->crs();
4850
}
4951

52+
QgsRasterCalculator::QgsRasterCalculator( const QString& formulaString, const QString& outputFile, const QString& outputFormat,
53+
const QgsRectangle& outputExtent, const QgsCoordinateReferenceSystem& outputCrs, int nOutputColumns, int nOutputRows, const QVector<QgsRasterCalculatorEntry>& rasterEntries )
54+
: mFormulaString( formulaString )
55+
, mOutputFile( outputFile )
56+
, mOutputFormat( outputFormat )
57+
, mOutputRectangle( outputExtent )
58+
, mOutputCrs( outputCrs )
59+
, mNumOutputColumns( nOutputColumns )
60+
, mNumOutputRows( nOutputRows )
61+
, mRasterEntries( rasterEntries )
62+
{
63+
}
64+
65+
5066
QgsRasterCalculator::~QgsRasterCalculator()
5167
{
5268
}
@@ -62,56 +78,31 @@ int QgsRasterCalculator::processCalculation( QProgressDialog* p )
6278
return 4;
6379
}
6480

65-
double targetGeoTransform[6];
66-
outputGeoTransform( targetGeoTransform );
67-
68-
//open all input rasters for reading
69-
QMap< QString, GDALRasterBandH > mInputRasterBands; //raster references and corresponding scanline data
70-
QMap< QString, QgsRasterMatrix* > inputScanLineData; //stores raster references and corresponding scanline data
71-
QVector< GDALDatasetH > mInputDatasets; //raster references and corresponding dataset
72-
81+
QMap< QString, QgsRasterBlock* > inputBlocks;
7382
QVector<QgsRasterCalculatorEntry>::const_iterator it = mRasterEntries.constBegin();
7483
for ( ; it != mRasterEntries.constEnd(); ++it )
7584
{
7685
if ( !it->raster ) // no raster layer in entry
7786
{
7887
return 2;
7988
}
80-
GDALDatasetH inputDataset = GDALOpen( TO8F( it->raster->source() ), GA_ReadOnly );
81-
if ( !inputDataset )
82-
{
83-
return 2;
84-
}
8589

86-
//check if the input dataset is south up or rotated. If yes, use GDALAutoCreateWarpedVRT to create a north up raster
87-
double inputGeoTransform[6];
88-
if ( GDALGetGeoTransform( inputDataset, inputGeoTransform ) == CE_None
89-
&& ( inputGeoTransform[1] < 0.0
90-
|| inputGeoTransform[2] != 0.0
91-
|| inputGeoTransform[4] != 0.0
92-
|| inputGeoTransform[5] > 0.0 ) )
90+
QgsRasterBlock* block = 0;
91+
// if crs transform needed
92+
if ( it->raster->crs() != mOutputCrs )
9393
{
94-
GDALDatasetH vDataset = GDALAutoCreateWarpedVRT( inputDataset, NULL, NULL, GRA_NearestNeighbour, 0.2, NULL );
95-
mInputDatasets.push_back( vDataset );
96-
mInputDatasets.push_back( inputDataset );
97-
inputDataset = vDataset;
94+
QgsRasterProjector* proj = new QgsRasterProjector();
95+
proj->setCRS( it->raster->crs(), mOutputCrs );
96+
proj->setInput( it->raster->dataProvider()->clone() );
97+
proj->setPrecision( QgsRasterProjector::Exact );
98+
99+
block = proj->block( it->bandNumber, mOutputRectangle, mNumOutputColumns, mNumOutputRows );
98100
}
99101
else
100102
{
101-
mInputDatasets.push_back( inputDataset );
103+
block = it->raster->dataProvider()->block( it->bandNumber, mOutputRectangle, mNumOutputColumns, mNumOutputRows );
102104
}
103-
104-
GDALRasterBandH inputRasterBand = GDALGetRasterBand( inputDataset, it->bandNumber );
105-
if ( inputRasterBand == NULL )
106-
{
107-
return 2;
108-
}
109-
110-
int nodataSuccess;
111-
double nodataValue = GDALGetRasterNoDataValue( inputRasterBand, &nodataSuccess );
112-
113-
mInputRasterBands.insert( it->ref, inputRasterBand );
114-
inputScanLineData.insert( it->ref, new QgsRasterMatrix( mNumOutputColumns, 1, new double[mNumOutputColumns], nodataValue ) );
105+
inputBlocks.insert( it->ref, block );
115106
}
116107

117108
//open output dataset for writing
@@ -120,44 +111,21 @@ int QgsRasterCalculator::processCalculation( QProgressDialog* p )
120111
{
121112
return 1;
122113
}
123-
GDALDatasetH outputDataset = openOutputFile( outputDriver );
124-
125-
//copy the projection info from the first input raster
126-
if ( mRasterEntries.size() > 0 )
127-
{
128-
QgsRasterLayer* rl = mRasterEntries.at( 0 ).raster;
129-
if ( rl )
130-
{
131-
char* crsWKT = 0;
132-
OGRSpatialReferenceH ogrSRS = OSRNewSpatialReference( NULL );
133-
if ( OSRSetFromUserInput( ogrSRS, rl->crs().authid().toUtf8().constData() ) == OGRERR_NONE )
134-
{
135-
OSRExportToWkt( ogrSRS, &crsWKT );
136-
GDALSetProjection( outputDataset, crsWKT );
137-
}
138-
else
139-
{
140-
GDALSetProjection( outputDataset, TO8( rl->crs().toWkt() ) );
141-
}
142-
OSRDestroySpatialReference( ogrSRS );
143-
CPLFree( crsWKT );
144-
}
145-
}
146-
147114

115+
GDALDatasetH outputDataset = openOutputFile( outputDriver );
116+
GDALSetProjection( outputDataset, mOutputCrs.toWkt().toLocal8Bit().data() );
148117
GDALRasterBandH outputRasterBand = GDALGetRasterBand( outputDataset, 1 );
149118

150119
float outputNodataValue = -FLT_MAX;
151120
GDALSetRasterNoDataValue( outputRasterBand, outputNodataValue );
152121

153-
float* resultScanLine = ( float * ) CPLMalloc( sizeof( float ) * mNumOutputColumns );
154-
155122
if ( p )
156123
{
157124
p->setMaximum( mNumOutputRows );
158125
}
159126

160127
QgsRasterMatrix resultMatrix;
128+
resultMatrix.setNodataValue( -FLT_MAX );
161129

162130
//read / write line by line
163131
for ( int i = 0; i < mNumOutputRows; ++i )
@@ -172,28 +140,7 @@ int QgsRasterCalculator::processCalculation( QProgressDialog* p )
172140
break;
173141
}
174142

175-
//fill buffers
176-
QMap< QString, QgsRasterMatrix* >::iterator bufferIt = inputScanLineData.begin();
177-
for ( ; bufferIt != inputScanLineData.end(); ++bufferIt )
178-
{
179-
double sourceTransformation[6];
180-
GDALRasterBandH sourceRasterBand = mInputRasterBands[bufferIt.key()];
181-
if ( GDALGetGeoTransform( GDALGetBandDataset( sourceRasterBand ), sourceTransformation ) != CE_None )
182-
{
183-
qWarning( "GDALGetGeoTransform failed!" );
184-
}
185-
186-
float* inputScanLine = ( float * ) CPLMalloc( sizeof( float ) * mNumOutputColumns );
187-
188-
//the function readRasterPart calls GDALRasterIO (and ev. does some conversion if raster transformations are not the same)
189-
readRasterPart( targetGeoTransform, 0, i, mNumOutputColumns, 1, sourceTransformation, sourceRasterBand, inputScanLine );
190-
for ( int col = 0; col < mNumOutputColumns; ++col )
191-
{
192-
bufferIt.value()->data()[col] = ( double )inputScanLine[col];
193-
}
194-
}
195-
196-
if ( calcNode->calculate( inputScanLineData, resultMatrix ) )
143+
if ( calcNode->calculate( inputBlocks, resultMatrix, i ) )
197144
{
198145
bool resultIsNumber = resultMatrix.isNumber();
199146
float* calcData = new float[mNumOutputColumns];
@@ -225,18 +172,8 @@ int QgsRasterCalculator::processCalculation( QProgressDialog* p )
225172

226173
//close datasets and release memory
227174
delete calcNode;
228-
QMap< QString, QgsRasterMatrix* >::iterator bufferIt = inputScanLineData.begin();
229-
for ( ; bufferIt != inputScanLineData.end(); ++bufferIt )
230-
{
231-
delete bufferIt.value();
232-
}
233-
inputScanLineData.clear();
234-
235-
QVector< GDALDatasetH >::iterator datasetIt = mInputDatasets.begin();
236-
for ( ; datasetIt != mInputDatasets.end(); ++ datasetIt )
237-
{
238-
GDALClose( *datasetIt );
239-
}
175+
qDeleteAll( inputBlocks );
176+
inputBlocks.clear();
240177

241178
if ( p && p->wasCanceled() )
242179
{
@@ -245,7 +182,7 @@ int QgsRasterCalculator::processCalculation( QProgressDialog* p )
245182
return 3;
246183
}
247184
GDALClose( outputDataset );
248-
CPLFree( resultScanLine );
185+
249186
return 0;
250187
}
251188

0 commit comments

Comments
 (0)