Quantum GIS API Documentation  master-693a1fe
src/analysis/vector/qgszonalstatistics.cpp
Go to the documentation of this file.
00001 /***************************************************************************
00002                           qgszonalstatistics.cpp  -  description
00003                           ----------------------------
00004     begin                : August 29th, 2009
00005     copyright            : (C) 2009 by Marco Hugentobler
00006     email                : marco at hugis dot net
00007  ***************************************************************************/
00008 
00009 /***************************************************************************
00010  *                                                                         *
00011  *   This program is free software; you can redistribute it and/or modify  *
00012  *   it under the terms of the GNU General Public License as published by  *
00013  *   the Free Software Foundation; either version 2 of the License, or     *
00014  *   (at your option) any later version.                                   *
00015  *                                                                         *
00016  ***************************************************************************/
00017 
00018 #include "qgszonalstatistics.h"
00019 #include "qgsgeometry.h"
00020 #include "qgsvectordataprovider.h"
00021 #include "qgsvectorlayer.h"
00022 #include "gdal.h"
00023 #include "cpl_string.h"
00024 #include <QProgressDialog>
00025 
00026 #if defined(GDAL_VERSION_NUM) && GDAL_VERSION_NUM >= 1800
00027 #define TO8(x) (x).toUtf8().constData()
00028 #else
00029 #define TO8(x) (x).toLocal8Bit().constData()
00030 #endif
00031 
00032 QgsZonalStatistics::QgsZonalStatistics( QgsVectorLayer* polygonLayer, const QString& rasterFile, const QString& attributePrefix, int rasterBand )
00033     : mRasterFilePath( rasterFile )
00034     , mRasterBand( rasterBand )
00035     , mPolygonLayer( polygonLayer )
00036     , mAttributePrefix( attributePrefix )
00037     , mInputNodataValue( -1 )
00038 {
00039 
00040 }
00041 
00042 QgsZonalStatistics::QgsZonalStatistics()
00043     : mRasterBand( 0 )
00044     , mPolygonLayer( 0 )
00045 {
00046 
00047 }
00048 
00049 QgsZonalStatistics::~QgsZonalStatistics()
00050 {
00051 
00052 }
00053 
00054 int QgsZonalStatistics::calculateStatistics( QProgressDialog* p )
00055 {
00056   if ( !mPolygonLayer || mPolygonLayer->geometryType() != QGis::Polygon )
00057   {
00058     return 1;
00059   }
00060 
00061   QgsVectorDataProvider* vectorProvider = mPolygonLayer->dataProvider();
00062   if ( !vectorProvider )
00063   {
00064     return 2;
00065   }
00066 
00067   //open the raster layer and the raster band
00068   GDALAllRegister();
00069   GDALDatasetH inputDataset = GDALOpen( TO8( mRasterFilePath ), GA_ReadOnly );
00070   if ( inputDataset == NULL )
00071   {
00072     return 3;
00073   }
00074 
00075   if ( GDALGetRasterCount( inputDataset ) < ( mRasterBand - 1 ) )
00076   {
00077     GDALClose( inputDataset );
00078     return 4;
00079   }
00080 
00081   GDALRasterBandH rasterBand = GDALGetRasterBand( inputDataset, mRasterBand );
00082   if ( rasterBand == NULL )
00083   {
00084     GDALClose( inputDataset );
00085     return 5;
00086   }
00087   mInputNodataValue = GDALGetRasterNoDataValue( rasterBand, NULL );
00088 
00089   //get geometry info about raster layer
00090   int nCellsXGDAL = GDALGetRasterXSize( inputDataset );
00091   int nCellsYGDAL = GDALGetRasterYSize( inputDataset );
00092   double geoTransform[6];
00093   if ( GDALGetGeoTransform( inputDataset, geoTransform ) != CE_None )
00094   {
00095     GDALClose( inputDataset );
00096     return 6;
00097   }
00098   double cellsizeX = geoTransform[1];
00099   if ( cellsizeX < 0 )
00100   {
00101     cellsizeX = -cellsizeX;
00102   }
00103   double cellsizeY = geoTransform[5];
00104   if ( cellsizeY < 0 )
00105   {
00106     cellsizeY = -cellsizeY;
00107   }
00108   QgsRectangle rasterBBox( geoTransform[0], geoTransform[3] - ( nCellsYGDAL * cellsizeY ),
00109                            geoTransform[0] + ( nCellsXGDAL * cellsizeX ), geoTransform[3] );
00110 
00111   //add the new count, sum, mean fields to the provider
00112   QList<QgsField> newFieldList;
00113   QgsField countField( mAttributePrefix + "count", QVariant::Double, "double precision" );
00114   QgsField sumField( mAttributePrefix + "sum", QVariant::Double, "double precision" );
00115   QgsField meanField( mAttributePrefix + "mean", QVariant::Double, "double precision" );
00116   newFieldList.push_back( countField );
00117   newFieldList.push_back( sumField );
00118   newFieldList.push_back( meanField );
00119   vectorProvider->addAttributes( newFieldList );
00120 
00121   //index of the new fields
00122   int countIndex = vectorProvider->fieldNameIndex( mAttributePrefix + "count" );
00123   int sumIndex = vectorProvider->fieldNameIndex( mAttributePrefix + "sum" );
00124   int meanIndex = vectorProvider->fieldNameIndex( mAttributePrefix + "mean" );
00125 
00126   if ( countIndex == -1 || sumIndex == -1 || meanIndex == -1 )
00127   {
00128     return 8;
00129   }
00130 
00131   //progress dialog
00132   long featureCount = vectorProvider->featureCount();
00133   if ( p )
00134   {
00135     p->setMaximum( featureCount );
00136   }
00137 
00138 
00139   //iterate over each polygon
00140   QgsFeatureRequest request;
00141   request.setSubsetOfAttributes( QgsAttributeList() );
00142   QgsFeatureIterator fi = vectorProvider->getFeatures( request );
00143   QgsFeature f;
00144   double count = 0;
00145   double sum = 0;
00146   double mean = 0;
00147   int featureCounter = 0;
00148 
00149   while ( fi.nextFeature( f ) )
00150   {
00151     if ( p )
00152     {
00153       p->setValue( featureCounter );
00154     }
00155 
00156     if ( p && p->wasCanceled() )
00157     {
00158       break;
00159     }
00160 
00161     QgsGeometry* featureGeometry = f.geometry();
00162     if ( !featureGeometry )
00163     {
00164       ++featureCounter;
00165       continue;
00166     }
00167 
00168     QgsRectangle featureRect = featureGeometry->boundingBox().intersect( &rasterBBox );
00169     if ( featureRect.isEmpty() )
00170     {
00171       ++featureCounter;
00172       continue;
00173     }
00174 
00175     int offsetX, offsetY, nCellsX, nCellsY;
00176     if ( cellInfoForBBox( rasterBBox, featureRect, cellsizeX, cellsizeY, offsetX, offsetY, nCellsX, nCellsY ) != 0 )
00177     {
00178       ++featureCounter;
00179       continue;
00180     }
00181 
00182     //avoid access to cells outside of the raster (may occur because of rounding)
00183     if (( offsetX + nCellsX ) > nCellsXGDAL )
00184     {
00185       nCellsX = nCellsXGDAL - offsetX;
00186     }
00187     if (( offsetY + nCellsY ) > nCellsYGDAL )
00188     {
00189       nCellsY = nCellsYGDAL - offsetY;
00190     }
00191 
00192     statisticsFromMiddlePointTest( rasterBand, featureGeometry, offsetX, offsetY, nCellsX, nCellsY, cellsizeX, cellsizeY,
00193                                    rasterBBox, sum, count );
00194 
00195     if ( count <= 1 )
00196     {
00197       //the cell resolution is probably larger than the polygon area. We switch to precise pixel - polygon intersection in this case
00198       statisticsFromPreciseIntersection( rasterBand, featureGeometry, offsetX, offsetY, nCellsX, nCellsY, cellsizeX, cellsizeY,
00199                                          rasterBBox, sum, count );
00200     }
00201 
00202 
00203     if ( count == 0 )
00204     {
00205       mean = 0;
00206     }
00207     else
00208     {
00209       mean = sum / count;
00210     }
00211 
00212     //write the statistics value to the vector data provider
00213     QgsChangedAttributesMap changeMap;
00214     QgsAttributeMap changeAttributeMap;
00215     changeAttributeMap.insert( countIndex, QVariant( count ) );
00216     changeAttributeMap.insert( sumIndex, QVariant( sum ) );
00217     changeAttributeMap.insert( meanIndex, QVariant( mean ) );
00218     changeMap.insert( f.id(), changeAttributeMap );
00219     vectorProvider->changeAttributeValues( changeMap );
00220 
00221     ++featureCounter;
00222   }
00223 
00224   if ( p )
00225   {
00226     p->setValue( featureCount );
00227   }
00228 
00229   GDALClose( inputDataset );
00230 
00231   if ( p && p->wasCanceled() )
00232   {
00233     return 9;
00234   }
00235 
00236   return 0;
00237 }
00238 
00239 int QgsZonalStatistics::cellInfoForBBox( const QgsRectangle& rasterBBox, const QgsRectangle& featureBBox, double cellSizeX, double cellSizeY,
00240     int& offsetX, int& offsetY, int& nCellsX, int& nCellsY ) const
00241 {
00242   //get intersecting bbox
00243   QgsRectangle intersectBox = rasterBBox.intersect( &featureBBox );
00244   if ( intersectBox.isEmpty() )
00245   {
00246     nCellsX = 0; nCellsY = 0; offsetX = 0; offsetY = 0;
00247     return 0;
00248   }
00249 
00250   //get offset in pixels in x- and y- direction
00251   offsetX = ( int )(( intersectBox.xMinimum() - rasterBBox.xMinimum() ) / cellSizeX );
00252   offsetY = ( int )(( rasterBBox.yMaximum() - intersectBox.yMaximum() ) / cellSizeY );
00253 
00254   int maxColumn = ( int )(( intersectBox.xMaximum() - rasterBBox.xMinimum() ) / cellSizeX ) + 1;
00255   int maxRow = ( int )(( rasterBBox.yMaximum() - intersectBox.yMinimum() ) / cellSizeY ) + 1;
00256 
00257   nCellsX = maxColumn - offsetX;
00258   nCellsY = maxRow - offsetY;
00259 
00260   return 0;
00261 }
00262 
00263 void QgsZonalStatistics::statisticsFromMiddlePointTest( void* band, QgsGeometry* poly, int pixelOffsetX,
00264     int pixelOffsetY, int nCellsX, int nCellsY, double cellSizeX, double cellSizeY, const QgsRectangle& rasterBBox, double& sum, double& count )
00265 {
00266   double cellCenterX, cellCenterY;
00267 
00268   float* scanLine = ( float * ) CPLMalloc( sizeof( float ) * nCellsX );
00269   cellCenterY = rasterBBox.yMaximum() - pixelOffsetY * cellSizeY - cellSizeY / 2;
00270   count = 0;
00271   sum = 0;
00272 
00273   GEOSGeometry* polyGeos = poly->asGeos();
00274   if ( !polyGeos )
00275   {
00276     return;
00277   }
00278 
00279   const GEOSPreparedGeometry* polyGeosPrepared = GEOSPrepare( poly->asGeos() );
00280   if ( !polyGeosPrepared )
00281   {
00282     return;
00283   }
00284 
00285   GEOSCoordSequence* cellCenterCoords = 0;
00286   GEOSGeometry* currentCellCenter = 0;
00287 
00288   for ( int i = 0; i < nCellsY; ++i )
00289   {
00290     if ( GDALRasterIO( band, GF_Read, pixelOffsetX, pixelOffsetY + i, nCellsX, 1, scanLine, nCellsX, 1, GDT_Float32, 0, 0 )
00291          != CPLE_None )
00292     {
00293       continue;
00294     }
00295     cellCenterX = rasterBBox.xMinimum() + pixelOffsetX * cellSizeX + cellSizeX / 2;
00296     for ( int j = 0; j < nCellsX; ++j )
00297     {
00298       GEOSGeom_destroy( currentCellCenter );
00299       cellCenterCoords = GEOSCoordSeq_create( 1, 2 );
00300       GEOSCoordSeq_setX( cellCenterCoords, 0, cellCenterX );
00301       GEOSCoordSeq_setY( cellCenterCoords, 0, cellCenterY );
00302       currentCellCenter = GEOSGeom_createPoint( cellCenterCoords );
00303 
00304       if ( GEOSPreparedContains( polyGeosPrepared, currentCellCenter ) )
00305       {
00306         if ( scanLine[j] != mInputNodataValue ) //don't consider nodata values
00307         {
00308           sum += scanLine[j];
00309           ++count;
00310         }
00311       }
00312       cellCenterX += cellSizeX;
00313     }
00314     cellCenterY -= cellSizeY;
00315   }
00316   CPLFree( scanLine );
00317   GEOSPreparedGeom_destroy( polyGeosPrepared );
00318 }
00319 
00320 void QgsZonalStatistics::statisticsFromPreciseIntersection( void* band, QgsGeometry* poly, int pixelOffsetX,
00321     int pixelOffsetY, int nCellsX, int nCellsY, double cellSizeX, double cellSizeY, const QgsRectangle& rasterBBox, double& sum, double& count )
00322 {
00323   sum = 0;
00324   count = 0;
00325   double currentY = rasterBBox.yMaximum() - pixelOffsetY * cellSizeY - cellSizeY / 2;
00326   float* pixelData = ( float * ) CPLMalloc( sizeof( float ) );
00327   QgsGeometry* pixelRectGeometry = 0;
00328 
00329   double hCellSizeX = cellSizeX / 2.0;
00330   double hCellSizeY = cellSizeY / 2.0;
00331   double pixelArea = cellSizeX * cellSizeY;
00332   double weight = 0;
00333 
00334   for ( int row = 0; row < nCellsY; ++row )
00335   {
00336     double currentX = rasterBBox.xMinimum() + cellSizeX / 2.0 + pixelOffsetX * cellSizeX;
00337     for ( int col = 0; col < nCellsX; ++col )
00338     {
00339       GDALRasterIO( band, GF_Read, pixelOffsetX + col, pixelOffsetY + row, nCellsX, 1, pixelData, 1, 1, GDT_Float32, 0, 0 );
00340       pixelRectGeometry = QgsGeometry::fromRect( QgsRectangle( currentX - hCellSizeX, currentY - hCellSizeY, currentX + hCellSizeX, currentY + hCellSizeY ) );
00341       if ( pixelRectGeometry )
00342       {
00343         //intersection
00344         QgsGeometry *intersectGeometry = pixelRectGeometry->intersection( poly );
00345         if ( intersectGeometry )
00346         {
00347           double intersectionArea = intersectGeometry->area();
00348           if ( intersectionArea >= 0.0 )
00349           {
00350             weight = intersectionArea / pixelArea;
00351             count += weight;
00352             sum += *pixelData * weight;
00353           }
00354           delete intersectGeometry;
00355         }
00356       }
00357       currentX += cellSizeX;
00358     }
00359     currentY -= cellSizeY;
00360   }
00361   CPLFree( pixelData );
00362 }
00363 
00364 
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Defines