QGIS API Documentation 3.99.0-Master (e9821da5c6b)
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 )
35 : QgsZonalStatistics( polygonLayer, rasterLayer ? rasterLayer->dataProvider() : nullptr, rasterLayer ? rasterLayer->crs() : QgsCoordinateReferenceSystem(), rasterLayer ? rasterLayer->rasterUnitsPerPixelX() : 0, rasterLayer ? rasterLayer->rasterUnitsPerPixelY() : 0, attributePrefix, rasterBand, stats )
36{
37}
38
39QgsZonalStatistics::QgsZonalStatistics( QgsVectorLayer *polygonLayer, QgsRasterInterface *rasterInterface, const QgsCoordinateReferenceSystem &rasterCrs, double rasterUnitsPerPixelX, double rasterUnitsPerPixelY, const QString &attributePrefix, int rasterBand, Qgis::ZonalStatistics stats )
40 : mRasterInterface( rasterInterface )
41 , mRasterCrs( rasterCrs )
42 , mCellSizeX( std::fabs( rasterUnitsPerPixelX ) )
43 , mCellSizeY( std::fabs( rasterUnitsPerPixelY ) )
44 , mRasterBand( rasterBand )
45 , mPolygonLayer( polygonLayer )
46 , mAttributePrefix( attributePrefix )
47 , mStatistics( stats )
48{
49}
50
52{
53 if ( !mRasterInterface )
54 {
56 }
57
58 if ( mRasterInterface->bandCount() < mRasterBand )
59 {
61 }
62
63 if ( !mPolygonLayer || mPolygonLayer->geometryType() != Qgis::GeometryType::Polygon )
64 {
66 }
67
68 QgsVectorDataProvider *vectorProvider = mPolygonLayer->dataProvider();
69 if ( !vectorProvider )
70 {
72 }
73
74 QMap<Qgis::ZonalStatistic, int> statFieldIndexes;
75
76 //add the new fields to the provider
77 int oldFieldCount = vectorProvider->fields().count();
78 QList<QgsField> newFieldList;
79 for ( Qgis::ZonalStatistic stat :
80 {
93 } )
94 {
95 if ( mStatistics & stat )
96 {
97 QString fieldName = getUniqueFieldName( mAttributePrefix + QgsZonalStatistics::shortName( stat ), newFieldList );
98 QgsField field( fieldName, QMetaType::Type::Double, u"double precision"_s );
99 newFieldList.push_back( field );
100 statFieldIndexes.insert( stat, oldFieldCount + newFieldList.count() - 1 );
101 }
102 }
103
104 vectorProvider->addAttributes( newFieldList );
105
106 long featureCount = vectorProvider->featureCount();
107
108 QgsFeatureRequest request;
109 request.setNoAttributes();
110
111 request.setDestinationCrs( mRasterCrs, QgsProject::instance()->transformContext() );
112 QgsFeatureIterator fi = vectorProvider->getFeatures( request );
113 QgsFeature feature;
114
115 int featureCounter = 0;
116
117 QgsChangedAttributesMap changeMap;
118 while ( fi.nextFeature( feature ) )
119 {
120 ++featureCounter;
121 if ( feedback && feedback->isCanceled() )
122 {
123 break;
124 }
125
126 if ( feedback )
127 {
128 feedback->setProgress( 100.0 * static_cast<double>( featureCounter ) / featureCount );
129 }
130
131 QgsGeometry featureGeometry = feature.geometry();
132
133 QMap<Qgis::ZonalStatistic, QVariant> results = calculateStatistics( mRasterInterface, featureGeometry, mCellSizeX, mCellSizeY, mRasterBand, mStatistics );
134
135 if ( results.empty() )
136 continue;
137
138 QgsAttributeMap changeAttributeMap;
139 for ( const auto &result : results.toStdMap() )
140 {
141 changeAttributeMap.insert( statFieldIndexes.value( result.first ), result.second );
142 }
143
144 changeMap.insert( feature.id(), changeAttributeMap );
145 }
146
147 vectorProvider->changeAttributeValues( changeMap );
148 mPolygonLayer->updateFields();
149
150 if ( feedback )
151 {
152 if ( feedback->isCanceled() )
154
155 feedback->setProgress( 100 );
156 }
157
159}
160
161QString QgsZonalStatistics::getUniqueFieldName( const QString &fieldName, const QList<QgsField> &newFields )
162{
163 QgsVectorDataProvider *dp = mPolygonLayer->dataProvider();
164
165 if ( !dp->storageType().contains( "ESRI Shapefile"_L1 ) )
166 {
167 return fieldName;
168 }
169
170 QList<QgsField> allFields = dp->fields().toList();
171 allFields.append( newFields );
172 QString shortName = fieldName.mid( 0, 10 );
173
174 bool found = false;
175 for ( const QgsField &field : std::as_const( allFields ) )
176 {
177 if ( shortName == field.name() )
178 {
179 found = true;
180 break;
181 }
182 }
183
184 if ( !found )
185 {
186 return shortName;
187 }
188
189 int n = 1;
190 shortName = u"%1_%2"_s.arg( fieldName.mid( 0, 8 ) ).arg( n );
191 found = true;
192 while ( found )
193 {
194 found = false;
195 for ( const QgsField &field : std::as_const( allFields ) )
196 {
197 if ( shortName == field.name() )
198 {
199 n += 1;
200 if ( n < 9 )
201 {
202 shortName = u"%1_%2"_s.arg( fieldName.mid( 0, 8 ) ).arg( n );
203 }
204 else
205 {
206 shortName = u"%1_%2"_s.arg( fieldName.mid( 0, 7 ) ).arg( n );
207 }
208 found = true;
209 }
210 }
211 }
212 return shortName;
213}
214
216{
217 switch ( statistic )
218 {
220 return QObject::tr( "Count" );
222 return QObject::tr( "Sum" );
224 return QObject::tr( "Mean" );
226 return QObject::tr( "Median" );
228 return QObject::tr( "St dev" );
230 return QObject::tr( "Minimum" );
232 return QObject::tr( "Maximum" );
234 return QObject::tr( "Minimum point" );
236 return QObject::tr( "Maximum point" );
238 return QObject::tr( "Range" );
240 return QObject::tr( "Minority" );
242 return QObject::tr( "Majority" );
244 return QObject::tr( "Variety" );
246 return QObject::tr( "Variance" );
249 return QString();
250 }
251 return QString();
252}
253
255{
256 switch ( statistic )
257 {
259 return u"count"_s;
261 return u"sum"_s;
263 return u"mean"_s;
265 return u"median"_s;
267 return u"stdev"_s;
269 return u"min"_s;
271 return u"max"_s;
273 return u"minpoint"_s;
275 return u"maxpoint"_s;
277 return u"range"_s;
279 return u"minority"_s;
281 return u"majority"_s;
283 return u"variety"_s;
285 return u"variance"_s;
288 return QString();
289 }
290 return QString();
291}
292
294QMap<int, QVariant> QgsZonalStatistics::calculateStatisticsInt( QgsRasterInterface *rasterInterface, const QgsGeometry &geometry, double cellSizeX, double cellSizeY, int rasterBand, Qgis::ZonalStatistics statistics )
295{
296 const auto result { QgsZonalStatistics::calculateStatistics( rasterInterface, geometry, cellSizeX, cellSizeY, rasterBand, statistics ) };
297 QMap<int, QVariant> pyResult;
298 for ( auto it = result.constBegin(); it != result.constEnd(); ++it )
299 {
300 pyResult.insert( static_cast<int>( it.key() ), it.value() );
301 }
302 return pyResult;
303}
305
306QMap<Qgis::ZonalStatistic, QVariant> QgsZonalStatistics::calculateStatistics( QgsRasterInterface *rasterInterface, const QgsGeometry &geometry, double cellSizeX, double cellSizeY, int rasterBand, Qgis::ZonalStatistics statistics )
307{
308 QMap<Qgis::ZonalStatistic, QVariant> results;
309
310 if ( geometry.isEmpty() )
311 return results;
312
313 QgsRectangle rasterBBox = rasterInterface->extent();
314
315 QgsRectangle featureRect = geometry.boundingBox().intersect( rasterBBox );
316
317 if ( featureRect.isEmpty() )
318 return results;
319
320 bool statsStoreValues = ( statistics & Qgis::ZonalStatistic::Median ) || ( statistics & Qgis::ZonalStatistic::StDev ) || ( statistics & Qgis::ZonalStatistic::Variance );
321 bool statsStoreValueCount = ( statistics & Qgis::ZonalStatistic::Minority ) || ( statistics & Qgis::ZonalStatistic::Majority );
322
323 FeatureStats featureStats( statsStoreValues, statsStoreValueCount );
324
325 int nCellsXProvider = rasterInterface->xSize();
326 int nCellsYProvider = rasterInterface->ySize();
327
328 int nCellsX, nCellsY;
329 QgsRectangle rasterBlockExtent;
330 QgsRasterAnalysisUtils::cellInfoForBBox( rasterBBox, featureRect, cellSizeX, cellSizeY, nCellsX, nCellsY, nCellsXProvider, nCellsYProvider, rasterBlockExtent );
331
332 featureStats.reset();
333 QgsRasterAnalysisUtils::statisticsFromMiddlePointTest( rasterInterface, rasterBand, geometry, nCellsX, nCellsY, cellSizeX, cellSizeY, rasterBlockExtent, [&featureStats]( double value, const QgsPointXY &point ) { featureStats.addValue( value, point ); } );
334
335 if ( featureStats.count <= 1 )
336 {
337 //the cell resolution is probably larger than the polygon area. We switch to precise pixel - polygon intersection in this case
338 featureStats.reset();
339 QgsRasterAnalysisUtils::statisticsFromPreciseIntersection( rasterInterface, rasterBand, geometry, nCellsX, nCellsY, cellSizeX, cellSizeY, rasterBlockExtent, [&featureStats]( double value, double weight, const QgsPointXY &point ) { featureStats.addValue( value, point, weight ); } );
340 }
341
342 // calculate the statistics
343
344 if ( statistics & Qgis::ZonalStatistic::Count )
345 results.insert( Qgis::ZonalStatistic::Count, QVariant( featureStats.count ) );
346 if ( statistics & Qgis::ZonalStatistic::Sum )
347 results.insert( Qgis::ZonalStatistic::Sum, QVariant( featureStats.sum ) );
348 if ( featureStats.count > 0 )
349 {
350 double mean = featureStats.sum / featureStats.count;
351 if ( statistics & Qgis::ZonalStatistic::Mean )
352 results.insert( Qgis::ZonalStatistic::Mean, QVariant( mean ) );
353 if ( statistics & Qgis::ZonalStatistic::Median )
354 {
355 std::sort( featureStats.values.begin(), featureStats.values.end() );
356 int size = featureStats.values.count();
357 bool even = ( size % 2 ) < 1;
358 double medianValue;
359 if ( even )
360 {
361 medianValue = ( featureStats.values.at( size / 2 - 1 ) + featureStats.values.at( size / 2 ) ) / 2;
362 }
363 else //odd
364 {
365 medianValue = featureStats.values.at( ( size + 1 ) / 2 - 1 );
366 }
367 results.insert( Qgis::ZonalStatistic::Median, QVariant( medianValue ) );
368 }
369 if ( statistics & Qgis::ZonalStatistic::StDev || statistics & Qgis::ZonalStatistic::Variance )
370 {
371 double sumSquared = 0;
372 for ( int i = 0; i < featureStats.values.count(); ++i )
373 {
374 double diff = featureStats.values.at( i ) - mean;
375 sumSquared += diff * diff;
376 }
377 double variance = sumSquared / featureStats.values.count();
378 if ( statistics & Qgis::ZonalStatistic::StDev )
379 {
380 double stdev = std::pow( variance, 0.5 );
381 results.insert( Qgis::ZonalStatistic::StDev, QVariant( stdev ) );
382 }
383 if ( statistics & Qgis::ZonalStatistic::Variance )
384 results.insert( Qgis::ZonalStatistic::Variance, QVariant( variance ) );
385 }
386 if ( statistics & Qgis::ZonalStatistic::Min )
387 results.insert( Qgis::ZonalStatistic::Min, QVariant( featureStats.min ) );
388 if ( statistics & Qgis::ZonalStatistic::Max )
389 results.insert( Qgis::ZonalStatistic::Max, QVariant( featureStats.max ) );
390 if ( statistics & Qgis::ZonalStatistic::MinimumPoint )
391 results.insert( Qgis::ZonalStatistic::MinimumPoint, QVariant( featureStats.minPoint ) );
392 if ( statistics & Qgis::ZonalStatistic::MaximumPoint )
393 results.insert( Qgis::ZonalStatistic::MaximumPoint, QVariant( featureStats.maxPoint ) );
394 if ( statistics & Qgis::ZonalStatistic::Range )
395 results.insert( Qgis::ZonalStatistic::Range, QVariant( featureStats.max - featureStats.min ) );
396 if ( statistics & Qgis::ZonalStatistic::Minority || statistics & Qgis::ZonalStatistic::Majority )
397 {
398 QList<int> vals = featureStats.valueCount.values();
399 std::sort( vals.begin(), vals.end() );
400 if ( statistics & Qgis::ZonalStatistic::Minority )
401 {
402 double minorityKey = featureStats.valueCount.key( vals.first() );
403 results.insert( Qgis::ZonalStatistic::Minority, QVariant( minorityKey ) );
404 }
405 if ( statistics & Qgis::ZonalStatistic::Majority )
406 {
407 double majKey = featureStats.valueCount.key( vals.last() );
408 results.insert( Qgis::ZonalStatistic::Majority, QVariant( majKey ) );
409 }
410 }
411 if ( statistics & Qgis::ZonalStatistic::Variety )
412 results.insert( Qgis::ZonalStatistic::Variety, QVariant( featureStats.valueCount.count() ) );
413 }
414
415 return results;
416}
ZonalStatistic
Statistics to be calculated during a zonal statistics operation.
Definition qgis.h:6053
@ StDev
Standard deviation of pixel values.
Definition qgis.h:6058
@ Mean
Mean of pixel values.
Definition qgis.h:6056
@ Median
Median of pixel values.
Definition qgis.h:6057
@ Max
Max of pixel values.
Definition qgis.h:6060
@ Variance
Variance of pixel values.
Definition qgis.h:6065
@ MinimumPoint
Pixel centroid for minimum pixel value.
Definition qgis.h:6066
@ Min
Min of pixel values.
Definition qgis.h:6059
@ Default
Default statistics.
Definition qgis.h:6070
@ Range
Range of pixel values (max - min).
Definition qgis.h:6061
@ Sum
Sum of pixel values.
Definition qgis.h:6055
@ Minority
Minority of pixel values.
Definition qgis.h:6062
@ All
All statistics. For QGIS 3.x this includes ONLY numeric statistics, but for 4.0 this will be extended...
Definition qgis.h:6068
@ Majority
Majority of pixel values.
Definition qgis.h:6063
@ Variety
Variety (count of distinct) pixel values.
Definition qgis.h:6064
@ MaximumPoint
Pixel centroid for maximum pixel value.
Definition qgis.h:6067
@ Count
Pixel count.
Definition qgis.h:6054
@ Polygon
Polygons.
Definition qgis.h:368
ZonalStatisticResult
Zonal statistics result codes.
Definition qgis.h:6088
@ Canceled
Algorithm was canceled.
Definition qgis.h:6095
@ LayerTypeWrong
Layer is not a polygon layer.
Definition qgis.h:6090
@ RasterBandInvalid
The raster band does not exist on the raster layer.
Definition qgis.h:6093
@ RasterInvalid
Raster layer is invalid.
Definition qgis.h:6092
@ LayerInvalid
Layer is invalid.
Definition qgis.h:6091
QFlags< ZonalStatistic > ZonalStatistics
Statistics to be calculated during a zonal statistics operation.
Definition qgis.h:6079
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:55
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:63
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