QGIS API Documentation 4.3.0-Master (0d5b841b09e)
Loading...
Searching...
No Matches
qgsalgorithmroundrastervalues.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsalgorithmroundrastervalues.cpp
3 ---------------------
4 begin : April 2020
5 copyright : (C) 2020 by Clemens Raffler
6 email : clemens dot raffler 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 "qgsrasterfilewriter.h"
21
22#include <QString>
23
24using namespace Qt::StringLiterals;
25
27
28QString QgsRoundRasterValuesAlgorithm::name() const
29{
30 return u"roundrastervalues"_s;
31}
32
33QString QgsRoundRasterValuesAlgorithm::displayName() const
34{
35 return QObject::tr( "Round raster" );
36}
37
38QStringList QgsRoundRasterValuesAlgorithm::tags() const
39{
40 return QObject::tr( "data,cells,round,truncate" ).split( ',' );
41}
42
43QString QgsRoundRasterValuesAlgorithm::group() const
44{
45 return QObject::tr( "Raster analysis" );
46}
47
48QString QgsRoundRasterValuesAlgorithm::groupId() const
49{
50 return u"rasteranalysis"_s;
51}
52
53void QgsRoundRasterValuesAlgorithm::initAlgorithm( const QVariantMap & )
54{
55 addParameter( new QgsProcessingParameterRasterLayer( u"INPUT"_s, u"Input raster"_s ) );
56 addParameter( new QgsProcessingParameterBand( u"BAND"_s, QObject::tr( "Band number" ), 1, u"INPUT"_s ) );
57 addParameter(
58 new QgsProcessingParameterEnum( u"ROUNDING_DIRECTION"_s, QObject::tr( "Rounding direction" ), QStringList() << QObject::tr( "Round up" ) << QObject::tr( "Round to nearest" ) << QObject::tr( "Round down" ), false, 1 )
59 );
60 addParameter( new QgsProcessingParameterNumber( u"DECIMAL_PLACES"_s, QObject::tr( "Number of decimals places" ), Qgis::ProcessingNumberParameterType::Integer, 2 ) );
61 std::unique_ptr<QgsProcessingParameterDefinition> baseParameter
62 = std::make_unique<QgsProcessingParameterNumber>( u"BASE_N"_s, QObject::tr( "Base n for rounding to multiples of n" ), Qgis::ProcessingNumberParameterType::Integer, 10, true, 1 );
63 baseParameter->setFlags( Qgis::ProcessingParameterFlag::Advanced );
64 addParameter( baseParameter.release() );
65
66 // backwards compatibility parameter
67 // TODO QGIS 5: remove parameter and related logic
68 auto createOptsParam = std::make_unique<QgsProcessingParameterString>( u"CREATE_OPTIONS"_s, QObject::tr( "Creation options" ), QVariant(), false, true );
69 createOptsParam->setMetadata( QVariantMap( { { u"widget_wrapper"_s, QVariantMap( { { u"widget_type"_s, u"rasteroptions"_s } } ) } } ) );
70 createOptsParam->setFlags( createOptsParam->flags() | Qgis::ProcessingParameterFlag::Hidden );
71 addParameter( createOptsParam.release() );
72
73 auto creationOptsParam = std::make_unique<QgsProcessingParameterString>( u"CREATION_OPTIONS"_s, QObject::tr( "Creation options" ), QVariant(), false, true );
74 creationOptsParam->setMetadata( QVariantMap( { { u"widget_wrapper"_s, QVariantMap( { { u"widget_type"_s, u"rasteroptions"_s } } ) } } ) );
75 creationOptsParam->setFlags( creationOptsParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
76 addParameter( creationOptsParam.release() );
77
78 addParameter( new QgsProcessingParameterRasterDestination( u"OUTPUT"_s, QObject::tr( "Output raster" ) ) );
79}
80
81QString QgsRoundRasterValuesAlgorithm::shortHelpString() const
82{
83 return QObject::tr(
84 "This algorithm rounds the cell values of a raster dataset to the specified number of decimals.\n "
85 "Alternatively, a negative number of decimal places may be used to round values to powers of a base n "
86 "(specified in the advanced parameter Base n). For example, with a Base value n of 10 and Decimal places of -1 "
87 "the algorithm rounds cell values to multiples of 10, -2 rounds to multiples of 100, and so on. Arbitrary base values "
88 "may be chosen, the algorithm applies the same multiplicative principle. Rounding cell values to multiples of "
89 "a base n may be used to generalize raster layers.\n"
90 "The algorithm preserves the data type of the input raster. Therefore byte/integer rasters can only be rounded "
91 "to multiples of a base n, otherwise a warning is raised and the raster gets copied as byte/integer raster."
92 );
93}
94
95QString QgsRoundRasterValuesAlgorithm::shortDescription() const
96{
97 return QObject::tr( "Rounds the cell values of a raster dataset to a specified number of decimals." );
98}
99
100QgsRoundRasterValuesAlgorithm *QgsRoundRasterValuesAlgorithm::createInstance() const
101{
102 return new QgsRoundRasterValuesAlgorithm();
103}
104
105bool QgsRoundRasterValuesAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
106{
107 Q_UNUSED( feedback );
108 QgsRasterLayer *inputRaster = parameterAsRasterLayer( parameters, u"INPUT"_s, context );
109 mDecimalPrecision = parameterAsInt( parameters, u"DECIMAL_PLACES"_s, context );
110 mBaseN = parameterAsInt( parameters, u"BASE_N"_s, context );
111 mMultipleOfBaseN = pow( mBaseN, abs( mDecimalPrecision ) );
112 mScaleFactor = std::pow( 10.0, mDecimalPrecision );
113
114 if ( !inputRaster )
115 throw QgsProcessingException( invalidRasterError( parameters, u"INPUT"_s ) );
116
117 mBand = parameterAsInt( parameters, u"BAND"_s, context );
118 if ( mBand < 1 || mBand > inputRaster->bandCount() )
119 throw QgsProcessingException( QObject::tr( "Invalid band number for BAND (%1): Valid values for input raster are 1 to %2" ).arg( mBand ).arg( inputRaster->bandCount() ) );
120
121 mRoundingDirection = parameterAsEnum( parameters, u"ROUNDING_DIRECTION"_s, context );
122
123 mInterface.reset( inputRaster->dataProvider()->clone() );
124 mDataType = mInterface->dataType( mBand );
125
126 switch ( mDataType )
127 {
133 mIsInteger = true;
134 if ( mDecimalPrecision > -1 )
135 feedback->reportError( QObject::tr( "Input raster is of byte or integer type. The cell values cannot be rounded and will be output using the same data type." ), false );
136 break;
137 default:
138 mIsInteger = false;
139 break;
140 }
141
142 mInputNoDataValue = inputRaster->dataProvider()->sourceNoDataValue( mBand );
143 mExtent = inputRaster->extent();
144 mLayerWidth = inputRaster->width();
145 mLayerHeight = inputRaster->height();
146 mCrs = inputRaster->crs();
147 mNbCellsXProvider = mInterface->xSize();
148 mNbCellsYProvider = mInterface->ySize();
149 return true;
150}
151
152QVariantMap QgsRoundRasterValuesAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
153{
154 QGS_MARK_ALGORITHM_SOURCE
155
156 //prepare output dataset
157 QString creationOptions = parameterAsString( parameters, u"CREATION_OPTIONS"_s, context ).trimmed();
158 // handle backwards compatibility parameter CREATE_OPTIONS
159 const QString optionsString = parameterAsString( parameters, u"CREATE_OPTIONS"_s, context );
160 if ( !optionsString.isEmpty() )
161 creationOptions = optionsString;
162
163 const QString outputFile = parameterAsOutputLayer( parameters, u"OUTPUT"_s, context );
164 const QString outputFormat = parameterAsOutputRasterFormat( parameters, u"OUTPUT"_s, context );
165 auto writer = std::make_unique<QgsRasterFileWriter>( outputFile );
166 writer->setOutputProviderKey( u"gdal"_s );
167 if ( !creationOptions.isEmpty() )
168 {
169 writer->setCreationOptions( creationOptions.split( '|' ) );
170 }
171 writer->setOutputFormat( outputFormat );
172 std::unique_ptr<QgsRasterDataProvider> provider( writer->createOneBandRaster( mInterface->dataType( mBand ), mNbCellsXProvider, mNbCellsYProvider, mExtent, mCrs ) );
173 if ( !provider )
174 throw QgsProcessingException( QObject::tr( "Could not create raster output: %1" ).arg( outputFile ) );
175 if ( !provider->isValid() )
176 throw QgsProcessingException( QObject::tr( "Could not create raster output %1: %2" ).arg( outputFile, provider->error().message( QgsErrorMessage::Text ) ) );
177
178 //prepare output provider
179 QgsRasterDataProvider *destinationRasterProvider;
180 destinationRasterProvider = provider.get();
181 destinationRasterProvider->setEditable( true );
182 destinationRasterProvider->setNoDataValue( 1, mInputNoDataValue );
183
184 const bool hasReportsDuringClose = provider->hasReportsDuringClose();
185 const double maxProgressDuringBlockWriting = hasReportsDuringClose ? 50.0 : 100.0;
186
187 QgsRasterIterator iter( mInterface.get() );
188 iter.startRasterRead( mBand, mLayerWidth, mLayerHeight, mExtent );
189 int iterLeft = 0;
190 int iterTop = 0;
191 int iterCols = 0;
192 int iterRows = 0;
193 std::unique_ptr<QgsRasterBlock> analysisRasterBlock;
194 while ( iter.readNextRasterPart( mBand, iterCols, iterRows, analysisRasterBlock, iterLeft, iterTop ) )
195 {
196 if ( feedback )
197 feedback->setProgress( maxProgressDuringBlockWriting * iter.progress( mBand ) );
198 if ( mIsInteger && mDecimalPrecision > -1 )
199 {
200 //nothing to round, just write raster block
201 analysisRasterBlock->setNoDataValue( mInputNoDataValue );
202 if ( !destinationRasterProvider->writeBlock( analysisRasterBlock.get(), mBand, iterLeft, iterTop ) )
203 {
204 throw QgsProcessingException( QObject::tr( "Could not write raster block: %1" ).arg( destinationRasterProvider->error().summary() ) );
205 }
206 }
207 else
208 {
209 for ( int row = 0; row < iterRows; row++ )
210 {
211 if ( feedback && feedback->isCanceled() )
212 break;
213 for ( int column = 0; column < iterCols; column++ )
214 {
215 bool isNoData = false;
216 const double val = analysisRasterBlock->valueAndNoData( row, column, isNoData );
217 if ( isNoData )
218 {
219 analysisRasterBlock->setValue( row, column, mInputNoDataValue );
220 }
221 else
222 {
223 double roundedVal = mInputNoDataValue;
224 if ( mRoundingDirection == 0 && mDecimalPrecision < 0 )
225 {
226 roundedVal = roundUpBaseN( val );
227 }
228 else if ( mRoundingDirection == 0 && mDecimalPrecision > -1 )
229 {
230 const double m = ( val < 0.0 ) ? -1.0 : 1.0;
231 roundedVal = roundUp( val, m );
232 }
233 else if ( mRoundingDirection == 1 && mDecimalPrecision < 0 )
234 {
235 roundedVal = roundNearestBaseN( val );
236 }
237 else if ( mRoundingDirection == 1 && mDecimalPrecision > -1 )
238 {
239 const double m = ( val < 0.0 ) ? -1.0 : 1.0;
240 roundedVal = roundNearest( val, m );
241 }
242 else if ( mRoundingDirection == 2 && mDecimalPrecision < 0 )
243 {
244 roundedVal = roundDownBaseN( val );
245 }
246 else
247 {
248 const double m = ( val < 0.0 ) ? -1.0 : 1.0;
249 roundedVal = roundDown( val, m );
250 }
251 //integer values get automatically cast to double when reading and back to int when writing
252 analysisRasterBlock->setValue( row, column, roundedVal );
253 }
254 }
255 }
256 if ( !destinationRasterProvider->writeBlock( analysisRasterBlock.get(), mBand, iterLeft, iterTop ) )
257 {
258 throw QgsProcessingException( QObject::tr( "Could not write raster block: %1" ).arg( destinationRasterProvider->error().summary() ) );
259 }
260 }
261 }
262 destinationRasterProvider->setEditable( false );
263
264 if ( feedback && hasReportsDuringClose )
265 {
266 std::unique_ptr<QgsFeedback> scaledFeedback( QgsFeedback::createScaledFeedback( feedback, maxProgressDuringBlockWriting, 100.0 ) );
267 if ( !provider->closeWithProgress( scaledFeedback.get() ) )
268 {
269 if ( feedback->isCanceled() )
270 return {};
271 throw QgsProcessingException( QObject::tr( "Could not write raster dataset" ) );
272 }
273 }
274
275 QVariantMap outputs;
276 outputs.insert( u"OUTPUT"_s, outputFile );
277 return outputs;
278}
279
280double QgsRoundRasterValuesAlgorithm::roundNearest( double value, double m )
281{
282 return ( std::round( value * m * mScaleFactor ) / mScaleFactor ) * m;
283}
284
285double QgsRoundRasterValuesAlgorithm::roundUp( double value, double m )
286{
287 return ( std::ceil( value * m * mScaleFactor ) / mScaleFactor ) * m;
288}
289
290double QgsRoundRasterValuesAlgorithm::roundDown( double value, double m )
291{
292 return ( std::floor( value * m * mScaleFactor ) / mScaleFactor ) * m;
293}
294
295double QgsRoundRasterValuesAlgorithm::roundNearestBaseN( double value )
296{
297 return static_cast<double>( mMultipleOfBaseN * round( value / mMultipleOfBaseN ) );
298}
299
300double QgsRoundRasterValuesAlgorithm::roundUpBaseN( double value )
301{
302 return static_cast<double>( mMultipleOfBaseN * ceil( value / mMultipleOfBaseN ) );
303}
304
305double QgsRoundRasterValuesAlgorithm::roundDownBaseN( double value )
306{
307 return static_cast<double>( mMultipleOfBaseN * floor( value / mMultipleOfBaseN ) );
308}
309
@ Int16
Sixteen bit signed integer (qint16).
Definition qgis.h:398
@ UInt16
Sixteen bit unsigned integer (quint16).
Definition qgis.h:397
@ Byte
Eight bit unsigned integer (quint8).
Definition qgis.h:395
@ Int32
Thirty two bit signed integer (qint32).
Definition qgis.h:400
@ UInt32
Thirty two bit unsigned integer (quint32).
Definition qgis.h:399
@ 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
virtual QgsError error() const
Gets current status error.
@ Text
Plain text format.
Definition qgserror.h:40
QString summary() const
Short error description, usually the first error in chain, the real error.
Definition qgserror.cpp:132
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:56
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:65
static std::unique_ptr< QgsFeedback > createScaledFeedback(QgsFeedback *parentFeedback, double startPercentage, double endPercentage)
Returns a feedback object whose [0, 100] progression range will be mapped to parentFeedback [startPer...
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.
virtual void reportError(const QString &error, bool fatalError=false)
Reports that the algorithm encountered an error while executing.
A raster band parameter for Processing algorithms.
An enum based parameter for processing algorithms, allowing for selection from predefined values.
A numeric 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.
Base class for raster data providers.
QgsRasterDataProvider * clone() const override=0
Clone itself, create deep copy.
virtual bool setNoDataValue(int bandNo, double noDataValue)
Set no data value on created dataset.
virtual double sourceNoDataValue(int bandNo) const
Value representing no data value.
bool writeBlock(QgsRasterBlock *block, int band, int xOffset=0, int yOffset=0)
Writes pixel data from a raster block into the provider data source.
virtual bool setEditable(bool enabled)
Turns on/off editing mode of the provider.
Iterator for sequentially processing raster cells.
Represents a raster layer.
int height() const
Returns the height of the (unclipped) raster.
int bandCount() const
Returns the number of bands in this layer.
QgsRasterDataProvider * dataProvider() override
Returns the source data provider.
int width() const
Returns the width of the (unclipped) raster.