QGIS API Documentation  3.26.3-Buenos Aires (65e4edfdad)
qgsalgorithmrastersurfacevolume.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsalgorithmrasterlayeruniquevalues.cpp
3  ---------------------
4  begin : January 2019
5  copyright : (C) 2019 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 #include "qgsstringutils.h"
20 #include <QTextStream>
21 
23 
24 QString QgsRasterSurfaceVolumeAlgorithm::name() const
25 {
26  return QStringLiteral( "rastersurfacevolume" );
27 }
28 
29 QString QgsRasterSurfaceVolumeAlgorithm::displayName() const
30 {
31  return QObject::tr( "Raster surface volume" );
32 }
33 
34 QStringList QgsRasterSurfaceVolumeAlgorithm::tags() const
35 {
36  return QObject::tr( "sum,volume,area,height,terrain,dem,elevation" ).split( ',' );
37 }
38 
39 QString QgsRasterSurfaceVolumeAlgorithm::group() const
40 {
41  return QObject::tr( "Raster analysis" );
42 }
43 
44 QString QgsRasterSurfaceVolumeAlgorithm::groupId() const
45 {
46  return QStringLiteral( "rasteranalysis" );
47 }
48 
49 void QgsRasterSurfaceVolumeAlgorithm::initAlgorithm( const QVariantMap & )
50 {
51  addParameter( new QgsProcessingParameterRasterLayer( QStringLiteral( "INPUT" ),
52  QObject::tr( "Input layer" ) ) );
53  addParameter( new QgsProcessingParameterBand( QStringLiteral( "BAND" ),
54  QObject::tr( "Band number" ), 1, QStringLiteral( "INPUT" ) ) );
55  addParameter( new QgsProcessingParameterNumber( QStringLiteral( "LEVEL" ),
56  QObject::tr( "Base level" ), QgsProcessingParameterNumber::Double, 0 ) );
57  addParameter( new QgsProcessingParameterEnum( QStringLiteral( "METHOD" ),
58  QObject::tr( "Method" ), QStringList()
59  << QObject::tr( "Count Only Above Base Level" )
60  << QObject::tr( "Count Only Below Base Level" )
61  << QObject::tr( "Subtract Volumes Below Base Level" )
62  << QObject::tr( "Add Volumes Below Base Level" ) ) );
63 
64  addParameter( new QgsProcessingParameterFileDestination( QStringLiteral( "OUTPUT_HTML_FILE" ),
65  QObject::tr( "Surface volume report" ), QObject::tr( "HTML files (*.html)" ), QVariant(), true ) );
66  addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT_TABLE" ),
67  QObject::tr( "Surface volume table" ), QgsProcessing::TypeVector, QVariant(), true, false ) );
68 
69  addOutput( new QgsProcessingOutputNumber( QStringLiteral( "VOLUME" ), QObject::tr( "Volume" ) ) );
70  addOutput( new QgsProcessingOutputNumber( QStringLiteral( "PIXEL_COUNT" ), QObject::tr( "Pixel count" ) ) );
71  addOutput( new QgsProcessingOutputNumber( QStringLiteral( "AREA" ), QObject::tr( "Area" ) ) );
72 }
73 
74 QString QgsRasterSurfaceVolumeAlgorithm::shortHelpString() const
75 {
76  return QObject::tr( "This algorithm calculates the volume under a raster grid's surface.\n\n"
77  "Several methods of volume calculation are available, which control whether "
78  "only values above or below the specified base level are considered, or "
79  "whether volumes below the base level should be added or subtracted from the total volume.\n\n"
80  "The algorithm outputs the calculated volume, the total area, and the total number of pixels analysed. "
81  "If the 'Count Only Above Base Level' or 'Count Only Below Base Level' methods are used, "
82  "then the calculated area and pixel count only includes pixels which are above or below the "
83  "specified base level respectively.\n\n"
84  "Units of the calculated volume are dependent on the coordinate reference system of "
85  "the input raster file. For a CRS in meters, with a DEM height in meters, the calculated "
86  "value will be in meters³. If instead the input raster is in a geographic coordinate system "
87  "(e.g. latitude/longitude values), then the result will be in degrees² × meters, and an "
88  "appropriate scaling factor will need to be applied in order to convert to meters³." );
89 }
90 
91 QString QgsRasterSurfaceVolumeAlgorithm::shortDescription() const
92 {
93  return QObject::tr( "Calculates the volume under a raster grid's surface." );
94 }
95 
96 QgsRasterSurfaceVolumeAlgorithm *QgsRasterSurfaceVolumeAlgorithm::createInstance() const
97 {
98  return new QgsRasterSurfaceVolumeAlgorithm();
99 }
100 
101 bool QgsRasterSurfaceVolumeAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
102 {
103  QgsRasterLayer *layer = parameterAsRasterLayer( parameters, QStringLiteral( "INPUT" ), context );
104  const int band = parameterAsInt( parameters, QStringLiteral( "BAND" ), context );
105 
106  if ( !layer )
107  throw QgsProcessingException( invalidRasterError( parameters, QStringLiteral( "INPUT" ) ) );
108 
109  mBand = parameterAsInt( parameters, QStringLiteral( "BAND" ), context );
110  if ( mBand < 1 || mBand > layer->bandCount() )
111  throw QgsProcessingException( QObject::tr( "Invalid band number for BAND (%1): Valid values for input raster are 1 to %2" ).arg( mBand )
112  .arg( layer->bandCount() ) );
113 
114  mInterface.reset( layer->dataProvider()->clone() );
115  mHasNoDataValue = layer->dataProvider()->sourceHasNoDataValue( band );
116  mLayerWidth = layer->width();
117  mLayerHeight = layer->height();
118  mExtent = layer->extent();
119  mCrs = layer->crs();
120  mRasterUnitsPerPixelX = layer->rasterUnitsPerPixelX();
121  mRasterUnitsPerPixelY = layer->rasterUnitsPerPixelY();
122  mSource = layer->source();
123 
124  mLevel = parameterAsDouble( parameters, QStringLiteral( "LEVEL" ), context );
125  mMethod = static_cast< Method >( parameterAsEnum( parameters, QStringLiteral( "METHOD" ), context ) );
126  return true;
127 }
128 
129 QVariantMap QgsRasterSurfaceVolumeAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
130 {
131  const QString outputFile = parameterAsFileOutput( parameters, QStringLiteral( "OUTPUT_HTML_FILE" ), context );
132  QString areaUnit = QgsUnitTypes::toAbbreviatedString( QgsUnitTypes::distanceToAreaUnit( mCrs.mapUnits() ) );
133 
134  QString tableDest;
135  std::unique_ptr< QgsFeatureSink > sink;
136  if ( parameters.contains( QStringLiteral( "OUTPUT_TABLE" ) ) && parameters.value( QStringLiteral( "OUTPUT_TABLE" ) ).isValid() )
137  {
138  QgsFields outFields;
139  outFields.append( QgsField( QStringLiteral( "volume" ), QVariant::Double, QString(), 20, 8 ) );
140  outFields.append( QgsField( areaUnit.replace( QStringLiteral( "²" ), QStringLiteral( "2" ) ), QVariant::Double, QString(), 20, 8 ) );
141  outFields.append( QgsField( QStringLiteral( "pixel_count" ), QVariant::LongLong ) );
142  sink.reset( parameterAsSink( parameters, QStringLiteral( "OUTPUT_TABLE" ), context, tableDest, outFields, QgsWkbTypes::NoGeometry, QgsCoordinateReferenceSystem() ) );
143  if ( !sink )
144  throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT_TABLE" ) ) );
145  }
146 
147  double volume = 0;
148  long long count = 0;
149 
152  const int nbBlocksWidth = static_cast< int >( std::ceil( 1.0 * mLayerWidth / maxWidth ) );
153  const int nbBlocksHeight = static_cast< int >( std::ceil( 1.0 * mLayerHeight / maxHeight ) );
154  const int nbBlocks = nbBlocksWidth * nbBlocksHeight;
155 
156  QgsRasterIterator iter( mInterface.get() );
157  iter.startRasterRead( mBand, mLayerWidth, mLayerHeight, mExtent );
158 
159  int iterLeft = 0;
160  int iterTop = 0;
161  int iterCols = 0;
162  int iterRows = 0;
163  std::unique_ptr< QgsRasterBlock > rasterBlock;
164  while ( iter.readNextRasterPart( mBand, iterCols, iterRows, rasterBlock, iterLeft, iterTop ) )
165  {
166  feedback->setProgress( 100 * ( ( iterTop / maxHeight * nbBlocksWidth ) + iterLeft / maxWidth ) / nbBlocks );
167  for ( int row = 0; row < iterRows; row++ )
168  {
169  if ( feedback->isCanceled() )
170  break;
171  for ( int column = 0; column < iterCols; column++ )
172  {
173  if ( mHasNoDataValue && rasterBlock->isNoData( row, column ) )
174  {
175  continue;
176  }
177 
178  const double z = rasterBlock->value( row, column ) - mLevel;
179 
180  switch ( mMethod )
181  {
182  case CountOnlyAboveBaseLevel:
183  if ( z > 0.0 )
184  {
185  volume += z;
186  count++;
187  }
188  continue;
189 
190  case CountOnlyBelowBaseLevel:
191  if ( z < 0.0 )
192  {
193  volume += z;
194  count++;
195  }
196  continue;
197 
198  case SubtractVolumesBelowBaseLevel:
199  volume += z;
200  count++;
201  continue;
202 
203  case AddVolumesBelowBaseLevel:
204  volume += std::fabs( z );
205  count++;
206  continue;
207  }
208  }
209  }
210  }
211 
212  QVariantMap outputs;
213  const double pixelArea = mRasterUnitsPerPixelX * mRasterUnitsPerPixelY;
214  const double area = count * pixelArea;
215  volume *= pixelArea;
216  if ( !outputFile.isEmpty() )
217  {
218  QFile file( outputFile );
219  if ( file.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
220  {
221  const QString encodedAreaUnit = QgsStringUtils::ampersandEncode( areaUnit );
222 
223  QTextStream out( &file );
224 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
225  out.setCodec( "UTF-8" );
226 #endif
227  out << QStringLiteral( "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"/></head><body>\n" );
228  out << QStringLiteral( "<p>%1: %2 (%3 %4)</p>\n" ).arg( QObject::tr( "Analyzed file" ), mSource, QObject::tr( "band" ) ).arg( mBand );
229  out << QObject::tr( "<p>%1: %2</p>\n" ).arg( QObject::tr( "Volume" ), QString::number( volume, 'g', 16 ) );
230  out << QObject::tr( "<p>%1: %2</p>\n" ).arg( QObject::tr( "Pixel count" ) ).arg( count );
231  out << QObject::tr( "<p>%1: %2 %3</p>\n" ).arg( QObject::tr( "Area" ), QString::number( area, 'g', 16 ), encodedAreaUnit );
232  out << QStringLiteral( "</body></html>" );
233  outputs.insert( QStringLiteral( "OUTPUT_HTML_FILE" ), outputFile );
234  }
235  }
236 
237  if ( sink )
238  {
239  QgsFeature f;
240  f.setAttributes( QgsAttributes() << volume << area << count );
241  if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
242  throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT_TABLE" ) ) );
243  outputs.insert( QStringLiteral( "OUTPUT_TABLE" ), tableDest );
244  }
245  outputs.insert( QStringLiteral( "VOLUME" ), volume );
246  outputs.insert( QStringLiteral( "AREA" ), area );
247  outputs.insert( QStringLiteral( "PIXEL_COUNT" ), count );
248  return outputs;
249 }
250 
251 
253 
254 
255 
QgsMapLayer::crs
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:79
QgsFeedback::setProgress
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition: qgsfeedback.h:76
QgsRasterLayer::bandCount
int bandCount() const
Returns the number of bands in this layer.
Definition: qgsrasterlayer.cpp:240
QgsProcessingParameterNumber::Double
@ Double
Double/float values.
Definition: qgsprocessingparameters.h:2187
QgsProcessingParameterNumber
A numeric parameter for processing algorithms.
Definition: qgsprocessingparameters.h:2179
QgsProcessingFeedback
Base class for providing feedback from a processing algorithm.
Definition: qgsprocessingfeedback.h:37
qgsstringutils.h
QgsFields
Container of fields for a vector layer.
Definition: qgsfields.h:44
QgsUnitTypes::distanceToAreaUnit
static Q_INVOKABLE QgsUnitTypes::AreaUnit distanceToAreaUnit(QgsUnitTypes::DistanceUnit distanceUnit)
Converts a distance unit to its corresponding area unit, e.g., meters to square meters.
Definition: qgsunittypes.cpp:1176
QgsUnitTypes::toAbbreviatedString
static Q_INVOKABLE QString toAbbreviatedString(QgsUnitTypes::DistanceUnit unit)
Returns a translated abbreviation representing a distance unit.
Definition: qgsunittypes.cpp:269
QgsFeedback::isCanceled
bool isCanceled() const SIP_HOLDGIL
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:67
QgsStringUtils::ampersandEncode
static QString ampersandEncode(const QString &string)
Makes a raw string safe for inclusion as a HTML/XML string literal.
Definition: qgsstringutils.cpp:118
QgsFields::append
bool append(const QgsField &field, FieldOrigin origin=OriginProvider, int originIndex=-1)
Appends a field. The field must have unique name, otherwise it is rejected (returns false)
Definition: qgsfields.cpp:59
qgsalgorithmrastersurfacevolume.h
QgsProcessingOutputNumber
A numeric output for processing algorithms.
Definition: qgsprocessingoutputs.h:312
QgsRasterDataProvider::clone
QgsRasterDataProvider * clone() const override=0
Clone itself, create deep copy.
QgsRasterDataProvider::sourceHasNoDataValue
virtual bool sourceHasNoDataValue(int bandNo) const
Returns true if source band has no data value.
Definition: qgsrasterdataprovider.h:241
QgsProcessingParameterFeatureSink
A feature sink output for processing algorithms.
Definition: qgsprocessingparameters.h:3219
QgsRasterLayer::width
int width() const
Returns the width of the (unclipped) raster.
Definition: qgsrasterlayer.cpp:2618
QgsProcessing::TypeVector
@ TypeVector
Tables (i.e. vector layers with or without geometry). When used for a sink this indicates the sink ha...
Definition: qgsprocessing.h:54
QgsProcessingContext
Contains information about the context in which a processing algorithm is executed.
Definition: qgsprocessingcontext.h:46
QgsProcessingParameterFileDestination
A generic file based destination parameter, for specifying the destination path for a file (non-map l...
Definition: qgsprocessingparameters.h:3451
QgsRasterLayer::height
int height() const
Returns the height of the (unclipped) raster.
Definition: qgsrasterlayer.cpp:2624
QgsMapLayer::extent
virtual QgsRectangle extent() const
Returns the extent of the layer.
Definition: qgsmaplayer.cpp:305
QgsRasterIterator::DEFAULT_MAXIMUM_TILE_HEIGHT
static const int DEFAULT_MAXIMUM_TILE_HEIGHT
Default maximum tile height.
Definition: qgsrasteriterator.h:170
QgsProcessingParameterRasterLayer
A raster layer parameter for processing algorithms.
Definition: qgsprocessingparameters.h:2495
QgsRasterLayer
Represents a raster layer.
Definition: qgsrasterlayer.h:76
QgsRasterIterator
Iterator for sequentially processing raster cells.
Definition: qgsrasteriterator.h:34
QgsCoordinateReferenceSystem
This class represents a coordinate reference system (CRS).
Definition: qgscoordinatereferencesystem.h:211
QgsRasterLayer::rasterUnitsPerPixelY
double rasterUnitsPerPixelY() const
Returns the number of raster units per each raster pixel in Y axis.
Definition: qgsrasterlayer.cpp:582
QgsMapLayer::source
QString source() const
Returns the source for the layer.
Definition: qgsmaplayer.cpp:300
QgsWkbTypes::NoGeometry
@ NoGeometry
Definition: qgswkbtypes.h:85
QgsRasterIterator::DEFAULT_MAXIMUM_TILE_WIDTH
static const int DEFAULT_MAXIMUM_TILE_WIDTH
Default maximum tile width.
Definition: qgsrasteriterator.h:167
QgsProcessingParameterBand
A raster band parameter for Processing algorithms.
Definition: qgsprocessingparameters.h:3548
QgsAttributes
A vector of attributes. Mostly equal to QVector<QVariant>.
Definition: qgsattributes.h:57
QgsFeature
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:55
QgsProcessingParameterEnum
An enum based parameter for processing algorithms, allowing for selection from predefined values.
Definition: qgsprocessingparameters.h:2540
QgsFeature::setAttributes
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
Definition: qgsfeature.cpp:160
QgsProcessingException
Custom exception class for processing related exceptions.
Definition: qgsexception.h:82
QgsRasterLayer::dataProvider
QgsRasterDataProvider * dataProvider() override
Returns the source data provider.
Definition: qgsrasterlayer.cpp:257
QgsFeatureSink::FastInsert
@ FastInsert
Use faster inserts, at the cost of updating the passed features to reflect changes made at the provid...
Definition: qgsfeaturesink.h:70
QgsRasterLayer::rasterUnitsPerPixelX
double rasterUnitsPerPixelX() const
Returns the number of raster units per each raster pixel in X axis.
Definition: qgsrasterlayer.cpp:566
QgsField
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:50