QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
qgsalgorithmreclassifybylayer.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsalgorithmreclassifybylayer.cpp
3  ---------------------
4  begin : June, 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 #include "qgsrasterfilewriter.h"
20 #include "qgsreclassifyutils.h"
21 #include "qgsrasteranalysisutils.h"
22 #include "qgis.h"
23 
25 
26 //
27 // QgsReclassifyAlgorithmBase
28 //
29 
30 
31 QString QgsReclassifyAlgorithmBase::group() const
32 {
33  return QObject::tr( "Raster analysis" );
34 }
35 
36 QString QgsReclassifyAlgorithmBase::groupId() const
37 {
38  return QStringLiteral( "rasteranalysis" );
39 }
40 
41 void QgsReclassifyAlgorithmBase::initAlgorithm( const QVariantMap & )
42 {
43  addParameter( new QgsProcessingParameterRasterLayer( QStringLiteral( "INPUT_RASTER" ),
44  QObject::tr( "Raster layer" ) ) );
45  addParameter( new QgsProcessingParameterBand( QStringLiteral( "RASTER_BAND" ),
46  QObject::tr( "Band number" ), 1, QStringLiteral( "INPUT_RASTER" ) ) );
47 
48  addAlgorithmParams();
49 
50  std::unique_ptr< QgsProcessingParameterNumber > noDataValueParam = std::make_unique< QgsProcessingParameterNumber >( QStringLiteral( "NO_DATA" ),
51  QObject::tr( "Output no data value" ), QgsProcessingParameterNumber::Double, -9999 );
52  noDataValueParam->setFlags( QgsProcessingParameterDefinition::FlagAdvanced );
53  addParameter( noDataValueParam.release() );
54 
55  std::unique_ptr< QgsProcessingParameterEnum > boundsHandling = std::make_unique< QgsProcessingParameterEnum >( QStringLiteral( "RANGE_BOUNDARIES" ),
56  QObject::tr( "Range boundaries" ), QStringList() << QObject::tr( "min < value <= max" )
57  << QObject::tr( "min <= value < max" )
58  << QObject::tr( "min <= value <= max" )
59  << QObject::tr( "min < value < max" ),
60  false, 0 );
61  boundsHandling->setFlags( QgsProcessingParameterDefinition::FlagAdvanced );
62  addParameter( boundsHandling.release() );
63 
64  std::unique_ptr< QgsProcessingParameterBoolean > missingValuesParam = std::make_unique< QgsProcessingParameterBoolean >( QStringLiteral( "NODATA_FOR_MISSING" ),
65  QObject::tr( "Use no data when no range matches value" ), false, false );
66  missingValuesParam->setFlags( QgsProcessingParameterDefinition::FlagAdvanced );
67  addParameter( missingValuesParam.release() );
68 
69  std::unique_ptr< QgsProcessingParameterDefinition > typeChoice = QgsRasterAnalysisUtils::createRasterTypeParameter( QStringLiteral( "DATA_TYPE" ), QObject::tr( "Output data type" ), Qgis::DataType::Float32 );
70  typeChoice->setFlags( QgsProcessingParameterDefinition::FlagAdvanced );
71  addParameter( typeChoice.release() );
72 
73  addParameter( new QgsProcessingParameterRasterDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Reclassified raster" ) ) );
74 }
75 
76 bool QgsReclassifyAlgorithmBase::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
77 {
78  mDataType = QgsRasterAnalysisUtils::rasterTypeChoiceToDataType( parameterAsEnum( parameters, QStringLiteral( "DATA_TYPE" ), context ) );
79  QgsRasterLayer *layer = parameterAsRasterLayer( parameters, QStringLiteral( "INPUT_RASTER" ), context );
80 
81  if ( !layer )
82  throw QgsProcessingException( invalidRasterError( parameters, QStringLiteral( "INPUT_RASTER" ) ) );
83 
84  mBand = parameterAsInt( parameters, QStringLiteral( "RASTER_BAND" ), context );
85  if ( mBand < 1 || mBand > layer->bandCount() )
86  throw QgsProcessingException( QObject::tr( "Invalid band number for RASTER_BAND (%1): Valid values for input raster are 1 to %2" ).arg( mBand )
87  .arg( layer->bandCount() ) );
88 
89  mInterface.reset( layer->dataProvider()->clone() );
90  mExtent = layer->extent();
91  mCrs = layer->crs();
92  mRasterUnitsPerPixelX = std::abs( layer->rasterUnitsPerPixelX() );
93  mRasterUnitsPerPixelY = std::abs( layer->rasterUnitsPerPixelY() );
94  mNbCellsXProvider = mInterface->xSize();
95  mNbCellsYProvider = mInterface->ySize();
96 
97  mNoDataValue = parameterAsDouble( parameters, QStringLiteral( "NO_DATA" ), context );
98  mUseNoDataForMissingValues = parameterAsBoolean( parameters, QStringLiteral( "NODATA_FOR_MISSING" ), context );
99 
100  const int boundsType = parameterAsEnum( parameters, QStringLiteral( "RANGE_BOUNDARIES" ), context );
101  switch ( boundsType )
102  {
103  case 0:
104  mBoundsType = QgsReclassifyUtils::RasterClass::IncludeMax;
105  break;
106 
107  case 1:
108  mBoundsType = QgsReclassifyUtils::RasterClass::IncludeMin;
109  break;
110 
111  case 2:
112  mBoundsType = QgsReclassifyUtils::RasterClass::IncludeMinAndMax;
113  break;
114 
115  case 3:
116  mBoundsType = QgsReclassifyUtils::RasterClass::Exclusive;
117  break;
118  }
119 
120  return _prepareAlgorithm( parameters, context, feedback );
121 }
122 
123 QVariantMap QgsReclassifyAlgorithmBase::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
124 {
125  const QVector< QgsReclassifyUtils::RasterClass > classes = createClasses( mBoundsType, parameters, context, feedback );
126 
127  QgsReclassifyUtils::reportClasses( classes, feedback );
128  QgsReclassifyUtils::checkForOverlaps( classes, feedback );
129 
130  const QString outputFile = parameterAsOutputLayer( parameters, QStringLiteral( "OUTPUT" ), context );
131  const QFileInfo fi( outputFile );
132  const QString outputFormat = QgsRasterFileWriter::driverForExtension( fi.suffix() );
133 
134  std::unique_ptr< QgsRasterFileWriter > writer = std::make_unique< QgsRasterFileWriter >( outputFile );
135  writer->setOutputProviderKey( QStringLiteral( "gdal" ) );
136  writer->setOutputFormat( outputFormat );
137  std::unique_ptr<QgsRasterDataProvider > provider( writer->createOneBandRaster( mDataType, mNbCellsXProvider, mNbCellsYProvider, mExtent, mCrs ) );
138  if ( !provider )
139  throw QgsProcessingException( QObject::tr( "Could not create raster output: %1" ).arg( outputFile ) );
140  if ( !provider->isValid() )
141  throw QgsProcessingException( QObject::tr( "Could not create raster output %1: %2" ).arg( outputFile, provider->error().message( QgsErrorMessage::Text ) ) );
142 
143  provider->setNoDataValue( 1, mNoDataValue );
144 
145  QgsReclassifyUtils::reclassify( classes, mInterface.get(), mBand, mExtent, mNbCellsXProvider, mNbCellsYProvider, provider.get(), mNoDataValue, mUseNoDataForMissingValues,
146  feedback );
147 
148  QVariantMap outputs;
149  outputs.insert( QStringLiteral( "OUTPUT" ), outputFile );
150  return outputs;
151 }
152 
153 
154 //
155 // QgsReclassifyByLayerAlgorithm
156 //
157 
158 QString QgsReclassifyByLayerAlgorithm::name() const
159 {
160  return QStringLiteral( "reclassifybylayer" );
161 }
162 
163 QString QgsReclassifyByLayerAlgorithm::displayName() const
164 {
165  return QObject::tr( "Reclassify by layer" );
166 }
167 
168 QStringList QgsReclassifyByLayerAlgorithm::tags() const
169 {
170  return QObject::tr( "raster,reclassify,classes,calculator" ).split( ',' );
171 }
172 
173 QString QgsReclassifyByLayerAlgorithm::shortHelpString() const
174 {
175  return QObject::tr( "This algorithm reclassifies a raster band by assigning new class values based on the ranges specified in a vector table." );
176 }
177 
178 QgsReclassifyByLayerAlgorithm *QgsReclassifyByLayerAlgorithm::createInstance() const
179 {
180  return new QgsReclassifyByLayerAlgorithm();
181 }
182 
183 void QgsReclassifyByLayerAlgorithm::addAlgorithmParams()
184 {
185  addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT_TABLE" ),
186  QObject::tr( "Layer containing class breaks" ), QList< int >() << QgsProcessing::TypeVector ) );
187  addParameter( new QgsProcessingParameterField( QStringLiteral( "MIN_FIELD" ),
188  QObject::tr( "Minimum class value field" ), QVariant(), QStringLiteral( "INPUT_TABLE" ), QgsProcessingParameterField::Numeric ) );
189  addParameter( new QgsProcessingParameterField( QStringLiteral( "MAX_FIELD" ),
190  QObject::tr( "Maximum class value field" ), QVariant(), QStringLiteral( "INPUT_TABLE" ), QgsProcessingParameterField::Numeric ) );
191  addParameter( new QgsProcessingParameterField( QStringLiteral( "VALUE_FIELD" ),
192  QObject::tr( "Output value field" ), QVariant(), QStringLiteral( "INPUT_TABLE" ), QgsProcessingParameterField::Numeric ) );
193 }
194 
195 bool QgsReclassifyByLayerAlgorithm::_prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
196 {
197  std::unique_ptr< QgsFeatureSource >tableSource( parameterAsSource( parameters, QStringLiteral( "INPUT_TABLE" ), context ) );
198  if ( !tableSource )
199  throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT_TABLE" ) ) );
200 
201  const QString fieldMin = parameterAsString( parameters, QStringLiteral( "MIN_FIELD" ), context );
202  mMinFieldIdx = tableSource->fields().lookupField( fieldMin );
203  if ( mMinFieldIdx < 0 )
204  throw QgsProcessingException( QObject::tr( "Invalid field specified for MIN_FIELD: %1" ).arg( fieldMin ) );
205  const QString fieldMax = parameterAsString( parameters, QStringLiteral( "MAX_FIELD" ), context );
206  mMaxFieldIdx = tableSource->fields().lookupField( fieldMax );
207  if ( mMaxFieldIdx < 0 )
208  throw QgsProcessingException( QObject::tr( "Invalid field specified for MAX_FIELD: %1" ).arg( fieldMax ) );
209  const QString fieldValue = parameterAsString( parameters, QStringLiteral( "VALUE_FIELD" ), context );
210  mValueFieldIdx = tableSource->fields().lookupField( fieldValue );
211  if ( mValueFieldIdx < 0 )
212  throw QgsProcessingException( QObject::tr( "Invalid field specified for VALUE_FIELD: %1" ).arg( fieldValue ) );
213 
214  QgsFeatureRequest request;
216  request.setSubsetOfAttributes( QgsAttributeList() << mMinFieldIdx << mMaxFieldIdx << mValueFieldIdx );
217  mTableIterator = tableSource->getFeatures( request );
218 
219  return true;
220 }
221 
222 QVector<QgsReclassifyUtils::RasterClass> QgsReclassifyByLayerAlgorithm::createClasses( QgsRasterRange::BoundsType boundsType, const QVariantMap &, QgsProcessingContext &, QgsProcessingFeedback * )
223 {
224  QVector< QgsReclassifyUtils::RasterClass > classes;
225  QgsFeature f;
226  while ( mTableIterator.nextFeature( f ) )
227  {
228  bool ok = false;
229 
230  // null values map to nan, which corresponds to a range extended to +/- infinity....
231  const QVariant minVariant = f.attribute( mMinFieldIdx );
232  double minValue;
233  if ( minVariant.isNull() || minVariant.toString().isEmpty() )
234  {
235  minValue = std::numeric_limits<double>::quiet_NaN();
236  }
237  else
238  {
239  minValue = minVariant.toDouble( &ok );
240  if ( !ok )
241  throw QgsProcessingException( QObject::tr( "Invalid value for minimum: %1" ).arg( minVariant.toString() ) );
242  }
243  const QVariant maxVariant = f.attribute( mMaxFieldIdx );
244  double maxValue;
245  if ( maxVariant.isNull() || maxVariant.toString().isEmpty() )
246  {
247  maxValue = std::numeric_limits<double>::quiet_NaN();
248  ok = true;
249  }
250  else
251  {
252  maxValue = maxVariant.toDouble( &ok );
253  if ( !ok )
254  throw QgsProcessingException( QObject::tr( "Invalid value for maximum: %1" ).arg( maxVariant.toString() ) );
255  }
256 
257  const double value = f.attribute( mValueFieldIdx ).toDouble( &ok );
258  if ( !ok )
259  throw QgsProcessingException( QObject::tr( "Invalid output value: %1" ).arg( f.attribute( mValueFieldIdx ).toString() ) );
260 
261  classes << QgsReclassifyUtils::RasterClass( minValue, maxValue, boundsType, value );
262  }
263  return classes;
264 }
265 
266 
267 //
268 // QgsReclassifyByTableAlgorithm
269 //
270 
271 QString QgsReclassifyByTableAlgorithm::name() const
272 {
273  return QStringLiteral( "reclassifybytable" );
274 }
275 
276 QString QgsReclassifyByTableAlgorithm::displayName() const
277 {
278  return QObject::tr( "Reclassify by table" );
279 }
280 
281 QStringList QgsReclassifyByTableAlgorithm::tags() const
282 {
283  return QObject::tr( "raster,reclassify,classes,calculator" ).split( ',' );
284 }
285 
286 QString QgsReclassifyByTableAlgorithm::shortHelpString() const
287 {
288  return QObject::tr( "This algorithm reclassifies a raster band by assigning new class values based on the ranges specified in a fixed table." );
289 }
290 
291 QgsReclassifyByTableAlgorithm *QgsReclassifyByTableAlgorithm::createInstance() const
292 {
293  return new QgsReclassifyByTableAlgorithm();
294 }
295 
296 void QgsReclassifyByTableAlgorithm::addAlgorithmParams()
297 {
298  addParameter( new QgsProcessingParameterMatrix( QStringLiteral( "TABLE" ),
299  QObject::tr( "Reclassification table" ),
300  1, false, QStringList() << QObject::tr( "Minimum" )
301  << QObject::tr( "Maximum" )
302  << QObject::tr( "Value" ) ) );
303 }
304 
305 bool QgsReclassifyByTableAlgorithm::_prepareAlgorithm( const QVariantMap &, QgsProcessingContext &, QgsProcessingFeedback * )
306 {
307  return true;
308 }
309 
310 QVector<QgsReclassifyUtils::RasterClass> QgsReclassifyByTableAlgorithm::createClasses( QgsReclassifyUtils::RasterClass::BoundsType boundsType, const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
311 {
312  const QVariantList table = parameterAsMatrix( parameters, QStringLiteral( "TABLE" ), context );
313  if ( table.count() % 3 != 0 )
314  throw QgsProcessingException( QObject::tr( "Invalid value for TABLE: list must contain a multiple of 3 elements (found %1)" ).arg( table.count() ) );
315 
316  const int rows = table.count() / 3;
317  QVector< QgsReclassifyUtils::RasterClass > classes;
318  classes.reserve( rows );
319  for ( int row = 0; row < rows; ++row )
320  {
321  bool ok = false;
322 
323  // null values map to nan, which corresponds to a range extended to +/- infinity....
324  const QVariant minVariant = table.at( row * 3 );
325  double minValue;
326  if ( minVariant.isNull() || minVariant.toString().isEmpty() )
327  {
328  minValue = std::numeric_limits<double>::quiet_NaN();
329  }
330  else
331  {
332  minValue = minVariant.toDouble( &ok );
333  if ( !ok )
334  throw QgsProcessingException( QObject::tr( "Invalid value for minimum: %1" ).arg( table.at( row * 3 ).toString() ) );
335  }
336  const QVariant maxVariant = table.at( row * 3 + 1 );
337  double maxValue;
338  if ( maxVariant.isNull() || maxVariant.toString().isEmpty() )
339  {
340  maxValue = std::numeric_limits<double>::quiet_NaN();
341  ok = true;
342  }
343  else
344  {
345  maxValue = maxVariant.toDouble( &ok );
346  if ( !ok )
347  throw QgsProcessingException( QObject::tr( "Invalid value for maximum: %1" ).arg( table.at( row * 3 + 1 ).toString() ) );
348  }
349 
350  const double value = table.at( row * 3 + 2 ).toDouble( &ok );
351  if ( !ok )
352  throw QgsProcessingException( QObject::tr( "Invalid output value: %1" ).arg( table.at( row * 3 + 2 ).toString() ) );
353 
354  classes << QgsReclassifyUtils::RasterClass( minValue, maxValue, boundsType, value );
355  }
356  return classes;
357 }
358 
360 
361 
@ Float32
Thirty two bit floating point (float)
This class wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setFlags(QgsFeatureRequest::Flags flags)
Sets flags that affect how features will be fetched.
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
Definition: qgsfeature.cpp:320
virtual QgsRectangle extent() const
Returns the extent of the layer.
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:79
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 raster band parameter for Processing algorithms.
@ FlagAdvanced
Parameter is an advanced parameter which should be hidden from users by default.
An input feature source (such as vector layers) parameter for processing algorithms.
A vector layer or feature source field parameter for processing algorithms.
@ Numeric
Accepts numeric fields.
A table (matrix) parameter for processing algorithms.
A raster layer destination parameter, for specifying the destination path for a raster layer created ...
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
QgsRasterDataProvider * clone() const override=0
Clone itself, create deep copy.
static QString driverForExtension(const QString &extension)
Returns the GDAL driver name for a specified file extension.
Represents a raster layer.
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.
BoundsType
Handling for min and max bounds.
QList< int > QgsAttributeList
Definition: qgsfield.h:26