QGIS API Documentation 3.99.0-Master (26c88405ac0)
Loading...
Searching...
No Matches
qgsalgorithmrasterzonalstats.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsalgorithmrasterzonalstats.cpp
3 ---------------------
4 begin : December 2018
5 copyright : (C) 2018 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
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
19
20#include "qgsrasterprojector.h"
22#include "qgsstringutils.h"
23#include "qgsunittypes.h"
24
26
27QString QgsRasterLayerZonalStatsAlgorithm::name() const
28{
29 return QStringLiteral( "rasterlayerzonalstats" );
30}
31
32QString QgsRasterLayerZonalStatsAlgorithm::displayName() const
33{
34 return QObject::tr( "Raster layer zonal statistics" );
35}
36
37QStringList QgsRasterLayerZonalStatsAlgorithm::tags() const
38{
39 return QObject::tr( "count,area,statistics,stats,zones,categories,minimum,maximum,mean,sum,total" ).split( ',' );
40}
41
42QString QgsRasterLayerZonalStatsAlgorithm::group() const
43{
44 return QObject::tr( "Raster analysis" );
45}
46
47QString QgsRasterLayerZonalStatsAlgorithm::groupId() const
48{
49 return QStringLiteral( "rasteranalysis" );
50}
51
52void QgsRasterLayerZonalStatsAlgorithm::initAlgorithm( const QVariantMap & )
53{
54 addParameter( new QgsProcessingParameterRasterLayer( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) );
55 addParameter( new QgsProcessingParameterBand( QStringLiteral( "BAND" ), QObject::tr( "Band number" ), 1, QStringLiteral( "INPUT" ) ) );
56 addParameter( new QgsProcessingParameterRasterLayer( QStringLiteral( "ZONES" ), QObject::tr( "Zones layer" ) ) );
57 addParameter( new QgsProcessingParameterBand( QStringLiteral( "ZONES_BAND" ), QObject::tr( "Zones band number" ), 1, QStringLiteral( "ZONES" ) ) );
58
59 auto refParam = std::make_unique<QgsProcessingParameterEnum>( QStringLiteral( "REF_LAYER" ), QObject::tr( "Reference layer" ), QStringList() << QObject::tr( "Input layer" ) << QObject::tr( "Zones layer" ), false, 0 );
60 refParam->setFlags( refParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
61 addParameter( refParam.release() );
62
63 addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT_TABLE" ), QObject::tr( "Statistics" ), Qgis::ProcessingSourceType::Vector ) );
64
65 addOutput( new QgsProcessingOutputString( QStringLiteral( "EXTENT" ), QObject::tr( "Extent" ) ) );
66 addOutput( new QgsProcessingOutputString( QStringLiteral( "CRS_AUTHID" ), QObject::tr( "CRS authority identifier" ) ) );
67 addOutput( new QgsProcessingOutputNumber( QStringLiteral( "WIDTH_IN_PIXELS" ), QObject::tr( "Width in pixels" ) ) );
68 addOutput( new QgsProcessingOutputNumber( QStringLiteral( "HEIGHT_IN_PIXELS" ), QObject::tr( "Height in pixels" ) ) );
69 addOutput( new QgsProcessingOutputNumber( QStringLiteral( "TOTAL_PIXEL_COUNT" ), QObject::tr( "Total pixel count" ) ) );
70 addOutput( new QgsProcessingOutputNumber( QStringLiteral( "NODATA_PIXEL_COUNT" ), QObject::tr( "NoData pixel count" ) ) );
71}
72
73QString QgsRasterLayerZonalStatsAlgorithm::shortDescription() const
74{
75 return QObject::tr( "Calculates statistics for a raster layer's values, categorized by zones defined in another raster layer." );
76}
77
78QString QgsRasterLayerZonalStatsAlgorithm::shortHelpString() const
79{
80 return QObject::tr( "This algorithm calculates statistics for a raster layer's values, categorized by zones defined in another raster layer.\n\n"
81 "If the reference layer parameter is set to \"Input layer\", then zones are determined by sampling the zone raster layer value at the centroid of each pixel from the source raster layer.\n\n"
82 "If the reference layer parameter is set to \"Zones layer\", then the input raster layer will be sampled at the centroid of each pixel from the zones raster layer.\n\n"
83 "If either the source raster layer or the zone raster layer value is NoData for a pixel, that pixel's value will be skipped and not included in the calculated statistics." );
84}
85
86QgsRasterLayerZonalStatsAlgorithm *QgsRasterLayerZonalStatsAlgorithm::createInstance() const
87{
88 return new QgsRasterLayerZonalStatsAlgorithm();
89}
90
91bool QgsRasterLayerZonalStatsAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
92{
93 mRefLayer = static_cast<RefLayer>( parameterAsEnum( parameters, QStringLiteral( "REF_LAYER" ), context ) );
94
95 QgsRasterLayer *layer = parameterAsRasterLayer( parameters, QStringLiteral( "INPUT" ), context );
96 const int band = parameterAsInt( parameters, QStringLiteral( "BAND" ), context );
97
98 if ( !layer )
99 throw QgsProcessingException( invalidRasterError( parameters, QStringLiteral( "INPUT" ) ) );
100
101 mBand = parameterAsInt( parameters, QStringLiteral( "BAND" ), context );
102 if ( mBand < 1 || mBand > layer->bandCount() )
103 throw QgsProcessingException( QObject::tr( "Invalid band number for BAND (%1): Valid values for input raster are 1 to %2" ).arg( mBand ).arg( layer->bandCount() ) );
104
105 mHasNoDataValue = layer->dataProvider()->sourceHasNoDataValue( band );
106
107 QgsRasterLayer *zonesLayer = parameterAsRasterLayer( parameters, QStringLiteral( "ZONES" ), context );
108
109 if ( !zonesLayer )
110 throw QgsProcessingException( invalidRasterError( parameters, QStringLiteral( "ZONES" ) ) );
111
112 mZonesBand = parameterAsInt( parameters, QStringLiteral( "ZONES_BAND" ), context );
113 if ( mZonesBand < 1 || mZonesBand > zonesLayer->bandCount() )
114 throw QgsProcessingException( QObject::tr( "Invalid band number for ZONES_BAND (%1): Valid values for input raster are 1 to %2" ).arg( mZonesBand ).arg( zonesLayer->bandCount() ) );
115 mZonesHasNoDataValue = zonesLayer->dataProvider()->sourceHasNoDataValue( band );
116
117 mSourceDataProvider.reset( layer->dataProvider()->clone() );
118 mSourceInterface = mSourceDataProvider.get();
119 mZonesDataProvider.reset( zonesLayer->dataProvider()->clone() );
120 mZonesInterface = mZonesDataProvider.get();
121
122 switch ( mRefLayer )
123 {
124 case Source:
125 mCrs = layer->crs();
126 mRasterUnitsPerPixelX = layer->rasterUnitsPerPixelX();
127 mRasterUnitsPerPixelY = layer->rasterUnitsPerPixelY();
128 mLayerWidth = layer->width();
129 mLayerHeight = layer->height();
130 mExtent = layer->extent();
131
132 // add projector if necessary
133 if ( layer->crs() != zonesLayer->crs() )
134 {
135 mProjector = std::make_unique<QgsRasterProjector>();
136 mProjector->setInput( mZonesDataProvider.get() );
137 mProjector->setCrs( zonesLayer->crs(), layer->crs(), context.transformContext() );
138 mZonesInterface = mProjector.get();
139 }
140 break;
141
142 case Zones:
143 mCrs = zonesLayer->crs();
144 mRasterUnitsPerPixelX = zonesLayer->rasterUnitsPerPixelX();
145 mRasterUnitsPerPixelY = zonesLayer->rasterUnitsPerPixelY();
146 mLayerWidth = zonesLayer->width();
147 mLayerHeight = zonesLayer->height();
148 mExtent = zonesLayer->extent();
149
150 // add projector if necessary
151 if ( layer->crs() != zonesLayer->crs() )
152 {
153 mProjector = std::make_unique<QgsRasterProjector>();
154 mProjector->setInput( mSourceDataProvider.get() );
155 mProjector->setCrs( layer->crs(), zonesLayer->crs(), context.transformContext() );
156 mSourceInterface = mProjector.get();
157 }
158 break;
159 }
160
161 return true;
162}
163
164QVariantMap QgsRasterLayerZonalStatsAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
165{
166 QString areaUnit = QgsUnitTypes::toAbbreviatedString( QgsUnitTypes::distanceToAreaUnit( mCrs.mapUnits() ) );
167
168 QString tableDest;
169 std::unique_ptr<QgsFeatureSink> sink;
170 if ( parameters.contains( QStringLiteral( "OUTPUT_TABLE" ) ) && parameters.value( QStringLiteral( "OUTPUT_TABLE" ) ).isValid() )
171 {
172 QgsFields outFields;
173 outFields.append( QgsField( QStringLiteral( "zone" ), QMetaType::Type::Double, QString(), 20, 8 ) );
174 outFields.append( QgsField( areaUnit.isEmpty() ? "area" : areaUnit.replace( QStringLiteral( "²" ), QStringLiteral( "2" ) ), QMetaType::Type::Double, QString(), 20, 8 ) );
175 outFields.append( QgsField( QStringLiteral( "sum" ), QMetaType::Type::Double, QString(), 20, 8 ) );
176 outFields.append( QgsField( QStringLiteral( "count" ), QMetaType::Type::LongLong, QString(), 20 ) );
177 outFields.append( QgsField( QStringLiteral( "min" ), QMetaType::Type::Double, QString(), 20, 8 ) );
178 outFields.append( QgsField( QStringLiteral( "max" ), QMetaType::Type::Double, QString(), 20, 8 ) );
179 outFields.append( QgsField( QStringLiteral( "mean" ), QMetaType::Type::Double, QString(), 20, 8 ) );
180
181 sink.reset( parameterAsSink( parameters, QStringLiteral( "OUTPUT_TABLE" ), context, tableDest, outFields, Qgis::WkbType::NoGeometry, QgsCoordinateReferenceSystem() ) );
182 if ( !sink )
183 throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT_TABLE" ) ) );
184 }
185
186 struct StatCalculator
187 {
188 // only calculate cheap stats-- we cannot calculate stats which require holding values in memory -- because otherwise we'll end
189 // up trying to store EVERY pixel value from the input in memory
191 };
192 QHash<double, StatCalculator> zoneStats;
193 qgssize noDataCount = 0;
194
195 const qgssize layerSize = static_cast<qgssize>( mLayerWidth ) * static_cast<qgssize>( mLayerHeight );
198 const int nbBlocksWidth = static_cast<int>( std::ceil( 1.0 * mLayerWidth / maxWidth ) );
199 const int nbBlocksHeight = static_cast<int>( std::ceil( 1.0 * mLayerHeight / maxHeight ) );
200 const int nbBlocks = nbBlocksWidth * nbBlocksHeight;
201
202 QgsRasterIterator iter = mRefLayer == Source ? QgsRasterIterator( mSourceInterface )
203 : QgsRasterIterator( mZonesInterface );
204 iter.startRasterRead( mRefLayer == Source ? mBand : mZonesBand, mLayerWidth, mLayerHeight, mExtent );
205
206 int iterLeft = 0;
207 int iterTop = 0;
208 int iterCols = 0;
209 int iterRows = 0;
210 QgsRectangle blockExtent;
211 std::unique_ptr<QgsRasterBlock> rasterBlock;
212 std::unique_ptr<QgsRasterBlock> zonesRasterBlock;
213 bool isNoData = false;
214 while ( true )
215 {
216 if ( mRefLayer == Source )
217 {
218 if ( !iter.readNextRasterPart( mBand, iterCols, iterRows, rasterBlock, iterLeft, iterTop, &blockExtent ) )
219 break;
220
221 zonesRasterBlock.reset( mZonesInterface->block( mZonesBand, blockExtent, iterCols, iterRows ) );
222 }
223 else
224 {
225 if ( !iter.readNextRasterPart( mZonesBand, iterCols, iterRows, zonesRasterBlock, iterLeft, iterTop, &blockExtent ) )
226 break;
227
228 rasterBlock.reset( mSourceInterface->block( mBand, blockExtent, iterCols, iterRows ) );
229 }
230 if ( !zonesRasterBlock || !rasterBlock )
231 continue;
232
233 feedback->setProgress( 100 * ( ( iterTop / maxHeight * nbBlocksWidth ) + iterLeft / maxWidth ) / nbBlocks );
234 if ( !rasterBlock->isValid() || rasterBlock->isEmpty() || !zonesRasterBlock->isValid() || zonesRasterBlock->isEmpty() )
235 continue;
236
237 for ( int row = 0; row < iterRows; row++ )
238 {
239 if ( feedback->isCanceled() )
240 break;
241
242 for ( int column = 0; column < iterCols; column++ )
243 {
244 const double value = rasterBlock->valueAndNoData( row, column, isNoData );
245 if ( mHasNoDataValue && isNoData )
246 {
247 noDataCount += 1;
248 continue;
249 }
250 const double zone = zonesRasterBlock->valueAndNoData( row, column, isNoData );
251 if ( mZonesHasNoDataValue && isNoData )
252 {
253 noDataCount += 1;
254 continue;
255 }
256 zoneStats[zone].s.addValue( value );
257 }
258 }
259 }
260
261 QVariantMap outputs;
262 outputs.insert( QStringLiteral( "EXTENT" ), mExtent.toString() );
263 outputs.insert( QStringLiteral( "CRS_AUTHID" ), mCrs.authid() );
264 outputs.insert( QStringLiteral( "WIDTH_IN_PIXELS" ), mLayerWidth );
265 outputs.insert( QStringLiteral( "HEIGHT_IN_PIXELS" ), mLayerHeight );
266 outputs.insert( QStringLiteral( "TOTAL_PIXEL_COUNT" ), layerSize );
267 outputs.insert( QStringLiteral( "NODATA_PIXEL_COUNT" ), noDataCount );
268
269 const double pixelArea = mRasterUnitsPerPixelX * mRasterUnitsPerPixelY;
270
271 for ( auto it = zoneStats.begin(); it != zoneStats.end(); ++it )
272 {
273 QgsFeature f;
274 it->s.finalize();
275 f.setAttributes( QgsAttributes() << it.key() << it->s.count() * pixelArea << it->s.sum() << it->s.count() << it->s.min() << it->s.max() << it->s.mean() );
276 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
277 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT_TABLE" ) ) );
278 sink->finalize();
279 }
280 outputs.insert( QStringLiteral( "OUTPUT_TABLE" ), tableDest );
281
282 return outputs;
283}
284
285
@ Vector
Tables (i.e. vector layers with or without geometry). When used for a sink this indicates the sink ha...
Definition qgis.h:3539
@ Mean
Mean of values.
Definition qgis.h:5852
@ Max
Max of values.
Definition qgis.h:5857
@ Min
Min of values.
Definition qgis.h:5856
@ Sum
Sum of values.
Definition qgis.h:5851
@ Count
Count.
Definition qgis.h:5849
@ NoGeometry
No geometry.
Definition qgis.h:294
@ Advanced
Parameter is an advanced parameter which should be hidden from users by default.
Definition qgis.h:3763
Represents a coordinate reference system (CRS).
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:53
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:61
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:54
Container of fields for a vector layer.
Definition qgsfields.h:46
bool append(const QgsField &field, Qgis::FieldOrigin origin=Qgis::FieldOrigin::Provider, int originIndex=-1)
Appends a field.
Definition qgsfields.cpp:73
virtual QgsRectangle extent() const
Returns the extent of the layer.
QgsCoordinateReferenceSystem crs
Definition qgsmaplayer.h:87
Contains information about the context in which a processing algorithm is executed.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context.
Custom exception class for processing related exceptions.
Base class for providing feedback from a processing algorithm.
A numeric output for processing algorithms.
A string output for processing algorithms.
A raster band parameter for Processing algorithms.
A feature sink output for processing algorithms.
A raster layer parameter for processing algorithms.
QgsRasterDataProvider * clone() const override=0
Clone itself, create deep copy.
virtual bool sourceHasNoDataValue(int bandNo) const
Returns true if source band has no data value.
Iterator for sequentially processing raster cells.
static const int DEFAULT_MAXIMUM_TILE_WIDTH
Default maximum tile width.
bool readNextRasterPart(int bandNumber, int &nCols, int &nRows, QgsRasterBlock **block, int &topLeftCol, int &topLeftRow)
Fetches next part of raster data, caller takes ownership of the block and caller should delete the bl...
static const int DEFAULT_MAXIMUM_TILE_HEIGHT
Default maximum tile height.
void startRasterRead(int bandNumber, qgssize nCols, qgssize nRows, const QgsRectangle &extent, QgsRasterBlockFeedback *feedback=nullptr)
Start reading of raster band.
Represents a raster layer.
int height() const
Returns the height of the (unclipped) raster.
int bandCount() const
Returns the number of bands in this layer.
double rasterUnitsPerPixelX() const
Returns the number of raster units per each raster pixel in X axis.
QgsRasterDataProvider * dataProvider() override
Returns the source data provider.
double rasterUnitsPerPixelY() const
Returns the number of raster units per each raster pixel in Y axis.
int width() const
Returns the width of the (unclipped) raster.
A rectangle specified with double values.
static Q_INVOKABLE QString toAbbreviatedString(Qgis::DistanceUnit unit)
Returns a translated abbreviation representing a distance unit.
static Q_INVOKABLE Qgis::AreaUnit distanceToAreaUnit(Qgis::DistanceUnit distanceUnit)
Converts a distance unit to its corresponding area unit, e.g., meters to square meters.
unsigned long long qgssize
Qgssize is used instead of size_t, because size_t is stdlib type, unknown by SIP, and it would be har...
Definition qgis.h:7142