QGIS API Documentation  3.2.0-Bonn (bc43194)
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  for ( int row = 0; row < rows; ++row )
319  {
320  bool ok = false;
321 
322  // null values map to nan, which corresponds to a range extended to +/- infinity....
323  const QVariant minVariant = table.at( row * 3 );
324  double minValue;
325  if ( minVariant.isNull() || minVariant.toString().isEmpty() )
326  {
327  minValue = std::numeric_limits<double>::quiet_NaN();
328  }
329  else
330  {
331  minValue = minVariant.toDouble( &ok );
332  if ( !ok )
333  throw QgsProcessingException( QObject::tr( "Invalid value for minimum: %1" ).arg( table.at( row * 3 ).toString() ) );
334  }
335  const QVariant maxVariant = table.at( row * 3 + 1 );
336  double maxValue;
337  if ( maxVariant.isNull() || maxVariant.toString().isEmpty() )
338  {
339  maxValue = std::numeric_limits<double>::quiet_NaN();
340  ok = true;
341  }
342  else
343  {
344  maxValue = maxVariant.toDouble( &ok );
345  if ( !ok )
346  throw QgsProcessingException( QObject::tr( "Invalid value for maximum: %1" ).arg( table.at( row * 3 + 1 ).toString() ) );
347  }
348 
349  const double value = table.at( row * 3 + 2 ).toDouble( &ok );
350  if ( !ok )
351  throw QgsProcessingException( QObject::tr( "Invalid output value: %1" ).arg( table.at( row * 3 + 2 ).toString() ) );
352 
353  classes << QgsReclassifyUtils::RasterClass( minValue, maxValue, boundsType, value );
354  }
355  return classes;
356 }
357 
359 
360 
Base class for providing feedback from a processing algorithm.
Parameter is an advanced parameter which should be hidden from users by default.
int bandCount() const
Returns the number of bands in this layer.
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.
double rasterUnitsPerPixelY() const
Returns the number of raster units per each raster pixel in Y axis.
A raster band parameter for Processing algorithms.
Thirty two bit floating point (float)
Definition: qgis.h:99
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:62
virtual QgsRectangle extent() const
Returns the extent of the layer.
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.
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
QgsCoordinateReferenceSystem crs() const
Returns the layer&#39;s spatial reference system.
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.
QVariant attribute(const QString &name) const
Lookup attribute value from attribute name.
Definition: qgsfeature.cpp:255
Contains information about the context in which a processing algorithm is executed.
QgsFeatureRequest & setFlags(QgsFeatureRequest::Flags flags)
Sets flags that affect how features will be fetched.