QGIS API Documentation 3.43.0-Master (e01d6d7c4c0)
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#include "qgsrasterfilewriter.h"
20
22
23QString QgsRoundRasterValuesAlgorithm::name() const
24{
25 return QStringLiteral( "roundrastervalues" );
26}
27
28QString QgsRoundRasterValuesAlgorithm::displayName() const
29{
30 return QObject::tr( "Round raster" );
31}
32
33QStringList QgsRoundRasterValuesAlgorithm::tags() const
34{
35 return QObject::tr( "data,cells,round,truncate" ).split( ',' );
36}
37
38QString QgsRoundRasterValuesAlgorithm::group() const
39{
40 return QObject::tr( "Raster analysis" );
41}
42
43QString QgsRoundRasterValuesAlgorithm::groupId() const
44{
45 return QStringLiteral( "rasteranalysis" );
46}
47
48void QgsRoundRasterValuesAlgorithm::initAlgorithm( const QVariantMap & )
49{
50 addParameter( new QgsProcessingParameterRasterLayer( QStringLiteral( "INPUT" ), QStringLiteral( "Input raster" ) ) );
51 addParameter( new QgsProcessingParameterBand( QStringLiteral( "BAND" ), QObject::tr( "Band number" ), 1, QStringLiteral( "INPUT" ) ) );
52 addParameter( new QgsProcessingParameterEnum( QStringLiteral( "ROUNDING_DIRECTION" ), QObject::tr( "Rounding direction" ), QStringList() << QObject::tr( "Round up" ) << QObject::tr( "Round to nearest" ) << QObject::tr( "Round down" ), false, 1 ) );
53 addParameter( new QgsProcessingParameterNumber( QStringLiteral( "DECIMAL_PLACES" ), QObject::tr( "Number of decimals places" ), Qgis::ProcessingNumberParameterType::Integer, 2 ) );
54 std::unique_ptr<QgsProcessingParameterDefinition> baseParameter = std::make_unique<QgsProcessingParameterNumber>( QStringLiteral( "BASE_N" ), QObject::tr( "Base n for rounding to multiples of n" ), Qgis::ProcessingNumberParameterType::Integer, 10, true, 1 );
55 baseParameter->setFlags( Qgis::ProcessingParameterFlag::Advanced );
56 addParameter( baseParameter.release() );
57
58 // backwards compatibility parameter
59 // TODO QGIS 4: remove parameter and related logic
60 auto createOptsParam = std::make_unique<QgsProcessingParameterString>( QStringLiteral( "CREATE_OPTIONS" ), QObject::tr( "Creation options" ), QVariant(), false, true );
61 createOptsParam->setMetadata( QVariantMap( { { QStringLiteral( "widget_wrapper" ), QVariantMap( { { QStringLiteral( "widget_type" ), QStringLiteral( "rasteroptions" ) } } ) } } ) );
62 createOptsParam->setFlags( createOptsParam->flags() | Qgis::ProcessingParameterFlag::Hidden );
63 addParameter( createOptsParam.release() );
64
65 auto creationOptsParam = std::make_unique<QgsProcessingParameterString>( QStringLiteral( "CREATION_OPTIONS" ), QObject::tr( "Creation options" ), QVariant(), false, true );
66 creationOptsParam->setMetadata( QVariantMap( { { QStringLiteral( "widget_wrapper" ), QVariantMap( { { QStringLiteral( "widget_type" ), QStringLiteral( "rasteroptions" ) } } ) } } ) );
67 creationOptsParam->setFlags( creationOptsParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
68 addParameter( creationOptsParam.release() );
69
70 addParameter( new QgsProcessingParameterRasterDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Output raster" ) ) );
71}
72
73QString QgsRoundRasterValuesAlgorithm::shortHelpString() const
74{
75 return QObject::tr( "This algorithm rounds the cell values of a raster dataset to the specified number of decimals.\n "
76 "Alternatively, a negative number of decimal places may be used to round values to powers of a base n "
77 "(specified in the advanced parameter Base n). For example, with a Base value n of 10 and Decimal places of -1 "
78 "the algorithm rounds cell values to multiples of 10, -2 rounds to multiples of 100, and so on. Arbitrary base values "
79 "may be chosen, the algorithm applies the same multiplicative principle. Rounding cell values to multiples of "
80 "a base n may be used to generalize raster layers.\n"
81 "The algorithm preserves the data type of the input raster. Therefore byte/integer rasters can only be rounded "
82 "to multiples of a base n, otherwise a warning is raised and the raster gets copied as byte/integer raster." );
83}
84
85QString QgsRoundRasterValuesAlgorithm::shortDescription() const
86{
87 return QObject::tr( "Rounds the cell values of a raster dataset to a specified number of decimals." );
88}
89
90QgsRoundRasterValuesAlgorithm *QgsRoundRasterValuesAlgorithm::createInstance() const
91{
92 return new QgsRoundRasterValuesAlgorithm();
93}
94
95bool QgsRoundRasterValuesAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
96{
97 Q_UNUSED( feedback );
98 QgsRasterLayer *inputRaster = parameterAsRasterLayer( parameters, QStringLiteral( "INPUT" ), context );
99 mDecimalPrecision = parameterAsInt( parameters, QStringLiteral( "DECIMAL_PLACES" ), context );
100 mBaseN = parameterAsInt( parameters, QStringLiteral( "BASE_N" ), context );
101 mMultipleOfBaseN = pow( mBaseN, abs( mDecimalPrecision ) );
102 mScaleFactor = std::pow( 10.0, mDecimalPrecision );
103
104 if ( !inputRaster )
105 throw QgsProcessingException( invalidRasterError( parameters, QStringLiteral( "INPUT" ) ) );
106
107 mBand = parameterAsInt( parameters, QStringLiteral( "BAND" ), context );
108 if ( mBand < 1 || mBand > inputRaster->bandCount() )
109 throw QgsProcessingException( QObject::tr( "Invalid band number for BAND (%1): Valid values for input raster are 1 to %2" ).arg( mBand ).arg( inputRaster->bandCount() ) );
110
111 mRoundingDirection = parameterAsEnum( parameters, QStringLiteral( "ROUNDING_DIRECTION" ), context );
112
113 mInterface.reset( inputRaster->dataProvider()->clone() );
114 mDataType = mInterface->dataType( mBand );
115
116 switch ( mDataType )
117 {
123 mIsInteger = true;
124 if ( mDecimalPrecision > -1 )
125 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 );
126 break;
127 default:
128 mIsInteger = false;
129 break;
130 }
131
132 mInputNoDataValue = inputRaster->dataProvider()->sourceNoDataValue( mBand );
133 mExtent = inputRaster->extent();
134 mLayerWidth = inputRaster->width();
135 mLayerHeight = inputRaster->height();
136 mCrs = inputRaster->crs();
137 mNbCellsXProvider = mInterface->xSize();
138 mNbCellsYProvider = mInterface->ySize();
139 return true;
140}
141
142QVariantMap QgsRoundRasterValuesAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
143{
144 //prepare output dataset
145 QString creationOptions = parameterAsString( parameters, QStringLiteral( "CREATION_OPTIONS" ), context ).trimmed();
146 // handle backwards compatibility parameter CREATE_OPTIONS
147 const QString optionsString = parameterAsString( parameters, QStringLiteral( "CREATE_OPTIONS" ), context );
148 if ( !optionsString.isEmpty() )
149 creationOptions = optionsString;
150
151 const QString outputFile = parameterAsOutputLayer( parameters, QStringLiteral( "OUTPUT" ), context );
152 const QFileInfo fi( outputFile );
153 const QString outputFormat = QgsRasterFileWriter::driverForExtension( fi.suffix() );
154 auto writer = std::make_unique<QgsRasterFileWriter>( outputFile );
155 writer->setOutputProviderKey( QStringLiteral( "gdal" ) );
156 if ( !creationOptions.isEmpty() )
157 {
158 writer->setCreationOptions( creationOptions.split( '|' ) );
159 }
160 writer->setOutputFormat( outputFormat );
161 std::unique_ptr<QgsRasterDataProvider> provider( writer->createOneBandRaster( mInterface->dataType( mBand ), mNbCellsXProvider, mNbCellsYProvider, mExtent, mCrs ) );
162 if ( !provider )
163 throw QgsProcessingException( QObject::tr( "Could not create raster output: %1" ).arg( outputFile ) );
164 if ( !provider->isValid() )
165 throw QgsProcessingException( QObject::tr( "Could not create raster output %1: %2" ).arg( outputFile, provider->error().message( QgsErrorMessage::Text ) ) );
166
167 //prepare output provider
168 QgsRasterDataProvider *destinationRasterProvider;
169 destinationRasterProvider = provider.get();
170 destinationRasterProvider->setEditable( true );
171 destinationRasterProvider->setNoDataValue( 1, mInputNoDataValue );
172
175 const int nbBlocksWidth = static_cast<int>( std::ceil( 1.0 * mLayerWidth / maxWidth ) );
176 const int nbBlocksHeight = static_cast<int>( std::ceil( 1.0 * mLayerHeight / maxHeight ) );
177 const int nbBlocks = nbBlocksWidth * nbBlocksHeight;
178
179 QgsRasterIterator iter( mInterface.get() );
180 iter.startRasterRead( mBand, mLayerWidth, mLayerHeight, mExtent );
181 int iterLeft = 0;
182 int iterTop = 0;
183 int iterCols = 0;
184 int iterRows = 0;
185 std::unique_ptr<QgsRasterBlock> analysisRasterBlock;
186 while ( iter.readNextRasterPart( mBand, iterCols, iterRows, analysisRasterBlock, iterLeft, iterTop ) )
187 {
188 if ( feedback )
189 feedback->setProgress( 100 * ( ( iterTop / maxHeight * nbBlocksWidth ) + iterLeft / maxWidth ) / nbBlocks );
190 if ( mIsInteger && mDecimalPrecision > -1 )
191 {
192 //nothing to round, just write raster block
193 analysisRasterBlock->setNoDataValue( mInputNoDataValue );
194 if ( !destinationRasterProvider->writeBlock( analysisRasterBlock.get(), mBand, iterLeft, iterTop ) )
195 {
196 throw QgsProcessingException( QObject::tr( "Could not write raster block: %1" ).arg( destinationRasterProvider->error().summary() ) );
197 }
198 }
199 else
200 {
201 for ( int row = 0; row < iterRows; row++ )
202 {
203 if ( feedback && feedback->isCanceled() )
204 break;
205 for ( int column = 0; column < iterCols; column++ )
206 {
207 bool isNoData = false;
208 const double val = analysisRasterBlock->valueAndNoData( row, column, isNoData );
209 if ( isNoData )
210 {
211 analysisRasterBlock->setValue( row, column, mInputNoDataValue );
212 }
213 else
214 {
215 double roundedVal = mInputNoDataValue;
216 if ( mRoundingDirection == 0 && mDecimalPrecision < 0 )
217 {
218 roundedVal = roundUpBaseN( val );
219 }
220 else if ( mRoundingDirection == 0 && mDecimalPrecision > -1 )
221 {
222 const double m = ( val < 0.0 ) ? -1.0 : 1.0;
223 roundedVal = roundUp( val, m );
224 }
225 else if ( mRoundingDirection == 1 && mDecimalPrecision < 0 )
226 {
227 roundedVal = roundNearestBaseN( val );
228 }
229 else if ( mRoundingDirection == 1 && mDecimalPrecision > -1 )
230 {
231 const double m = ( val < 0.0 ) ? -1.0 : 1.0;
232 roundedVal = roundNearest( val, m );
233 }
234 else if ( mRoundingDirection == 2 && mDecimalPrecision < 0 )
235 {
236 roundedVal = roundDownBaseN( val );
237 }
238 else
239 {
240 const double m = ( val < 0.0 ) ? -1.0 : 1.0;
241 roundedVal = roundDown( val, m );
242 }
243 //integer values get automatically cast to double when reading and back to int when writing
244 analysisRasterBlock->setValue( row, column, roundedVal );
245 }
246 }
247 }
248 if ( !destinationRasterProvider->writeBlock( analysisRasterBlock.get(), mBand, iterLeft, iterTop ) )
249 {
250 throw QgsProcessingException( QObject::tr( "Could not write raster block: %1" ).arg( destinationRasterProvider->error().summary() ) );
251 }
252 }
253 }
254 destinationRasterProvider->setEditable( false );
255
256 QVariantMap outputs;
257 outputs.insert( QStringLiteral( "OUTPUT" ), outputFile );
258 return outputs;
259}
260
261double QgsRoundRasterValuesAlgorithm::roundNearest( double value, double m )
262{
263 return ( std::round( value * m * mScaleFactor ) / mScaleFactor ) * m;
264}
265
266double QgsRoundRasterValuesAlgorithm::roundUp( double value, double m )
267{
268 return ( std::ceil( value * m * mScaleFactor ) / mScaleFactor ) * m;
269}
270
271double QgsRoundRasterValuesAlgorithm::roundDown( double value, double m )
272{
273 return ( std::floor( value * m * mScaleFactor ) / mScaleFactor ) * m;
274}
275
276double QgsRoundRasterValuesAlgorithm::roundNearestBaseN( double value )
277{
278 return static_cast<double>( mMultipleOfBaseN * round( value / mMultipleOfBaseN ) );
279}
280
281double QgsRoundRasterValuesAlgorithm::roundUpBaseN( double value )
282{
283 return static_cast<double>( mMultipleOfBaseN * ceil( value / mMultipleOfBaseN ) );
284}
285
286double QgsRoundRasterValuesAlgorithm::roundDownBaseN( double value )
287{
288 return static_cast<double>( mMultipleOfBaseN * floor( value / mMultipleOfBaseN ) );
289}
290
@ Int16
Sixteen bit signed integer (qint16)
@ UInt16
Sixteen bit unsigned integer (quint16)
@ Byte
Eight bit unsigned integer (quint8)
@ Int32
Thirty two bit signed integer (qint32)
@ UInt32
Thirty two bit unsigned integer (quint32)
@ Hidden
Parameter is hidden and should not be shown to users.
@ Advanced
Parameter is an advanced parameter which should be hidden from users by default.
virtual QgsError error() const
Gets current status error.
QString summary() const
Short error description, usually the first error in chain, the real error.
Definition qgserror.cpp:129
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:53
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:61
virtual QgsRectangle extent() const
Returns the extent of the layer.
QgsCoordinateReferenceSystem crs
Definition qgsmaplayer.h:84
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.
static QString driverForExtension(const QString &extension)
Returns the GDAL driver name for a specified file extension.
Iterator for sequentially processing raster cells.
static const int DEFAULT_MAXIMUM_TILE_WIDTH
Default maximum tile width.
static const int DEFAULT_MAXIMUM_TILE_HEIGHT
Default maximum tile height.
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.