QGIS API Documentation  3.4.15-Madeira (e83d02e274)
qgsrastercalculator.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsrastercalculator.cpp - description
3  -----------------------
4  begin : September 28th, 2010
5  copyright : (C) 2010 by Marco Hugentobler
6  email : marco dot hugentobler at sourcepole dot ch
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
18 #include "qgsgdalutils.h"
19 #include "qgsrastercalculator.h"
20 #include "qgsrastercalcnode.h"
21 #include "qgsrasterdataprovider.h"
22 #include "qgsrasterinterface.h"
23 #include "qgsrasterlayer.h"
24 #include "qgsrastermatrix.h"
25 #include "qgsrasterprojector.h"
26 #include "qgsfeedback.h"
27 #include "qgsogrutils.h"
28 
29 #include <QFile>
30 
31 #include <cpl_string.h>
32 #include <gdalwarper.h>
33 
34 QgsRasterCalculator::QgsRasterCalculator( const QString &formulaString, const QString &outputFile, const QString &outputFormat,
35  const QgsRectangle &outputExtent, int nOutputColumns, int nOutputRows, const QVector<QgsRasterCalculatorEntry> &rasterEntries )
36  : mFormulaString( formulaString )
37  , mOutputFile( outputFile )
38  , mOutputFormat( outputFormat )
39  , mOutputRectangle( outputExtent )
40  , mNumOutputColumns( nOutputColumns )
41  , mNumOutputRows( nOutputRows )
42  , mRasterEntries( rasterEntries )
43 {
44  //default to first layer's crs
45  mOutputCrs = mRasterEntries.at( 0 ).raster->crs();
46 }
47 
48 QgsRasterCalculator::QgsRasterCalculator( const QString &formulaString, const QString &outputFile, const QString &outputFormat,
49  const QgsRectangle &outputExtent, const QgsCoordinateReferenceSystem &outputCrs, int nOutputColumns, int nOutputRows, const QVector<QgsRasterCalculatorEntry> &rasterEntries )
50  : mFormulaString( formulaString )
51  , mOutputFile( outputFile )
52  , mOutputFormat( outputFormat )
53  , mOutputRectangle( outputExtent )
54  , mOutputCrs( outputCrs )
55  , mNumOutputColumns( nOutputColumns )
56  , mNumOutputRows( nOutputRows )
57  , mRasterEntries( rasterEntries )
58 {
59 }
60 
62 {
63  mLastError.clear();
64 
65  //prepare search string / tree
66  std::unique_ptr< QgsRasterCalcNode > calcNode( QgsRasterCalcNode::parseRasterCalcString( mFormulaString, mLastError ) );
67  if ( !calcNode )
68  {
69  //error
70  return ParserError;
71  }
72 
73  // Check input layers and bands
74  for ( const auto &entry : qgis::as_const( mRasterEntries ) )
75  {
76  if ( !entry.raster ) // no raster layer in entry
77  {
78  mLastError = QObject::tr( "No raster layer for entry %1" ).arg( entry.ref );
79  return InputLayerError;
80  }
81  if ( entry.bandNumber <= 0 || entry.bandNumber > entry.raster->bandCount() )
82  {
83  mLastError = QObject::tr( "Band number %1 is not valid for entry %2" ).arg( entry.bandNumber ).arg( entry.ref );
84  return BandError;
85  }
86  }
87 
88  //open output dataset for writing
89  GDALDriverH outputDriver = openOutputDriver();
90  if ( !outputDriver )
91  {
92  mLastError = QObject::tr( "Could not obtain driver for %1" ).arg( mOutputFormat );
93  return CreateOutputError;
94  }
95 
96  gdal::dataset_unique_ptr outputDataset( openOutputFile( outputDriver ) );
97  if ( !outputDataset )
98  {
99  mLastError = QObject::tr( "Could not create output %1" ).arg( mOutputFile );
100  return CreateOutputError;
101  }
102 
103  GDALSetProjection( outputDataset.get(), mOutputCrs.toWkt().toLocal8Bit().data() );
104  GDALRasterBandH outputRasterBand = GDALGetRasterBand( outputDataset.get(), 1 );
105 
106  float outputNodataValue = -FLT_MAX;
107  GDALSetRasterNoDataValue( outputRasterBand, outputNodataValue );
108 
109  // Check if we need to read the raster as a whole (which is memory inefficient
110  // and not interruptable by the user) by checking if any raster matrix nodes are
111  // in the expression
112  bool requiresMatrix = ! calcNode->findNodes( QgsRasterCalcNode::Type::tMatrix ).isEmpty();
113 
114  // Take the fast route (process one line at a time) if we can
115  if ( ! requiresMatrix )
116  {
117  // Map of raster names -> blocks
118  std::map<QString, std::unique_ptr<QgsRasterBlock>> inputBlocks;
119  std::map<QString, QgsRasterCalculatorEntry> uniqueRasterEntries;
120  for ( const auto &r : calcNode->findNodes( QgsRasterCalcNode::Type::tRasterRef ) )
121  {
122  QString layerRef( r->toString().remove( 0, 1 ) );
123  layerRef.chop( 1 );
124  if ( ! inputBlocks.count( layerRef ) )
125  {
126  for ( const auto &ref : mRasterEntries )
127  {
128  if ( ref.ref == layerRef )
129  {
130  uniqueRasterEntries[layerRef] = ref;
131  inputBlocks[layerRef ] = qgis::make_unique<QgsRasterBlock>();
132  }
133  }
134  }
135  }
136 
137  //read / write line by line
138  QMap<QString, QgsRasterBlock * > _rasterData;
139  // Cast to float
140  std::vector<float> castedResult( static_cast<size_t>( mNumOutputColumns ), 0 );
141  auto rowHeight = mOutputRectangle.height() / mNumOutputRows;
142  for ( size_t row = 0; row < static_cast<size_t>( mNumOutputRows ); ++row )
143  {
144  if ( feedback )
145  {
146  feedback->setProgress( 100.0 * static_cast< double >( row ) / mNumOutputRows );
147  }
148 
149  if ( feedback && feedback->isCanceled() )
150  {
151  break;
152  }
153 
154  // Calculates the rect for a single row read
155  QgsRectangle rect( mOutputRectangle );
156  rect.setYMaximum( rect.yMaximum() - rowHeight * row );
157  rect.setYMinimum( rect.yMaximum() - rowHeight );
158 
159  // Read rows into input blocks
160  for ( auto &layerRef : inputBlocks )
161  {
162  QgsRasterCalculatorEntry ref = uniqueRasterEntries[layerRef.first];
163  if ( uniqueRasterEntries[layerRef.first].raster->crs() != mOutputCrs )
164  {
165  QgsRasterProjector proj;
166  proj.setCrs( ref.raster->crs(), mOutputCrs );
167  proj.setInput( ref.raster->dataProvider() );
169  layerRef.second.reset( proj.block( ref.bandNumber, rect, mNumOutputColumns, 1 ) );
170  }
171  else
172  {
173  inputBlocks[layerRef.first].reset( ref.raster->dataProvider()->block( ref.bandNumber, rect, mNumOutputColumns, 1 ) );
174  }
175  }
176 
177  QgsRasterMatrix resultMatrix;
178  resultMatrix.setNodataValue( outputNodataValue );
179 
180  _rasterData.clear();
181  for ( const auto &layerRef : inputBlocks )
182  {
183  _rasterData.insert( layerRef.first, inputBlocks[layerRef.first].get() );
184  }
185 
186  if ( calcNode->calculate( _rasterData, resultMatrix, 0 ) )
187  {
188  std::copy( resultMatrix.data(), resultMatrix.data() + mNumOutputColumns, castedResult.begin() );
189  if ( GDALRasterIO( outputRasterBand, GF_Write, 0, row, mNumOutputColumns, 1, castedResult.data(), mNumOutputColumns, 1, GDT_Float32, 0, 0 ) != CE_None )
190  {
191  QgsDebugMsg( QStringLiteral( "RasterIO error!" ) );
192  }
193  }
194  }
195 
196  if ( feedback )
197  {
198  feedback->setProgress( 100.0 );
199  }
200  }
201  else // Original code (memory inefficient route)
202  {
203  QMap< QString, QgsRasterBlock * > inputBlocks;
204  QVector<QgsRasterCalculatorEntry>::const_iterator it = mRasterEntries.constBegin();
205  for ( ; it != mRasterEntries.constEnd(); ++it )
206  {
207 
208  std::unique_ptr< QgsRasterBlock > block;
209  // if crs transform needed
210  if ( it->raster->crs() != mOutputCrs )
211  {
212  QgsRasterProjector proj;
213  proj.setCrs( it->raster->crs(), mOutputCrs );
214  proj.setInput( it->raster->dataProvider() );
216 
217  QgsRasterBlockFeedback *rasterBlockFeedback = new QgsRasterBlockFeedback();
218  QObject::connect( feedback, &QgsFeedback::canceled, rasterBlockFeedback, &QgsRasterBlockFeedback::cancel );
219  block.reset( proj.block( it->bandNumber, mOutputRectangle, mNumOutputColumns, mNumOutputRows, rasterBlockFeedback ) );
220  if ( rasterBlockFeedback->isCanceled() )
221  {
222  qDeleteAll( inputBlocks );
223  return Canceled;
224  }
225  }
226  else
227  {
228  block.reset( it->raster->dataProvider()->block( it->bandNumber, mOutputRectangle, mNumOutputColumns, mNumOutputRows ) );
229  }
230  if ( block->isEmpty() )
231  {
232  mLastError = QObject::tr( "Could not allocate required memory for %1" ).arg( it->ref );
233  qDeleteAll( inputBlocks );
234  return MemoryError;
235  }
236  inputBlocks.insert( it->ref, block.release() );
237  }
238 
239  QgsRasterMatrix resultMatrix;
240  resultMatrix.setNodataValue( outputNodataValue );
241 
242  //read / write line by line
243  for ( int i = 0; i < mNumOutputRows; ++i )
244  {
245  if ( feedback )
246  {
247  feedback->setProgress( 100.0 * static_cast< double >( i ) / mNumOutputRows );
248  }
249 
250  if ( feedback && feedback->isCanceled() )
251  {
252  break;
253  }
254 
255  if ( calcNode->calculate( inputBlocks, resultMatrix, i ) )
256  {
257  bool resultIsNumber = resultMatrix.isNumber();
258  float *calcData = new float[mNumOutputColumns];
259 
260  for ( int j = 0; j < mNumOutputColumns; ++j )
261  {
262  calcData[j] = ( float )( resultIsNumber ? resultMatrix.number() : resultMatrix.data()[j] );
263  }
264 
265  //write scanline to the dataset
266  if ( GDALRasterIO( outputRasterBand, GF_Write, 0, i, mNumOutputColumns, 1, calcData, mNumOutputColumns, 1, GDT_Float32, 0, 0 ) != CE_None )
267  {
268  QgsDebugMsg( QStringLiteral( "RasterIO error!" ) );
269  }
270 
271  delete[] calcData;
272  }
273 
274  }
275 
276  if ( feedback )
277  {
278  feedback->setProgress( 100.0 );
279  }
280 
281  //close datasets and release memory
282  calcNode.reset();
283  qDeleteAll( inputBlocks );
284  inputBlocks.clear();
285 
286  }
287 
288  if ( feedback && feedback->isCanceled() )
289  {
290  //delete the dataset without closing (because it is faster)
291  gdal::fast_delete_and_close( outputDataset, outputDriver, mOutputFile );
292  return Canceled;
293  }
294  return Success;
295 }
296 
297 GDALDriverH QgsRasterCalculator::openOutputDriver()
298 {
299  //open driver
300  GDALDriverH outputDriver = GDALGetDriverByName( mOutputFormat.toLocal8Bit().data() );
301 
302  if ( !outputDriver )
303  {
304  return outputDriver; //return nullptr, driver does not exist
305  }
306 
307  if ( !QgsGdalUtils::supportsRasterCreate( outputDriver ) )
308  {
309  return nullptr; //driver exist, but it does not support the create operation
310  }
311 
312  return outputDriver;
313 }
314 
315 gdal::dataset_unique_ptr QgsRasterCalculator::openOutputFile( GDALDriverH outputDriver )
316 {
317  //open output file
318  char **papszOptions = nullptr;
319  gdal::dataset_unique_ptr outputDataset( GDALCreate( outputDriver, mOutputFile.toUtf8().constData(), mNumOutputColumns, mNumOutputRows, 1, GDT_Float32, papszOptions ) );
320  if ( !outputDataset )
321  {
322  return nullptr;
323  }
324 
325  //assign georef information
326  double geotransform[6];
327  outputGeoTransform( geotransform );
328  GDALSetGeoTransform( outputDataset.get(), geotransform );
329 
330  return outputDataset;
331 }
332 
333 void QgsRasterCalculator::outputGeoTransform( double *transform ) const
334 {
335  transform[0] = mOutputRectangle.xMinimum();
336  transform[1] = mOutputRectangle.width() / mNumOutputColumns;
337  transform[2] = 0;
338  transform[3] = mOutputRectangle.yMaximum();
339  transform[4] = 0;
340  transform[5] = -mOutputRectangle.height() / mNumOutputRows;
341 }
342 
344 {
345  return mLastError;
346 }
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:54
A rectangle specified with double values.
Definition: qgsrectangle.h:40
Result processCalculation(QgsFeedback *feedback=nullptr)
Starts the calculation and writes a new raster.
QgsRasterLayer * raster
Raster layer associated with entry.
void cancel()
Tells the internal routines that the current operation should be canceled. This should be run by the ...
Definition: qgsfeedback.h:85
double yMaximum() const
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:171
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
Error allocating memory for result.
void setNodataValue(double d)
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition: qgsfeedback.h:63
QString toWkt() const
Returns a WKT representation of this CRS.
void canceled()
Internal routines can connect to this signal if they use event loop.
QgsRasterCalculator(const QString &formulaString, const QString &outputFile, const QString &outputFormat, const QgsRectangle &outputExtent, int nOutputColumns, int nOutputRows, const QVector< QgsRasterCalculatorEntry > &rasterEntries)
QgsRasterCalculator constructor.
static bool supportsRasterCreate(GDALDriverH driver)
Reads whether a driver supports GDALCreate() for raster purposes.
int bandNumber
Band number for entry.
User canceled calculation.
void setCrs(const QgsCoordinateReferenceSystem &srcCRS, const QgsCoordinateReferenceSystem &destCRS, int srcDatumTransform=-1, int destDatumTransform=-1)
Sets the source and destination CRS.
Base class for feedback objects to be used for cancellation of something running in a worker thread...
Definition: qgsfeedback.h:44
double number() const
QgsRasterDataProvider * dataProvider() override
Returns the layer&#39;s data provider.
void CORE_EXPORT fast_delete_and_close(dataset_unique_ptr &dataset, GDALDriverH driver, const QString &path)
Performs a fast close of an unwanted GDAL dataset handle by deleting the underlying data store...
Definition: qgsogrutils.cpp:62
Calculation successful.
void setYMinimum(double y)
Set the minimum y value.
Definition: qgsrectangle.h:139
Error creating output data file.
QgsRasterBlock * block(int bandNo, const QgsRectangle &extent, int width, int height, QgsRasterBlockFeedback *feedback=nullptr) override
Read block of data using given extent and size.
Represents an individual raster layer/band number entry within a raster calculation.
Result
Result of the calculation.
bool isNumber() const
Returns true if matrix is 1x1 (=scalar number)
void setPrecision(Precision precision)
QgsRasterProjector implements approximate projection support for it calculates grid of points in sour...
virtual bool setInput(QgsRasterInterface *input)
Set input.
QgsRasterBlock * block(int bandNo, const QgsRectangle &boundingBox, int width, int height, QgsRasterBlockFeedback *feedback=nullptr) override
Read block of data using given extent and size.
void setYMaximum(double y)
Set the maximum y value.
Definition: qgsrectangle.h:144
This class represents a coordinate reference system (CRS).
double * data()
Returns data array (but not ownership)
const QgsCoordinateReferenceSystem & outputCrs
static QgsRasterCalcNode * parseRasterCalcString(const QString &str, QString &parserErrorMsg)
Exact, precise but slow.
std::unique_ptr< std::remove_pointer< GDALDatasetH >::type, GDALDatasetCloser > dataset_unique_ptr
Scoped GDAL dataset.
Definition: qgsogrutils.h:134
QString lastError() const
Returns a description of the last error encountered.
Invalid band number for input.
double width() const
Returns the width of the rectangle.
Definition: qgsrectangle.h:201
Feedback object tailored for raster block reading.
double xMinimum() const
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:166
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:70
double height() const
Returns the height of the rectangle.
Definition: qgsrectangle.h:208