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