QGIS API Documentation  2.8.2-Wien
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgszonalstatistics.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgszonalstatistics.cpp - description
3  ----------------------------
4  begin : August 29th, 2009
5  copyright : (C) 2009 by Marco Hugentobler
6  email : marco at hugis dot net
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 "qgszonalstatistics.h"
19 #include "qgsgeometry.h"
20 #include "qgsvectordataprovider.h"
21 #include "qgsvectorlayer.h"
22 #include "gdal.h"
23 #include "cpl_string.h"
24 #include <QProgressDialog>
25 #include <QFile>
26 
27 #if defined(GDAL_VERSION_NUM) && GDAL_VERSION_NUM >= 1800
28 #define TO8F(x) (x).toUtf8().constData()
29 #else
30 #define TO8F(x) QFile::encodeName( x ).constData()
31 #endif
32 
33 QgsZonalStatistics::QgsZonalStatistics( QgsVectorLayer* polygonLayer, const QString& rasterFile, const QString& attributePrefix, int rasterBand )
34  : mRasterFilePath( rasterFile )
35  , mRasterBand( rasterBand )
36  , mPolygonLayer( polygonLayer )
37  , mAttributePrefix( attributePrefix )
38  , mInputNodataValue( -1 )
39 {
40 
41 }
42 
43 QgsZonalStatistics::QgsZonalStatistics()
44  : mRasterBand( 0 )
45  , mPolygonLayer( 0 )
46  , mInputNodataValue( -1 )
47 {
48 
49 }
50 
52 {
53 
54 }
55 
56 int QgsZonalStatistics::calculateStatistics( QProgressDialog* p )
57 {
58  if ( !mPolygonLayer || mPolygonLayer->geometryType() != QGis::Polygon )
59  {
60  return 1;
61  }
62 
63  QgsVectorDataProvider* vectorProvider = mPolygonLayer->dataProvider();
64  if ( !vectorProvider )
65  {
66  return 2;
67  }
68 
69  //open the raster layer and the raster band
70  GDALAllRegister();
71  GDALDatasetH inputDataset = GDALOpen( TO8F( mRasterFilePath ), GA_ReadOnly );
72  if ( inputDataset == NULL )
73  {
74  return 3;
75  }
76 
77  if ( GDALGetRasterCount( inputDataset ) < ( mRasterBand - 1 ) )
78  {
79  GDALClose( inputDataset );
80  return 4;
81  }
82 
83  GDALRasterBandH rasterBand = GDALGetRasterBand( inputDataset, mRasterBand );
84  if ( rasterBand == NULL )
85  {
86  GDALClose( inputDataset );
87  return 5;
88  }
89  mInputNodataValue = GDALGetRasterNoDataValue( rasterBand, NULL );
90 
91  //get geometry info about raster layer
92  int nCellsXGDAL = GDALGetRasterXSize( inputDataset );
93  int nCellsYGDAL = GDALGetRasterYSize( inputDataset );
94  double geoTransform[6];
95  if ( GDALGetGeoTransform( inputDataset, geoTransform ) != CE_None )
96  {
97  GDALClose( inputDataset );
98  return 6;
99  }
100  double cellsizeX = geoTransform[1];
101  if ( cellsizeX < 0 )
102  {
103  cellsizeX = -cellsizeX;
104  }
105  double cellsizeY = geoTransform[5];
106  if ( cellsizeY < 0 )
107  {
108  cellsizeY = -cellsizeY;
109  }
110  QgsRectangle rasterBBox( geoTransform[0], geoTransform[3] - ( nCellsYGDAL * cellsizeY ),
111  geoTransform[0] + ( nCellsXGDAL * cellsizeX ), geoTransform[3] );
112 
113  //add the new count, sum, mean fields to the provider
114  QList<QgsField> newFieldList;
115  QString countFieldName = getUniqueFieldName( mAttributePrefix + "count" );
116  QString sumFieldName = getUniqueFieldName( mAttributePrefix + "sum" );
117  QString meanFieldName = getUniqueFieldName( mAttributePrefix + "mean" );
118  QgsField countField( countFieldName, QVariant::Double, "double precision" );
119  QgsField sumField( sumFieldName, QVariant::Double, "double precision" );
120  QgsField meanField( meanFieldName, QVariant::Double, "double precision" );
121  newFieldList.push_back( countField );
122  newFieldList.push_back( sumField );
123  newFieldList.push_back( meanField );
124  vectorProvider->addAttributes( newFieldList );
125 
126  //index of the new fields
127  int countIndex = vectorProvider->fieldNameIndex( countFieldName );
128  int sumIndex = vectorProvider->fieldNameIndex( sumFieldName );
129  int meanIndex = vectorProvider->fieldNameIndex( meanFieldName );
130 
131  if ( countIndex == -1 || sumIndex == -1 || meanIndex == -1 )
132  {
133  return 8;
134  }
135 
136  //progress dialog
137  long featureCount = vectorProvider->featureCount();
138  if ( p )
139  {
140  p->setMaximum( featureCount );
141  }
142 
143 
144  //iterate over each polygon
145  QgsFeatureRequest request;
147  QgsFeatureIterator fi = vectorProvider->getFeatures( request );
148  QgsFeature f;
149  double count = 0;
150  double sum = 0;
151  double mean = 0;
152  int featureCounter = 0;
153 
154  while ( fi.nextFeature( f ) )
155  {
156  if ( p )
157  {
158  p->setValue( featureCounter );
159  }
160 
161  if ( p && p->wasCanceled() )
162  {
163  break;
164  }
165 
166  QgsGeometry* featureGeometry = f.geometry();
167  if ( !featureGeometry )
168  {
169  ++featureCounter;
170  continue;
171  }
172 
173  QgsRectangle featureRect = featureGeometry->boundingBox().intersect( &rasterBBox );
174  if ( featureRect.isEmpty() )
175  {
176  ++featureCounter;
177  continue;
178  }
179 
180  int offsetX, offsetY, nCellsX, nCellsY;
181  if ( cellInfoForBBox( rasterBBox, featureRect, cellsizeX, cellsizeY, offsetX, offsetY, nCellsX, nCellsY ) != 0 )
182  {
183  ++featureCounter;
184  continue;
185  }
186 
187  //avoid access to cells outside of the raster (may occur because of rounding)
188  if (( offsetX + nCellsX ) > nCellsXGDAL )
189  {
190  nCellsX = nCellsXGDAL - offsetX;
191  }
192  if (( offsetY + nCellsY ) > nCellsYGDAL )
193  {
194  nCellsY = nCellsYGDAL - offsetY;
195  }
196 
197  statisticsFromMiddlePointTest( rasterBand, featureGeometry, offsetX, offsetY, nCellsX, nCellsY, cellsizeX, cellsizeY,
198  rasterBBox, sum, count );
199 
200  if ( count <= 1 )
201  {
202  //the cell resolution is probably larger than the polygon area. We switch to precise pixel - polygon intersection in this case
203  statisticsFromPreciseIntersection( rasterBand, featureGeometry, offsetX, offsetY, nCellsX, nCellsY, cellsizeX, cellsizeY,
204  rasterBBox, sum, count );
205  }
206 
207 
208  if ( count == 0 )
209  {
210  mean = 0;
211  }
212  else
213  {
214  mean = sum / count;
215  }
216 
217  //write the statistics value to the vector data provider
218  QgsChangedAttributesMap changeMap;
219  QgsAttributeMap changeAttributeMap;
220  changeAttributeMap.insert( countIndex, QVariant( count ) );
221  changeAttributeMap.insert( sumIndex, QVariant( sum ) );
222  changeAttributeMap.insert( meanIndex, QVariant( mean ) );
223  changeMap.insert( f.id(), changeAttributeMap );
224  vectorProvider->changeAttributeValues( changeMap );
225 
226  ++featureCounter;
227  }
228 
229  if ( p )
230  {
231  p->setValue( featureCount );
232  }
233 
234  GDALClose( inputDataset );
235  mPolygonLayer->updateFields();
236 
237  if ( p && p->wasCanceled() )
238  {
239  return 9;
240  }
241 
242  return 0;
243 }
244 
245 int QgsZonalStatistics::cellInfoForBBox( const QgsRectangle& rasterBBox, const QgsRectangle& featureBBox, double cellSizeX, double cellSizeY,
246  int& offsetX, int& offsetY, int& nCellsX, int& nCellsY ) const
247 {
248  //get intersecting bbox
249  QgsRectangle intersectBox = rasterBBox.intersect( &featureBBox );
250  if ( intersectBox.isEmpty() )
251  {
252  nCellsX = 0; nCellsY = 0; offsetX = 0; offsetY = 0;
253  return 0;
254  }
255 
256  //get offset in pixels in x- and y- direction
257  offsetX = ( int )(( intersectBox.xMinimum() - rasterBBox.xMinimum() ) / cellSizeX );
258  offsetY = ( int )(( rasterBBox.yMaximum() - intersectBox.yMaximum() ) / cellSizeY );
259 
260  int maxColumn = ( int )(( intersectBox.xMaximum() - rasterBBox.xMinimum() ) / cellSizeX ) + 1;
261  int maxRow = ( int )(( rasterBBox.yMaximum() - intersectBox.yMinimum() ) / cellSizeY ) + 1;
262 
263  nCellsX = maxColumn - offsetX;
264  nCellsY = maxRow - offsetY;
265 
266  return 0;
267 }
268 
269 void QgsZonalStatistics::statisticsFromMiddlePointTest( void* band, QgsGeometry* poly, int pixelOffsetX,
270  int pixelOffsetY, int nCellsX, int nCellsY, double cellSizeX, double cellSizeY, const QgsRectangle& rasterBBox, double& sum, double& count )
271 {
272  double cellCenterX, cellCenterY;
273 
274  float* scanLine = ( float * ) CPLMalloc( sizeof( float ) * nCellsX );
275  cellCenterY = rasterBBox.yMaximum() - pixelOffsetY * cellSizeY - cellSizeY / 2;
276  count = 0;
277  sum = 0;
278 
279  const GEOSGeometry* polyGeos = poly->asGeos();
280  if ( !polyGeos )
281  {
282  return;
283  }
284 
285  GEOSContextHandle_t geosctxt = QgsGeometry::getGEOSHandler();
286  const GEOSPreparedGeometry* polyGeosPrepared = GEOSPrepare_r( geosctxt, poly->asGeos() );
287  if ( !polyGeosPrepared )
288  {
289  return;
290  }
291 
292  GEOSCoordSequence* cellCenterCoords = 0;
293  GEOSGeometry* currentCellCenter = 0;
294 
295  for ( int i = 0; i < nCellsY; ++i )
296  {
297  if ( GDALRasterIO( band, GF_Read, pixelOffsetX, pixelOffsetY + i, nCellsX, 1, scanLine, nCellsX, 1, GDT_Float32, 0, 0 )
298  != CPLE_None )
299  {
300  continue;
301  }
302  cellCenterX = rasterBBox.xMinimum() + pixelOffsetX * cellSizeX + cellSizeX / 2;
303  for ( int j = 0; j < nCellsX; ++j )
304  {
305  if ( validPixel( scanLine[j] ) )
306  {
307  GEOSGeom_destroy_r( geosctxt, currentCellCenter );
308  cellCenterCoords = GEOSCoordSeq_create_r( geosctxt, 1, 2 );
309  GEOSCoordSeq_setX_r( geosctxt, cellCenterCoords, 0, cellCenterX );
310  GEOSCoordSeq_setY_r( geosctxt, cellCenterCoords, 0, cellCenterY );
311  currentCellCenter = GEOSGeom_createPoint_r( geosctxt, cellCenterCoords );
312  if ( GEOSPreparedContains_r( geosctxt, polyGeosPrepared, currentCellCenter ) )
313  {
314  sum += scanLine[j];
315  ++count;
316  }
317  }
318  cellCenterX += cellSizeX;
319  }
320  cellCenterY -= cellSizeY;
321  }
322  GEOSGeom_destroy_r( geosctxt, currentCellCenter );
323  CPLFree( scanLine );
324  GEOSPreparedGeom_destroy_r( geosctxt, polyGeosPrepared );
325 }
326 
327 void QgsZonalStatistics::statisticsFromPreciseIntersection( void* band, QgsGeometry* poly, int pixelOffsetX,
328  int pixelOffsetY, int nCellsX, int nCellsY, double cellSizeX, double cellSizeY, const QgsRectangle& rasterBBox, double& sum, double& count )
329 {
330  sum = 0;
331  count = 0;
332  double currentY = rasterBBox.yMaximum() - pixelOffsetY * cellSizeY - cellSizeY / 2;
333  float* pixelData = ( float * ) CPLMalloc( sizeof( float ) );
334  QgsGeometry* pixelRectGeometry = 0;
335 
336  double hCellSizeX = cellSizeX / 2.0;
337  double hCellSizeY = cellSizeY / 2.0;
338  double pixelArea = cellSizeX * cellSizeY;
339  double weight = 0;
340 
341  for ( int row = 0; row < nCellsY; ++row )
342  {
343  double currentX = rasterBBox.xMinimum() + cellSizeX / 2.0 + pixelOffsetX * cellSizeX;
344  for ( int col = 0; col < nCellsX; ++col )
345  {
346  GDALRasterIO( band, GF_Read, pixelOffsetX + col, pixelOffsetY + row, nCellsX, 1, pixelData, 1, 1, GDT_Float32, 0, 0 );
347  if ( !validPixel( *pixelData ) )
348  continue;
349 
350  pixelRectGeometry = QgsGeometry::fromRect( QgsRectangle( currentX - hCellSizeX, currentY - hCellSizeY, currentX + hCellSizeX, currentY + hCellSizeY ) );
351  if ( pixelRectGeometry )
352  {
353  //intersection
354  QgsGeometry *intersectGeometry = pixelRectGeometry->intersection( poly );
355  if ( intersectGeometry )
356  {
357  double intersectionArea = intersectGeometry->area();
358  if ( intersectionArea >= 0.0 )
359  {
360  weight = intersectionArea / pixelArea;
361  count += weight;
362  sum += *pixelData * weight;
363  }
364  delete intersectGeometry;
365  }
366  delete pixelRectGeometry;
367  pixelRectGeometry = 0;
368  }
369  currentX += cellSizeX;
370  }
371  currentY -= cellSizeY;
372  }
373  CPLFree( pixelData );
374 }
375 
376 bool QgsZonalStatistics::validPixel( float value ) const
377 {
378  if ( value == mInputNodataValue || qIsNaN( value ) )
379  {
380  return false;
381  }
382  return true;
383 }
384 
385 QString QgsZonalStatistics::getUniqueFieldName( QString fieldName )
386 {
387  QgsVectorDataProvider* dp = mPolygonLayer->dataProvider();
388 
389  if ( !dp->storageType().contains( "ESRI Shapefile" ) )
390  {
391  return fieldName;
392  }
393 
394  const QgsFields& providerFields = dp->fields();
395  QString shortName = fieldName.mid( 0, 10 );
396 
397  bool found = false;
398  for ( int idx = 0; idx < providerFields.count(); ++idx )
399  {
400  if ( shortName == providerFields[idx].name() )
401  {
402  found = true;
403  break;
404  }
405  }
406 
407  if ( !found )
408  {
409  return shortName;
410  }
411 
412  int n = 1;
413  shortName = QString( "%1_%2" ).arg( fieldName.mid( 0, 8 ) ).arg( n );
414  found = true;
415  while ( found )
416  {
417  found = false;
418  for ( int idx = 0; idx < providerFields.count(); ++idx )
419  {
420  if ( shortName == providerFields[idx].name() )
421  {
422  n += 1;
423  if ( n < 9 )
424  {
425  shortName = QString( "%1_%2" ).arg( fieldName.mid( 0, 8 ) ).arg( n );
426  }
427  else
428  {
429  shortName = QString( "%1_%2" ).arg( fieldName.mid( 0, 7 ) ).arg( n );
430  }
431  found = true;
432  }
433  }
434  }
435  return shortName;
436 }