QGIS API Documentation 4.3.0-Master (16eddb2670e)
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 "qgsmessagelog.h"
25#include "qgsproject.h"
26#include "qgsrasterlayer.h"
28#include "qgsvectorlayer.h"
29
30#include <QFile>
31#include <QString>
32
33using namespace Qt::StringLiterals;
34
35QgsZonalStatistics::QgsZonalStatistics( QgsVectorLayer *polygonLayer, QgsRasterLayer *rasterLayer, const QString &attributePrefix, int rasterBand, Qgis::ZonalStatistics stats, bool overwriteExistingFields )
37 polygonLayer,
38 rasterLayer ? rasterLayer->dataProvider() : nullptr,
39 rasterLayer ? rasterLayer->crs() : QgsCoordinateReferenceSystem(),
40 rasterLayer ? rasterLayer->rasterUnitsPerPixelX() : 0,
41 rasterLayer ? rasterLayer->rasterUnitsPerPixelY() : 0,
42 attributePrefix,
43 rasterBand,
44 stats,
45 overwriteExistingFields
46 )
47{}
48
50 QgsVectorLayer *polygonLayer,
51 QgsRasterInterface *rasterInterface,
52 const QgsCoordinateReferenceSystem &rasterCrs,
53 double rasterUnitsPerPixelX,
54 double rasterUnitsPerPixelY,
55 const QString &attributePrefix,
56 int rasterBand,
58 bool overwriteExistingFields
59)
60 : mRasterInterface( rasterInterface )
61 , mRasterCrs( rasterCrs )
62 , mCellSizeX( std::fabs( rasterUnitsPerPixelX ) )
63 , mCellSizeY( std::fabs( rasterUnitsPerPixelY ) )
64 , mRasterBand( rasterBand )
65 , mPolygonLayer( polygonLayer )
66 , mAttributePrefix( attributePrefix )
67 , mStatistics( stats )
68 , mOverwriteExistingFields( overwriteExistingFields )
69{}
70
72{
73 if ( !mRasterInterface )
74 {
76 }
77
78 if ( mRasterInterface->bandCount() < mRasterBand )
79 {
81 }
82
83 if ( !mPolygonLayer || mPolygonLayer->geometryType() != Qgis::GeometryType::Polygon )
84 {
86 }
87
88 QgsVectorDataProvider *vectorProvider = mPolygonLayer->dataProvider();
89 if ( !vectorProvider )
90 {
92 }
93
94 QMap<Qgis::ZonalStatistic, int> statFieldIndexes;
95
96 //add the new fields to the provider
97 int oldFieldCount = vectorProvider->fields().count();
98 QList<QgsField> newFieldList;
99 for ( Qgis::ZonalStatistic stat :
112 {
113 if ( mStatistics & stat )
114 {
115 QString targetFieldName = mAttributePrefix + QgsZonalStatistics::shortName( stat );
116 int targetIndex = vectorProvider->fields().indexFromName( targetFieldName );
117
118 if ( mOverwriteExistingFields && targetIndex != -1 )
119 {
120 statFieldIndexes.insert( stat, targetIndex );
121 }
122 else
123 {
124 if ( mOverwriteExistingFields && targetIndex == -1 )
125 {
126 QgsMessageLog::logMessage( QObject::tr( "Could not find field '%1'. A new field will be created instead." ).arg( targetFieldName ), QObject::tr( "Zonal Statistics" ), Qgis::MessageLevel::Warning );
127 }
128
129 QString fieldName = getUniqueFieldName( targetFieldName, newFieldList );
130 QgsField field( fieldName, QMetaType::Type::Double, u"double precision"_s );
131 newFieldList.push_back( field );
132 statFieldIndexes.insert( stat, oldFieldCount + newFieldList.count() - 1 );
133 }
134 }
135 }
136
137 vectorProvider->addAttributes( newFieldList );
138
139 long featureCount = vectorProvider->featureCount();
140
141 QgsFeatureRequest request;
142 request.setNoAttributes();
143
144 request.setDestinationCrs( mRasterCrs, QgsProject::instance()->transformContext() );
145 QgsFeatureIterator fi = vectorProvider->getFeatures( request );
146 QgsFeature feature;
147
148 int featureCounter = 0;
149
150 QgsChangedAttributesMap changeMap;
151 while ( fi.nextFeature( feature ) )
152 {
153 ++featureCounter;
154 if ( feedback && feedback->isCanceled() )
155 {
156 break;
157 }
158
159 if ( feedback )
160 {
161 feedback->setProgress( 100.0 * static_cast<double>( featureCounter ) / featureCount );
162 }
163
164 QgsGeometry featureGeometry = feature.geometry();
165
166 QMap<Qgis::ZonalStatistic, QVariant> results = calculateStatistics( mRasterInterface, featureGeometry, mCellSizeX, mCellSizeY, mRasterBand, mStatistics );
167
168 if ( results.empty() )
169 continue;
170
171 QgsAttributeMap changeAttributeMap;
172 for ( const auto &result : results.toStdMap() )
173 {
174 changeAttributeMap.insert( statFieldIndexes.value( result.first ), result.second );
175 }
176
177 changeMap.insert( feature.id(), changeAttributeMap );
178 }
179
180 vectorProvider->changeAttributeValues( changeMap );
181 mPolygonLayer->updateFields();
182
183 if ( feedback )
184 {
185 if ( feedback->isCanceled() )
187
188 feedback->setProgress( 100 );
189 }
190
192}
193
194QString QgsZonalStatistics::getUniqueFieldName( const QString &fieldName, const QList<QgsField> &newFields )
195{
196 QgsVectorDataProvider *provider = mPolygonLayer->dataProvider();
197 const bool isShapefile = provider->storageType().contains( "ESRI Shapefile"_L1 );
198
199 QList<QgsField> allFields = provider->fields().toList();
200 allFields.append( newFields );
201
202 QString baseName = isShapefile ? fieldName.mid( 0, 10 ) : fieldName;
203
204 auto fieldExists = [&allFields]( const QString &name ) -> bool {
205 for ( const QgsField &field : std::as_const( allFields ) )
206 {
207 if ( field.name().compare( name, Qt::CaseSensitivity::CaseSensitive ) == 0 )
208 {
209 return true;
210 }
211 }
212 return false;
213 };
214
215 if ( !fieldExists( baseName ) )
216 {
217 return baseName;
218 }
219
220 int n = 1;
221 QString newName;
222 while ( true )
223 {
224 if ( isShapefile )
225 {
226 newName = u"%1_%2"_s.arg( fieldName.mid( 0, n < 9 ? 8 : 7 ) ).arg( n );
227 }
228 else
229 {
230 newName = u"%1_%2"_s.arg( fieldName ).arg( n );
231 }
232
233 if ( !fieldExists( newName ) )
234 return newName;
235
236 n++;
237 }
238}
239
241{
242 switch ( statistic )
243 {
245 return QObject::tr( "Count" );
247 return QObject::tr( "Sum" );
249 return QObject::tr( "Mean" );
251 return QObject::tr( "Median" );
253 return QObject::tr( "St dev" );
255 return QObject::tr( "Minimum" );
257 return QObject::tr( "Maximum" );
259 return QObject::tr( "Minimum point" );
261 return QObject::tr( "Maximum point" );
263 return QObject::tr( "Range" );
265 return QObject::tr( "Minority" );
267 return QObject::tr( "Majority" );
269 return QObject::tr( "Variety" );
271 return QObject::tr( "Variance" );
274 return QString();
275 }
276 return QString();
277}
278
280{
281 switch ( statistic )
282 {
284 return u"count"_s;
286 return u"sum"_s;
288 return u"mean"_s;
290 return u"median"_s;
292 return u"stdev"_s;
294 return u"min"_s;
296 return u"max"_s;
298 return u"minpoint"_s;
300 return u"maxpoint"_s;
302 return u"range"_s;
304 return u"minority"_s;
306 return u"majority"_s;
308 return u"variety"_s;
310 return u"variance"_s;
313 return QString();
314 }
315 return QString();
316}
317
319QMap<int, QVariant> QgsZonalStatistics::calculateStatisticsInt(
320 QgsRasterInterface *rasterInterface, const QgsGeometry &geometry, double cellSizeX, double cellSizeY, int rasterBand, Qgis::ZonalStatistics statistics
321)
322{
323 const auto result { QgsZonalStatistics::calculateStatistics( rasterInterface, geometry, cellSizeX, cellSizeY, rasterBand, statistics ) };
324 QMap<int, QVariant> pyResult;
325 for ( auto it = result.constBegin(); it != result.constEnd(); ++it )
326 {
327 pyResult.insert( static_cast<int>( it.key() ), it.value() );
328 }
329 return pyResult;
330}
332
333QMap<Qgis::ZonalStatistic, QVariant> QgsZonalStatistics::calculateStatistics(
334 QgsRasterInterface *rasterInterface, const QgsGeometry &geometry, double cellSizeX, double cellSizeY, int rasterBand, Qgis::ZonalStatistics statistics
335)
336{
337 QMap<Qgis::ZonalStatistic, QVariant> results;
338
339 if ( geometry.isEmpty() )
340 return results;
341
342 QgsRectangle rasterBBox = rasterInterface->extent();
343
344 QgsRectangle featureRect = geometry.boundingBox().intersect( rasterBBox );
345
346 if ( featureRect.isEmpty() )
347 return results;
348
349 bool statsStoreValues = ( statistics & Qgis::ZonalStatistic::Median ) || ( statistics & Qgis::ZonalStatistic::StDev ) || ( statistics & Qgis::ZonalStatistic::Variance );
350 bool statsStoreValueCount = ( statistics & Qgis::ZonalStatistic::Minority ) || ( statistics & Qgis::ZonalStatistic::Majority );
351
352 FeatureStats featureStats( statsStoreValues, statsStoreValueCount );
353
354 int nCellsXProvider = rasterInterface->xSize();
355 int nCellsYProvider = rasterInterface->ySize();
356
357 int nCellsX, nCellsY;
358 QgsRectangle rasterBlockExtent;
359 QgsRasterAnalysisUtils::cellInfoForBBox( rasterBBox, featureRect, cellSizeX, cellSizeY, nCellsX, nCellsY, nCellsXProvider, nCellsYProvider, rasterBlockExtent );
360
361 featureStats.reset();
362 QgsRasterAnalysisUtils::statisticsFromMiddlePointTest( rasterInterface, rasterBand, geometry, nCellsX, nCellsY, cellSizeX, cellSizeY, rasterBlockExtent, [&featureStats]( double value, const QgsPointXY &point ) {
363 featureStats.addValue( value, point );
364 } );
365
366 if ( featureStats.count <= 1 )
367 {
368 //the cell resolution is probably larger than the polygon area. We switch to precise pixel - polygon intersection in this case
369 featureStats.reset();
370 QgsRasterAnalysisUtils::
371 statisticsFromPreciseIntersection( rasterInterface, rasterBand, geometry, nCellsX, nCellsY, cellSizeX, cellSizeY, rasterBlockExtent, [&featureStats]( double value, double weight, const QgsPointXY &point ) {
372 featureStats.addValue( value, point, weight );
373 } );
374 }
375
376 // calculate the statistics
377
378 if ( statistics & Qgis::ZonalStatistic::Count )
379 results.insert( Qgis::ZonalStatistic::Count, QVariant( featureStats.count ) );
380 if ( statistics & Qgis::ZonalStatistic::Sum )
381 results.insert( Qgis::ZonalStatistic::Sum, QVariant( featureStats.sum ) );
382 if ( featureStats.count > 0 )
383 {
384 double mean = featureStats.sum / featureStats.count;
385 if ( statistics & Qgis::ZonalStatistic::Mean )
386 results.insert( Qgis::ZonalStatistic::Mean, QVariant( mean ) );
387 if ( statistics & Qgis::ZonalStatistic::Median )
388 {
389 std::sort( featureStats.values.begin(), featureStats.values.end() );
390 int size = featureStats.values.count();
391 bool even = ( size % 2 ) < 1;
392 double medianValue;
393 if ( even )
394 {
395 medianValue = ( featureStats.values.at( size / 2 - 1 ) + featureStats.values.at( size / 2 ) ) / 2;
396 }
397 else //odd
398 {
399 medianValue = featureStats.values.at( ( size + 1 ) / 2 - 1 );
400 }
401 results.insert( Qgis::ZonalStatistic::Median, QVariant( medianValue ) );
402 }
403 if ( statistics & Qgis::ZonalStatistic::StDev || statistics & Qgis::ZonalStatistic::Variance )
404 {
405 double sumSquared = 0;
406 for ( int i = 0; i < featureStats.values.count(); ++i )
407 {
408 double diff = featureStats.values.at( i ) - mean;
409 sumSquared += diff * diff;
410 }
411 double variance = sumSquared / featureStats.values.count();
412 if ( statistics & Qgis::ZonalStatistic::StDev )
413 {
414 double stdev = std::pow( variance, 0.5 );
415 results.insert( Qgis::ZonalStatistic::StDev, QVariant( stdev ) );
416 }
417 if ( statistics & Qgis::ZonalStatistic::Variance )
418 results.insert( Qgis::ZonalStatistic::Variance, QVariant( variance ) );
419 }
420 if ( statistics & Qgis::ZonalStatistic::Min )
421 results.insert( Qgis::ZonalStatistic::Min, QVariant( featureStats.min ) );
422 if ( statistics & Qgis::ZonalStatistic::Max )
423 results.insert( Qgis::ZonalStatistic::Max, QVariant( featureStats.max ) );
424 if ( statistics & Qgis::ZonalStatistic::MinimumPoint )
425 results.insert( Qgis::ZonalStatistic::MinimumPoint, QVariant( featureStats.minPoint ) );
426 if ( statistics & Qgis::ZonalStatistic::MaximumPoint )
427 results.insert( Qgis::ZonalStatistic::MaximumPoint, QVariant( featureStats.maxPoint ) );
428 if ( statistics & Qgis::ZonalStatistic::Range )
429 results.insert( Qgis::ZonalStatistic::Range, QVariant( featureStats.max - featureStats.min ) );
430 if ( statistics & Qgis::ZonalStatistic::Minority || statistics & Qgis::ZonalStatistic::Majority )
431 {
432 int64_t lowestCount = std::numeric_limits<int64_t>::max();
433 int64_t highestCount = 0;
434 double valueWithLowestCount = 0;
435 double valueWithHighestCount = 0;
436 for ( auto [value, count] : std::as_const( featureStats.valueCount ) )
437 {
438 if ( count < lowestCount )
439 {
440 lowestCount = count;
441 valueWithLowestCount = value;
442 }
443 if ( count > highestCount )
444 {
445 highestCount = count;
446 valueWithHighestCount = value;
447 }
448 }
449 if ( statistics & Qgis::ZonalStatistic::Minority )
450 {
451 results.insert( Qgis::ZonalStatistic::Minority, QVariant( valueWithLowestCount ) );
452 }
453 if ( statistics & Qgis::ZonalStatistic::Majority )
454 {
455 results.insert( Qgis::ZonalStatistic::Majority, QVariant( valueWithHighestCount ) );
456 }
457 }
458 if ( statistics & Qgis::ZonalStatistic::Variety )
459 results.insert( Qgis::ZonalStatistic::Variety, QVariant( static_cast<qlonglong>( featureStats.valueCount.size() ) ) );
460 }
461
462 return results;
463}
ZonalStatistic
Statistics to be calculated during a zonal statistics operation.
Definition qgis.h:6583
@ StDev
Standard deviation of pixel values.
Definition qgis.h:6588
@ Mean
Mean of pixel values.
Definition qgis.h:6586
@ Median
Median of pixel values.
Definition qgis.h:6587
@ Max
Max of pixel values.
Definition qgis.h:6590
@ Variance
Variance of pixel values.
Definition qgis.h:6595
@ MinimumPoint
Pixel centroid for minimum pixel value.
Definition qgis.h:6596
@ Min
Min of pixel values.
Definition qgis.h:6589
@ Default
Default statistics.
Definition qgis.h:6602
@ Range
Range of pixel values (max - min).
Definition qgis.h:6591
@ Sum
Sum of pixel values.
Definition qgis.h:6585
@ Minority
Minority of pixel values.
Definition qgis.h:6592
@ All
All statistics. For QGIS 3.x this includes ONLY numeric statistics, but for 4.0 this will be extended...
Definition qgis.h:6599
@ Majority
Majority of pixel values.
Definition qgis.h:6593
@ Variety
Variety (count of distinct) pixel values.
Definition qgis.h:6594
@ MaximumPoint
Pixel centroid for maximum pixel value.
Definition qgis.h:6597
@ Count
Pixel count.
Definition qgis.h:6584
@ Warning
Warning message.
Definition qgis.h:162
@ Polygon
Polygons.
Definition qgis.h:402
ZonalStatisticResult
Zonal statistics result codes.
Definition qgis.h:6620
@ Canceled
Algorithm was canceled.
Definition qgis.h:6627
@ LayerTypeWrong
Layer is not a polygon layer.
Definition qgis.h:6622
@ RasterBandInvalid
The raster band does not exist on the raster layer.
Definition qgis.h:6625
@ RasterInvalid
Raster layer is invalid.
Definition qgis.h:6624
@ LayerInvalid
Layer is invalid.
Definition qgis.h:6623
QFlags< ZonalStatistic > ZonalStatistics
Statistics to be calculated during a zonal statistics operation.
Definition qgis.h:6611
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:63
QgsGeometry geometry
Definition qgsfeature.h:66
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:49
QList< QgsField > toList() const
Utility function to return a list of QgsField instances.
Q_INVOKABLE int indexFromName(const QString &fieldName) const
Gets the field index from the field name.
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.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE(), Qgis::StringFormat format=Qgis::StringFormat::PlainText)
Adds a message to the log instance (and creates it if necessary).
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.
Qgis::ZonalStatisticResult calculateStatistics(QgsFeedback *feedback)
Runs the calculation.
static QString displayName(Qgis::ZonalStatistic statistic)
Returns the friendly display name for a statistic.
QgsZonalStatistics(QgsVectorLayer *polygonLayer, QgsRasterLayer *rasterLayer, const QString &attributePrefix=QString(), int rasterBand=1, Qgis::ZonalStatistics stats=Qgis::ZonalStatistic::Default, bool overwriteExistingFields=false)
Convenience constructor for QgsZonalStatistics, using an input raster layer.
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