QGIS API Documentation 4.1.0-Master (5bf3c20f3c9)
Loading...
Searching...
No Matches
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
21#include "qgsfeatureiterator.h"
22#include "qgsfeedback.h"
23#include "qgsgeometry.h"
24#include "qgsproject.h"
25#include "qgsrasterlayer.h"
27#include "qgsvectorlayer.h"
28
29#include <QFile>
30#include <QString>
31
32using namespace Qt::StringLiterals;
33
34QgsZonalStatistics::QgsZonalStatistics( QgsVectorLayer *polygonLayer, QgsRasterLayer *rasterLayer, const QString &attributePrefix, int rasterBand, Qgis::ZonalStatistics stats )
36 polygonLayer,
37 rasterLayer ? rasterLayer->dataProvider() : nullptr,
38 rasterLayer ? rasterLayer->crs() : QgsCoordinateReferenceSystem(),
39 rasterLayer ? rasterLayer->rasterUnitsPerPixelX() : 0,
40 rasterLayer ? rasterLayer->rasterUnitsPerPixelY() : 0,
41 attributePrefix,
42 rasterBand,
43 stats
44 )
45{}
46
48 QgsVectorLayer *polygonLayer,
49 QgsRasterInterface *rasterInterface,
50 const QgsCoordinateReferenceSystem &rasterCrs,
51 double rasterUnitsPerPixelX,
52 double rasterUnitsPerPixelY,
53 const QString &attributePrefix,
54 int rasterBand,
56)
57 : mRasterInterface( rasterInterface )
58 , mRasterCrs( rasterCrs )
59 , mCellSizeX( std::fabs( rasterUnitsPerPixelX ) )
60 , mCellSizeY( std::fabs( rasterUnitsPerPixelY ) )
61 , mRasterBand( rasterBand )
62 , mPolygonLayer( polygonLayer )
63 , mAttributePrefix( attributePrefix )
64 , mStatistics( stats )
65{}
66
68{
69 if ( !mRasterInterface )
70 {
72 }
73
74 if ( mRasterInterface->bandCount() < mRasterBand )
75 {
77 }
78
79 if ( !mPolygonLayer || mPolygonLayer->geometryType() != Qgis::GeometryType::Polygon )
80 {
82 }
83
84 QgsVectorDataProvider *vectorProvider = mPolygonLayer->dataProvider();
85 if ( !vectorProvider )
86 {
88 }
89
90 QMap<Qgis::ZonalStatistic, int> statFieldIndexes;
91
92 //add the new fields to the provider
93 int oldFieldCount = vectorProvider->fields().count();
94 QList<QgsField> newFieldList;
95 for ( Qgis::ZonalStatistic stat :
108 {
109 if ( mStatistics & stat )
110 {
111 QString fieldName = getUniqueFieldName( mAttributePrefix + QgsZonalStatistics::shortName( stat ), newFieldList );
112 QgsField field( fieldName, QMetaType::Type::Double, u"double precision"_s );
113 newFieldList.push_back( field );
114 statFieldIndexes.insert( stat, oldFieldCount + newFieldList.count() - 1 );
115 }
116 }
117
118 vectorProvider->addAttributes( newFieldList );
119
120 long featureCount = vectorProvider->featureCount();
121
122 QgsFeatureRequest request;
123 request.setNoAttributes();
124
125 request.setDestinationCrs( mRasterCrs, QgsProject::instance()->transformContext() );
126 QgsFeatureIterator fi = vectorProvider->getFeatures( request );
127 QgsFeature feature;
128
129 int featureCounter = 0;
130
131 QgsChangedAttributesMap changeMap;
132 while ( fi.nextFeature( feature ) )
133 {
134 ++featureCounter;
135 if ( feedback && feedback->isCanceled() )
136 {
137 break;
138 }
139
140 if ( feedback )
141 {
142 feedback->setProgress( 100.0 * static_cast<double>( featureCounter ) / featureCount );
143 }
144
145 QgsGeometry featureGeometry = feature.geometry();
146
147 QMap<Qgis::ZonalStatistic, QVariant> results = calculateStatistics( mRasterInterface, featureGeometry, mCellSizeX, mCellSizeY, mRasterBand, mStatistics );
148
149 if ( results.empty() )
150 continue;
151
152 QgsAttributeMap changeAttributeMap;
153 for ( const auto &result : results.toStdMap() )
154 {
155 changeAttributeMap.insert( statFieldIndexes.value( result.first ), result.second );
156 }
157
158 changeMap.insert( feature.id(), changeAttributeMap );
159 }
160
161 vectorProvider->changeAttributeValues( changeMap );
162 mPolygonLayer->updateFields();
163
164 if ( feedback )
165 {
166 if ( feedback->isCanceled() )
168
169 feedback->setProgress( 100 );
170 }
171
173}
174
175QString QgsZonalStatistics::getUniqueFieldName( const QString &fieldName, const QList<QgsField> &newFields )
176{
177 QgsVectorDataProvider *dp = mPolygonLayer->dataProvider();
178
179 if ( !dp->storageType().contains( "ESRI Shapefile"_L1 ) )
180 {
181 return fieldName;
182 }
183
184 QList<QgsField> allFields = dp->fields().toList();
185 allFields.append( newFields );
186 QString shortName = fieldName.mid( 0, 10 );
187
188 bool found = false;
189 for ( const QgsField &field : std::as_const( allFields ) )
190 {
191 if ( shortName == field.name() )
192 {
193 found = true;
194 break;
195 }
196 }
197
198 if ( !found )
199 {
200 return shortName;
201 }
202
203 int n = 1;
204 shortName = u"%1_%2"_s.arg( fieldName.mid( 0, 8 ) ).arg( n );
205 found = true;
206 while ( found )
207 {
208 found = false;
209 for ( const QgsField &field : std::as_const( allFields ) )
210 {
211 if ( shortName == field.name() )
212 {
213 n += 1;
214 if ( n < 9 )
215 {
216 shortName = u"%1_%2"_s.arg( fieldName.mid( 0, 8 ) ).arg( n );
217 }
218 else
219 {
220 shortName = u"%1_%2"_s.arg( fieldName.mid( 0, 7 ) ).arg( n );
221 }
222 found = true;
223 }
224 }
225 }
226 return shortName;
227}
228
230{
231 switch ( statistic )
232 {
234 return QObject::tr( "Count" );
236 return QObject::tr( "Sum" );
238 return QObject::tr( "Mean" );
240 return QObject::tr( "Median" );
242 return QObject::tr( "St dev" );
244 return QObject::tr( "Minimum" );
246 return QObject::tr( "Maximum" );
248 return QObject::tr( "Minimum point" );
250 return QObject::tr( "Maximum point" );
252 return QObject::tr( "Range" );
254 return QObject::tr( "Minority" );
256 return QObject::tr( "Majority" );
258 return QObject::tr( "Variety" );
260 return QObject::tr( "Variance" );
263 return QString();
264 }
265 return QString();
266}
267
269{
270 switch ( statistic )
271 {
273 return u"count"_s;
275 return u"sum"_s;
277 return u"mean"_s;
279 return u"median"_s;
281 return u"stdev"_s;
283 return u"min"_s;
285 return u"max"_s;
287 return u"minpoint"_s;
289 return u"maxpoint"_s;
291 return u"range"_s;
293 return u"minority"_s;
295 return u"majority"_s;
297 return u"variety"_s;
299 return u"variance"_s;
302 return QString();
303 }
304 return QString();
305}
306
308QMap<int, QVariant> QgsZonalStatistics::calculateStatisticsInt(
309 QgsRasterInterface *rasterInterface, const QgsGeometry &geometry, double cellSizeX, double cellSizeY, int rasterBand, Qgis::ZonalStatistics statistics
310)
311{
312 const auto result { QgsZonalStatistics::calculateStatistics( rasterInterface, geometry, cellSizeX, cellSizeY, rasterBand, statistics ) };
313 QMap<int, QVariant> pyResult;
314 for ( auto it = result.constBegin(); it != result.constEnd(); ++it )
315 {
316 pyResult.insert( static_cast<int>( it.key() ), it.value() );
317 }
318 return pyResult;
319}
321
322QMap<Qgis::ZonalStatistic, QVariant> QgsZonalStatistics::calculateStatistics(
323 QgsRasterInterface *rasterInterface, const QgsGeometry &geometry, double cellSizeX, double cellSizeY, int rasterBand, Qgis::ZonalStatistics statistics
324)
325{
326 QMap<Qgis::ZonalStatistic, QVariant> results;
327
328 if ( geometry.isEmpty() )
329 return results;
330
331 QgsRectangle rasterBBox = rasterInterface->extent();
332
333 QgsRectangle featureRect = geometry.boundingBox().intersect( rasterBBox );
334
335 if ( featureRect.isEmpty() )
336 return results;
337
338 bool statsStoreValues = ( statistics & Qgis::ZonalStatistic::Median ) || ( statistics & Qgis::ZonalStatistic::StDev ) || ( statistics & Qgis::ZonalStatistic::Variance );
339 bool statsStoreValueCount = ( statistics & Qgis::ZonalStatistic::Minority ) || ( statistics & Qgis::ZonalStatistic::Majority );
340
341 FeatureStats featureStats( statsStoreValues, statsStoreValueCount );
342
343 int nCellsXProvider = rasterInterface->xSize();
344 int nCellsYProvider = rasterInterface->ySize();
345
346 int nCellsX, nCellsY;
347 QgsRectangle rasterBlockExtent;
348 QgsRasterAnalysisUtils::cellInfoForBBox( rasterBBox, featureRect, cellSizeX, cellSizeY, nCellsX, nCellsY, nCellsXProvider, nCellsYProvider, rasterBlockExtent );
349
350 featureStats.reset();
351 QgsRasterAnalysisUtils::statisticsFromMiddlePointTest( rasterInterface, rasterBand, geometry, nCellsX, nCellsY, cellSizeX, cellSizeY, rasterBlockExtent, [&featureStats]( double value, const QgsPointXY &point ) {
352 featureStats.addValue( value, point );
353 } );
354
355 if ( featureStats.count <= 1 )
356 {
357 //the cell resolution is probably larger than the polygon area. We switch to precise pixel - polygon intersection in this case
358 featureStats.reset();
359 QgsRasterAnalysisUtils::
360 statisticsFromPreciseIntersection( rasterInterface, rasterBand, geometry, nCellsX, nCellsY, cellSizeX, cellSizeY, rasterBlockExtent, [&featureStats]( double value, double weight, const QgsPointXY &point ) {
361 featureStats.addValue( value, point, weight );
362 } );
363 }
364
365 // calculate the statistics
366
367 if ( statistics & Qgis::ZonalStatistic::Count )
368 results.insert( Qgis::ZonalStatistic::Count, QVariant( featureStats.count ) );
369 if ( statistics & Qgis::ZonalStatistic::Sum )
370 results.insert( Qgis::ZonalStatistic::Sum, QVariant( featureStats.sum ) );
371 if ( featureStats.count > 0 )
372 {
373 double mean = featureStats.sum / featureStats.count;
374 if ( statistics & Qgis::ZonalStatistic::Mean )
375 results.insert( Qgis::ZonalStatistic::Mean, QVariant( mean ) );
376 if ( statistics & Qgis::ZonalStatistic::Median )
377 {
378 std::sort( featureStats.values.begin(), featureStats.values.end() );
379 int size = featureStats.values.count();
380 bool even = ( size % 2 ) < 1;
381 double medianValue;
382 if ( even )
383 {
384 medianValue = ( featureStats.values.at( size / 2 - 1 ) + featureStats.values.at( size / 2 ) ) / 2;
385 }
386 else //odd
387 {
388 medianValue = featureStats.values.at( ( size + 1 ) / 2 - 1 );
389 }
390 results.insert( Qgis::ZonalStatistic::Median, QVariant( medianValue ) );
391 }
392 if ( statistics & Qgis::ZonalStatistic::StDev || statistics & Qgis::ZonalStatistic::Variance )
393 {
394 double sumSquared = 0;
395 for ( int i = 0; i < featureStats.values.count(); ++i )
396 {
397 double diff = featureStats.values.at( i ) - mean;
398 sumSquared += diff * diff;
399 }
400 double variance = sumSquared / featureStats.values.count();
401 if ( statistics & Qgis::ZonalStatistic::StDev )
402 {
403 double stdev = std::pow( variance, 0.5 );
404 results.insert( Qgis::ZonalStatistic::StDev, QVariant( stdev ) );
405 }
406 if ( statistics & Qgis::ZonalStatistic::Variance )
407 results.insert( Qgis::ZonalStatistic::Variance, QVariant( variance ) );
408 }
409 if ( statistics & Qgis::ZonalStatistic::Min )
410 results.insert( Qgis::ZonalStatistic::Min, QVariant( featureStats.min ) );
411 if ( statistics & Qgis::ZonalStatistic::Max )
412 results.insert( Qgis::ZonalStatistic::Max, QVariant( featureStats.max ) );
413 if ( statistics & Qgis::ZonalStatistic::MinimumPoint )
414 results.insert( Qgis::ZonalStatistic::MinimumPoint, QVariant( featureStats.minPoint ) );
415 if ( statistics & Qgis::ZonalStatistic::MaximumPoint )
416 results.insert( Qgis::ZonalStatistic::MaximumPoint, QVariant( featureStats.maxPoint ) );
417 if ( statistics & Qgis::ZonalStatistic::Range )
418 results.insert( Qgis::ZonalStatistic::Range, QVariant( featureStats.max - featureStats.min ) );
419 if ( statistics & Qgis::ZonalStatistic::Minority || statistics & Qgis::ZonalStatistic::Majority )
420 {
421 QList<int> vals = featureStats.valueCount.values();
422 std::sort( vals.begin(), vals.end() );
423 if ( statistics & Qgis::ZonalStatistic::Minority )
424 {
425 double minorityKey = featureStats.valueCount.key( vals.first() );
426 results.insert( Qgis::ZonalStatistic::Minority, QVariant( minorityKey ) );
427 }
428 if ( statistics & Qgis::ZonalStatistic::Majority )
429 {
430 double majKey = featureStats.valueCount.key( vals.last() );
431 results.insert( Qgis::ZonalStatistic::Majority, QVariant( majKey ) );
432 }
433 }
434 if ( statistics & Qgis::ZonalStatistic::Variety )
435 results.insert( Qgis::ZonalStatistic::Variety, QVariant( featureStats.valueCount.count() ) );
436 }
437
438 return results;
439}
ZonalStatistic
Statistics to be calculated during a zonal statistics operation.
Definition qgis.h:6119
@ StDev
Standard deviation of pixel values.
Definition qgis.h:6124
@ Mean
Mean of pixel values.
Definition qgis.h:6122
@ Median
Median of pixel values.
Definition qgis.h:6123
@ Max
Max of pixel values.
Definition qgis.h:6126
@ Variance
Variance of pixel values.
Definition qgis.h:6131
@ MinimumPoint
Pixel centroid for minimum pixel value.
Definition qgis.h:6132
@ Min
Min of pixel values.
Definition qgis.h:6125
@ Default
Default statistics.
Definition qgis.h:6138
@ Range
Range of pixel values (max - min).
Definition qgis.h:6127
@ Sum
Sum of pixel values.
Definition qgis.h:6121
@ Minority
Minority of pixel values.
Definition qgis.h:6128
@ All
All statistics. For QGIS 3.x this includes ONLY numeric statistics, but for 4.0 this will be extended...
Definition qgis.h:6135
@ Majority
Majority of pixel values.
Definition qgis.h:6129
@ Variety
Variety (count of distinct) pixel values.
Definition qgis.h:6130
@ MaximumPoint
Pixel centroid for maximum pixel value.
Definition qgis.h:6133
@ Count
Pixel count.
Definition qgis.h:6120
@ Polygon
Polygons.
Definition qgis.h:382
ZonalStatisticResult
Zonal statistics result codes.
Definition qgis.h:6156
@ Canceled
Algorithm was canceled.
Definition qgis.h:6163
@ LayerTypeWrong
Layer is not a polygon layer.
Definition qgis.h:6158
@ RasterBandInvalid
The raster band does not exist on the raster layer.
Definition qgis.h:6161
@ RasterInvalid
Raster layer is invalid.
Definition qgis.h:6160
@ LayerInvalid
Layer is invalid.
Definition qgis.h:6159
QFlags< ZonalStatistic > ZonalStatistics
Statistics to be calculated during a zonal statistics operation.
Definition qgis.h:6147
Represents a coordinate reference system (CRS).
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
Wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setDestinationCrs(const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &context)
Sets the destination crs for feature's geometries.
QgsFeatureRequest & setNoAttributes()
Set that no attributes will be fetched.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:60
QgsFeatureId id
Definition qgsfeature.h:68
QgsGeometry geometry
Definition qgsfeature.h:71
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition qgsfeedback.h:44
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:56
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:65
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:56
int count
Definition qgsfields.h:50
QList< QgsField > toList() const
Utility function to return a list of QgsField instances.
A geometry is the spatial representation of a feature.
bool isEmpty() const
Returns true if the geometry is empty (eg a linestring with no vertices, or a collection with no geom...
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
Represents a 2D point.
Definition qgspointxy.h:62
static QgsProject * instance()
Returns the QgsProject singleton instance.
Base class for processing filters like renderers, reprojector, resampler etc.
virtual int xSize() const
Gets raster size.
virtual int ySize() const
virtual QgsRectangle extent() const
Gets the extent of the interface.
Represents a raster layer.
A rectangle specified with double values.
QgsRectangle intersect(const QgsRectangle &rect) const
Returns the intersection with the given rectangle.
Base class for vector data providers.
long long featureCount() const override=0
Number of features in the layer.
virtual QString storageType() const
Returns the permanent storage type for this layer as a friendly name.
virtual bool changeAttributeValues(const QgsChangedAttributesMap &attr_map)
Changes attribute values of existing features.
QgsFields fields() const override=0
Returns the fields associated with this data provider.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const override=0
Query the provider for features specified in request.
virtual bool addAttributes(const QList< QgsField > &attributes)
Adds new attributes to the provider.
Represents a vector layer which manages a vector based dataset.
QgsVectorDataProvider * dataProvider() final
Returns the layer's data provider, it may be nullptr.
QgsZonalStatistics(QgsVectorLayer *polygonLayer, QgsRasterLayer *rasterLayer, const QString &attributePrefix=QString(), int rasterBand=1, Qgis::ZonalStatistics stats=Qgis::ZonalStatistic::Default)
Convenience constructor for QgsZonalStatistics, using an input raster layer.
Qgis::ZonalStatisticResult calculateStatistics(QgsFeedback *feedback)
Runs the calculation.
static QString displayName(Qgis::ZonalStatistic statistic)
Returns the friendly display name for a statistic.
static QString shortName(Qgis::ZonalStatistic statistic)
Returns a short, friendly display name for a statistic, suitable for use in a field name.
QMap< int, QVariant > QgsAttributeMap
QMap< QgsFeatureId, QgsAttributeMap > QgsChangedAttributesMap