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