QGIS API Documentation  3.4.15-Madeira (e83d02e274)
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 = qgis::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 = qgis::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 = qgis::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::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 = parameterAsBool( parameters, QStringLiteral( "NODATA_FOR_MISSING" ), context );
99 
100  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  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  QFileInfo fi( outputFile );
132  const QString outputFormat = QgsRasterFileWriter::driverForExtension( fi.suffix() );
133 
134  std::unique_ptr< QgsRasterFileWriter > writer = qgis::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  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  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  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 
double rasterUnitsPerPixelY() const
Returns the number of raster units per each raster pixel in Y axis.
Base class for providing feedback from a processing algorithm.
Parameter is an advanced parameter which should be hidden from users by default.
A vector layer or feature source field parameter for processing algorithms.
This class provides qgis with the ability to render raster datasets onto the mapcanvas.
double rasterUnitsPerPixelX() const
Returns the number of raster units per each raster pixel in X axis.
QgsRasterInterface * clone() const override=0
Clone itself, create deep copy.
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
BoundsType
Handling for min and max bounds.
A raster band parameter for Processing algorithms.
Thirty two bit floating point (float)
Definition: qgis.h:100
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:55
A raster layer destination parameter, for specifying the destination path for a raster layer created ...
static QString driverForExtension(const QString &extension)
Returns the GDAL driver name for a specified file extension.
QgsRasterDataProvider * dataProvider() override
Returns the layer&#39;s data provider.
A raster layer parameter for processing algorithms.
int bandCount() const
Returns the number of bands in this layer.
virtual QgsRectangle extent() const
Returns the extent of the layer.
This class wraps a request for features to a vector layer (or directly its vector data provider)...
Custom exception class for processing related exceptions.
Definition: qgsexception.h:82
QVariant attribute(const QString &name) const
Lookup attribute value from attribute name.
Definition: qgsfeature.cpp:262
An input feature source (such as vector layers) parameter for processing algorithms.
Tables (i.e. vector layers with or without geometry). When used for a sink this indicates the sink ha...
Definition: qgsprocessing.h:53
A table (matrix) parameter for processing algorithms.
QList< int > QgsAttributeList
Definition: qgsfield.h:27
Geometry is not required. It may still be returned if e.g. required for a filter condition.
Contains information about the context in which a processing algorithm is executed.
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:70
QgsFeatureRequest & setFlags(QgsFeatureRequest::Flags flags)
Sets flags that affect how features will be fetched.