QGIS API Documentation  3.20.0-Odense (decaadbb31)
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  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  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  int nbBlocksWidth = static_cast< int >( std::ceil( 1.0 * mLayerWidth / maxWidth ) );
153  int nbBlocksHeight = static_cast< int >( std::ceil( 1.0 * mLayerHeight / maxHeight ) );
154  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  double pixelArea = mRasterUnitsPerPixelX * mRasterUnitsPerPixelY;
214  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  sink->addFeature( f, QgsFeatureSink::FastInsert );
242  outputs.insert( QStringLiteral( "OUTPUT_TABLE" ), tableDest );
243  }
244  outputs.insert( QStringLiteral( "VOLUME" ), volume );
245  outputs.insert( QStringLiteral( "AREA" ), area );
246  outputs.insert( QStringLiteral( "PIXEL_COUNT" ), count );
247  return outputs;
248 }
249 
250 
252 
253 
254 
A vector of attributes.
Definition: qgsattributes.h:58
This class represents a coordinate reference system (CRS).
@ FastInsert
Use faster inserts, at the cost of updating the passed features to reflect changes made at the provid...
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
Definition: qgsfeature.cpp:135
bool isCanceled() const SIP_HOLDGIL
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:54
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:51
Container of fields for a vector layer.
Definition: qgsfields.h:45
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
virtual QgsRectangle extent() const
Returns the extent of the layer.
QString source() const
Returns the source for the layer.
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:76
Contains information about the context in which a processing algorithm is executed.
Custom exception class for processing related exceptions.
Definition: qgsexception.h:83
Base class for providing feedback from a processing algorithm.
A numeric output for processing algorithms.
A raster band parameter for Processing algorithms.
An enum based parameter for processing algorithms, allowing for selection from predefined values.
A feature sink output for processing algorithms.
A generic file based destination parameter, for specifying the destination path for a file (non-map l...
A numeric parameter for processing algorithms.
A raster layer parameter for processing algorithms.
@ TypeVector
Tables (i.e. vector layers with or without geometry). When used for a sink this indicates the sink ha...
Definition: qgsprocessing.h:54
virtual bool sourceHasNoDataValue(int bandNo) const
Returns true if source band has no data value.
QgsRasterDataProvider * clone() const override=0
Clone itself, create deep copy.
Iterator for sequentially processing raster cells.
static const int DEFAULT_MAXIMUM_TILE_WIDTH
Default maximum tile width.
static const int DEFAULT_MAXIMUM_TILE_HEIGHT
Default maximum tile height.
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.
static QString ampersandEncode(const QString &string)
Makes a raw string safe for inclusion as a HTML/XML string literal.
static Q_INVOKABLE QgsUnitTypes::AreaUnit distanceToAreaUnit(QgsUnitTypes::DistanceUnit distanceUnit)
Converts a distance unit to its corresponding area unit, e.g., meters to square meters.
static Q_INVOKABLE QString toAbbreviatedString(QgsUnitTypes::DistanceUnit unit)
Returns a translated abbreviation representing a distance unit.