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