QGIS API Documentation  2.0.1-Dufour
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
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 "qgsrastercalculator.h"
19 #include "qgsrastercalcnode.h"
20 #include "qgsrasterlayer.h"
21 #include "qgsrastermatrix.h"
22 #include "cpl_string.h"
23 #include <QProgressDialog>
24 #include <QFile>
25 
26 #include "gdalwarper.h"
27 #include <ogr_srs_api.h>
28 
29 #if defined(GDAL_VERSION_NUM) && GDAL_VERSION_NUM >= 1800
30 #define TO8(x) (x).toUtf8().constData()
31 #define TO8F(x) (x).toUtf8().constData()
32 #else
33 #define TO8(x) (x).toLocal8Bit().constData()
34 #define TO8F(x) QFile::encodeName( x ).constData()
35 #endif
36 
37 QgsRasterCalculator::QgsRasterCalculator( const QString& formulaString, const QString& outputFile, const QString& outputFormat,
38  const QgsRectangle& outputExtent, int nOutputColumns, int nOutputRows, const QVector<QgsRasterCalculatorEntry>& rasterEntries ): mFormulaString( formulaString ), mOutputFile( outputFile ), mOutputFormat( outputFormat ),
39  mOutputRectangle( outputExtent ), mNumOutputColumns( nOutputColumns ), mNumOutputRows( nOutputRows ), mRasterEntries( rasterEntries )
40 {
41 }
42 
44 {
45 }
46 
47 int QgsRasterCalculator::processCalculation( QProgressDialog* p )
48 {
49  //prepare search string / tree
50  QString errorString;
52  if ( !calcNode )
53  {
54  //error
55  }
56 
57  double targetGeoTransform[6];
58  outputGeoTransform( targetGeoTransform );
59 
60  //open all input rasters for reading
61  QMap< QString, GDALRasterBandH > mInputRasterBands; //raster references and corresponding scanline data
62  QMap< QString, QgsRasterMatrix* > inputScanLineData; //stores raster references and corresponding scanline data
63  QVector< GDALDatasetH > mInputDatasets; //raster references and corresponding dataset
64 
65  QVector<QgsRasterCalculatorEntry>::const_iterator it = mRasterEntries.constBegin();
66  for ( ; it != mRasterEntries.constEnd(); ++it )
67  {
68  if ( !it->raster ) // no raster layer in entry
69  {
70  return 2;
71  }
72  GDALDatasetH inputDataset = GDALOpen( TO8F( it->raster->source() ), GA_ReadOnly );
73  if ( inputDataset == NULL )
74  {
75  return 2;
76  }
77 
78  //check if the input dataset is south up or rotated. If yes, use GDALAutoCreateWarpedVRT to create a north up raster
79  double inputGeoTransform[6];
80  if ( GDALGetGeoTransform( inputDataset, inputGeoTransform ) == CE_None
81  && ( inputGeoTransform[1] < 0.0
82  || inputGeoTransform[2] != 0.0
83  || inputGeoTransform[4] != 0.0
84  || inputGeoTransform[5] > 0.0 ) )
85  {
86  GDALDatasetH vDataset = GDALAutoCreateWarpedVRT( inputDataset, NULL, NULL, GRA_NearestNeighbour, 0.2, NULL );
87  mInputDatasets.push_back( vDataset );
88  mInputDatasets.push_back( inputDataset );
89  inputDataset = vDataset;
90  }
91  else
92  {
93  mInputDatasets.push_back( inputDataset );
94  }
95 
96 
97  GDALRasterBandH inputRasterBand = GDALGetRasterBand( inputDataset, it->bandNumber );
98  if ( inputRasterBand == NULL )
99  {
100  return 2;
101  }
102 
103  int nodataSuccess;
104  double nodataValue = GDALGetRasterNoDataValue( inputRasterBand, &nodataSuccess );
105 
106  mInputRasterBands.insert( it->ref, inputRasterBand );
107  inputScanLineData.insert( it->ref, new QgsRasterMatrix( mNumOutputColumns, 1, new float[mNumOutputColumns], nodataValue ) );
108  }
109 
110  //open output dataset for writing
111  GDALDriverH outputDriver = openOutputDriver();
112  if ( outputDriver == NULL )
113  {
114  return 1;
115  }
116  GDALDatasetH outputDataset = openOutputFile( outputDriver );
117 
118  //copy the projection info from the first input raster
119  if ( mRasterEntries.size() > 0 )
120  {
121  QgsRasterLayer* rl = mRasterEntries.at( 0 ).raster;
122  if ( rl )
123  {
124  char* crsWKT = 0;
125  OGRSpatialReferenceH ogrSRS = OSRNewSpatialReference( NULL );
126  if ( OSRSetFromUserInput( ogrSRS, rl->crs().authid().toUtf8().constData() ) == OGRERR_NONE )
127  {
128  OSRExportToWkt( ogrSRS, &crsWKT );
129  GDALSetProjection( outputDataset, crsWKT );
130  }
131  else
132  {
133  GDALSetProjection( outputDataset, TO8( rl->crs().toWkt() ) );
134  }
135  OSRDestroySpatialReference( ogrSRS );
136  CPLFree( crsWKT );
137  }
138  }
139 
140 
141  GDALRasterBandH outputRasterBand = GDALGetRasterBand( outputDataset, 1 );
142 
143  float outputNodataValue = -FLT_MAX;
144  GDALSetRasterNoDataValue( outputRasterBand, outputNodataValue );
145 
146  float* resultScanLine = ( float * ) CPLMalloc( sizeof( float ) * mNumOutputColumns );
147 
148  if ( p )
149  {
150  p->setMaximum( mNumOutputRows );
151  }
152 
153  QgsRasterMatrix resultMatrix;
154 
155  //read / write line by line
156  for ( int i = 0; i < mNumOutputRows; ++i )
157  {
158  if ( p )
159  {
160  p->setValue( i );
161  }
162 
163  if ( p && p->wasCanceled() )
164  {
165  break;
166  }
167 
168  //fill buffers
169  QMap< QString, QgsRasterMatrix* >::iterator bufferIt = inputScanLineData.begin();
170  for ( ; bufferIt != inputScanLineData.end(); ++bufferIt )
171  {
172  double sourceTransformation[6];
173  GDALRasterBandH sourceRasterBand = mInputRasterBands[bufferIt.key()];
174  GDALGetGeoTransform( GDALGetBandDataset( sourceRasterBand ), sourceTransformation );
175  //the function readRasterPart calls GDALRasterIO (and ev. does some conversion if raster transformations are not the same)
176  readRasterPart( targetGeoTransform, 0, i, mNumOutputColumns, 1, sourceTransformation, sourceRasterBand, bufferIt.value()->data() );
177  }
178 
179  if ( calcNode->calculate( inputScanLineData, resultMatrix ) )
180  {
181  bool resultIsNumber = resultMatrix.isNumber();
182  float* calcData;
183 
184  if ( resultIsNumber ) //scalar result. Insert number for every pixel
185  {
186  calcData = new float[mNumOutputColumns];
187  for ( int j = 0; j < mNumOutputColumns; ++j )
188  {
189  calcData[j] = resultMatrix.number();
190  }
191  }
192  else //result is real matrix
193  {
194  calcData = resultMatrix.data();
195  }
196 
197  //replace all matrix nodata values with output nodatas
198  for ( int j = 0; j < mNumOutputColumns; ++j )
199  {
200  if ( calcData[j] == resultMatrix.nodataValue() )
201  {
202  calcData[j] = outputNodataValue;
203  }
204  }
205 
206  //write scanline to the dataset
207  if ( GDALRasterIO( outputRasterBand, GF_Write, 0, i, mNumOutputColumns, 1, calcData, mNumOutputColumns, 1, GDT_Float32, 0, 0 ) != CE_None )
208  {
209  qWarning( "RasterIO error!" );
210  }
211 
212  if ( resultIsNumber )
213  {
214  delete[] calcData;
215  }
216  }
217 
218  }
219 
220  if ( p )
221  {
222  p->setValue( mNumOutputRows );
223  }
224 
225  //close datasets and release memory
226  delete calcNode;
227  QMap< QString, QgsRasterMatrix* >::iterator bufferIt = inputScanLineData.begin();
228  for ( ; bufferIt != inputScanLineData.end(); ++bufferIt )
229  {
230  delete bufferIt.value();
231  }
232  inputScanLineData.clear();
233 
234  QVector< GDALDatasetH >::iterator datasetIt = mInputDatasets.begin();
235  for ( ; datasetIt != mInputDatasets.end(); ++ datasetIt )
236  {
237  GDALClose( *datasetIt );
238  }
239 
240  if ( p && p->wasCanceled() )
241  {
242  //delete the dataset without closing (because it is faster)
243  GDALDeleteDataset( outputDriver, TO8F( mOutputFile ) );
244  return 3;
245  }
246  GDALClose( outputDataset );
247  CPLFree( resultScanLine );
248  return 0;
249 }
250 
252 {
253 }
254 
256 {
257  char **driverMetadata;
258 
259  //open driver
260  GDALDriverH outputDriver = GDALGetDriverByName( mOutputFormat.toLocal8Bit().data() );
261 
262  if ( outputDriver == NULL )
263  {
264  return outputDriver; //return NULL, driver does not exist
265  }
266 
267  driverMetadata = GDALGetMetadata( outputDriver, NULL );
268  if ( !CSLFetchBoolean( driverMetadata, GDAL_DCAP_CREATE, false ) )
269  {
270  return NULL; //driver exist, but it does not support the create operation
271  }
272 
273  return outputDriver;
274 }
275 
276 GDALDatasetH QgsRasterCalculator::openOutputFile( GDALDriverH outputDriver )
277 {
278  //open output file
279  char **papszOptions = NULL;
280  GDALDatasetH outputDataset = GDALCreate( outputDriver, TO8F( mOutputFile ), mNumOutputColumns, mNumOutputRows, 1, GDT_Float32, papszOptions );
281  if ( outputDataset == NULL )
282  {
283  return outputDataset;
284  }
285 
286  //assign georef information
287  double geotransform[6];
288  outputGeoTransform( geotransform );
289  GDALSetGeoTransform( outputDataset, geotransform );
290 
291  return outputDataset;
292 }
293 
294 void QgsRasterCalculator::readRasterPart( double* targetGeotransform, int xOffset, int yOffset, int nCols, int nRows, double* sourceTransform, GDALRasterBandH sourceBand, float* rasterBuffer )
295 {
296  //If dataset transform is the same as the requested transform, do a normal GDAL raster io
297  if ( transformationsEqual( targetGeotransform, sourceTransform ) )
298  {
299  GDALRasterIO( sourceBand, GF_Read, xOffset, yOffset, nCols, nRows, rasterBuffer, nCols, nRows, GDT_Float32, 0, 0 );
300  return;
301  }
302 
303  int sourceBandXSize = GDALGetRasterBandXSize( sourceBand );
304  int sourceBandYSize = GDALGetRasterBandYSize( sourceBand );
305 
306  //pixel calculation needed because of different raster position / resolution
307  int nodataSuccess;
308  double nodataValue = GDALGetRasterNoDataValue( sourceBand, &nodataSuccess );
309  QgsRectangle targetRect( targetGeotransform[0] + targetGeotransform[1] * xOffset, targetGeotransform[3] + yOffset * targetGeotransform[5] + nRows * targetGeotransform[5]
310  , targetGeotransform[0] + targetGeotransform[1] * xOffset + targetGeotransform[1] * nCols, targetGeotransform[3] + yOffset * targetGeotransform[5] );
311  QgsRectangle sourceRect( sourceTransform[0], sourceTransform[3] + GDALGetRasterBandYSize( sourceBand ) * sourceTransform[5],
312  sourceTransform[0] + GDALGetRasterBandXSize( sourceBand )* sourceTransform[1], sourceTransform[3] );
313  QgsRectangle intersection = targetRect.intersect( &sourceRect );
314 
315  //no intersection, fill all the pixels with nodata values
316  if ( intersection.isEmpty() )
317  {
318  int nPixels = nCols * nRows;
319  for ( int i = 0; i < nPixels; ++i )
320  {
321  rasterBuffer[i] = nodataValue;
322  }
323  return;
324  }
325 
326  //do raster io in source resolution
327  int sourcePixelOffsetXMin = floor(( intersection.xMinimum() - sourceTransform[0] ) / sourceTransform[1] );
328  int sourcePixelOffsetXMax = ceil(( intersection.xMaximum() - sourceTransform[0] ) / sourceTransform[1] );
329  if ( sourcePixelOffsetXMax > sourceBandXSize )
330  {
331  sourcePixelOffsetXMax = sourceBandXSize;
332  }
333  int nSourcePixelsX = sourcePixelOffsetXMax - sourcePixelOffsetXMin;
334 
335  int sourcePixelOffsetYMax = floor(( intersection.yMaximum() - sourceTransform[3] ) / sourceTransform[5] );
336  int sourcePixelOffsetYMin = ceil(( intersection.yMinimum() - sourceTransform[3] ) / sourceTransform[5] );
337  if ( sourcePixelOffsetYMin > sourceBandYSize )
338  {
339  sourcePixelOffsetYMin = sourceBandYSize;
340  }
341  int nSourcePixelsY = sourcePixelOffsetYMin - sourcePixelOffsetYMax;
342  float* sourceRaster = ( float * ) CPLMalloc( sizeof( float ) * nSourcePixelsX * nSourcePixelsY );
343  double sourceRasterXMin = sourceRect.xMinimum() + sourcePixelOffsetXMin * sourceTransform[1];
344  double sourceRasterYMax = sourceRect.yMaximum() + sourcePixelOffsetYMax * sourceTransform[5];
345  if ( GDALRasterIO( sourceBand, GF_Read, sourcePixelOffsetXMin, sourcePixelOffsetYMax, nSourcePixelsX, nSourcePixelsY,
346  sourceRaster, nSourcePixelsX, nSourcePixelsY, GDT_Float32, 0, 0 ) != CE_None )
347  {
348  //IO error, fill array with nodata values
349  CPLFree( sourceRaster );
350  int npixels = nRows * nCols;
351  for ( int i = 0; i < npixels; ++i )
352  {
353  rasterBuffer[i] = nodataValue;
354  }
355  return;
356  }
357 
358 
359  double targetPixelX;
360  double targetPixelXMin = targetGeotransform[0] + targetGeotransform[1] * xOffset + targetGeotransform[1] / 2.0;
361  double targetPixelY = targetGeotransform[3] + targetGeotransform[5] * yOffset + targetGeotransform[5] / 2.0; //coordinates of current target pixel
362  int sourceIndexX, sourceIndexY; //current raster index in source pixels
363  double sx, sy;
364  for ( int i = 0; i < nRows; ++i )
365  {
366  targetPixelX = targetPixelXMin;
367  for ( int j = 0; j < nCols; ++j )
368  {
369  sx = ( targetPixelX - sourceRasterXMin ) / sourceTransform[1];
370  sourceIndexX = sx > 0 ? sx : floor( sx );
371  sy = ( targetPixelY - sourceRasterYMax ) / sourceTransform[5];
372  sourceIndexY = sy > 0 ? sy : floor( sy );
373  if ( sourceIndexX >= 0 && sourceIndexX < nSourcePixelsX
374  && sourceIndexY >= 0 && sourceIndexY < nSourcePixelsY )
375  {
376  rasterBuffer[j + i*nRows] = sourceRaster[ sourceIndexX + nSourcePixelsX * sourceIndexY ];
377  }
378  else
379  {
380  rasterBuffer[j + i*j] = nodataValue;
381  }
382  targetPixelX += targetGeotransform[1];
383  }
384  targetPixelY += targetGeotransform[5];
385  }
386 
387  CPLFree( sourceRaster );
388  return;
389 }
390 
391 bool QgsRasterCalculator::transformationsEqual( double* t1, double* t2 ) const
392 {
393  for ( int i = 0; i < 6; ++i )
394  {
395  if ( !qgsDoubleNear( t1[i], t2[i], 0.00001 ) )
396  {
397  return false;
398  }
399  }
400  return true;
401 }
402 
403 void QgsRasterCalculator::outputGeoTransform( double* transform ) const
404 {
405  transform[0] = mOutputRectangle.xMinimum();
406  transform[1] = mOutputRectangle.width() / mNumOutputColumns;
407  transform[2] = 0;
408  transform[3] = mOutputRectangle.yMaximum();
409  transform[4] = 0;
410  transform[5] = -mOutputRectangle.height() / mNumOutputRows;
411 }
412 
413