QGIS API Documentation 3.43.0-Master (2a27c31701b)
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 auto createOptsParam = std::make_unique<QgsProcessingParameterString>( QStringLiteral( "CREATE_OPTIONS" ), QObject::tr( "Creation options" ), QVariant(), false, true );
58 createOptsParam->setMetadata( QVariantMap( { { QStringLiteral( "widget_wrapper" ), QVariantMap( { { QStringLiteral( "widget_type" ), QStringLiteral( "rasteroptions" ) } } ) } } ) );
59 createOptsParam->setFlags( createOptsParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
60 addParameter( createOptsParam.release() );
61
62 addParameter( new QgsProcessingParameterRasterDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Output raster" ) ) );
63}
64
65QString QgsRoundRasterValuesAlgorithm::shortHelpString() const
66{
67 return QObject::tr( "This algorithm rounds the cell values of a raster dataset according to the specified number of decimals.\n "
68 "Alternatively, a negative number of decimal places may be used to round values to powers of a base n "
69 "(specified in the advanced parameter Base n). For example, with a Base value n of 10 and Decimal places of -1 "
70 "the algorithm rounds cell values to multiples of 10, -2 rounds to multiples of 100, and so on. Arbitrary base values "
71 "may be chosen, the algorithm applies the same multiplicative principle. Rounding cell values to multiples of "
72 "a base n may be used to generalize raster layers.\n"
73 "The algorithm preserves the data type of the input raster. Therefore byte/integer rasters can only be rounded "
74 "to multiples of a base n, otherwise a warning is raised and the raster gets copied as byte/integer raster" );
75}
76
77QgsRoundRasterValuesAlgorithm *QgsRoundRasterValuesAlgorithm::createInstance() const
78{
79 return new QgsRoundRasterValuesAlgorithm();
80}
81
82bool QgsRoundRasterValuesAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
83{
84 Q_UNUSED( feedback );
85 QgsRasterLayer *inputRaster = parameterAsRasterLayer( parameters, QStringLiteral( "INPUT" ), context );
86 mDecimalPrecision = parameterAsInt( parameters, QStringLiteral( "DECIMAL_PLACES" ), context );
87 mBaseN = parameterAsInt( parameters, QStringLiteral( "BASE_N" ), context );
88 mMultipleOfBaseN = pow( mBaseN, abs( mDecimalPrecision ) );
89 mScaleFactor = std::pow( 10.0, mDecimalPrecision );
90
91 if ( !inputRaster )
92 throw QgsProcessingException( invalidRasterError( parameters, QStringLiteral( "INPUT" ) ) );
93
94 mBand = parameterAsInt( parameters, QStringLiteral( "BAND" ), context );
95 if ( mBand < 1 || mBand > inputRaster->bandCount() )
96 throw QgsProcessingException( QObject::tr( "Invalid band number for BAND (%1): Valid values for input raster are 1 to %2" ).arg( mBand ).arg( inputRaster->bandCount() ) );
97
98 mRoundingDirection = parameterAsEnum( parameters, QStringLiteral( "ROUNDING_DIRECTION" ), context );
99
100 mInterface.reset( inputRaster->dataProvider()->clone() );
101 mDataType = mInterface->dataType( mBand );
102
103 switch ( mDataType )
104 {
110 mIsInteger = true;
111 if ( mDecimalPrecision > -1 )
112 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 );
113 break;
114 default:
115 mIsInteger = false;
116 break;
117 }
118
119 mInputNoDataValue = inputRaster->dataProvider()->sourceNoDataValue( mBand );
120 mExtent = inputRaster->extent();
121 mLayerWidth = inputRaster->width();
122 mLayerHeight = inputRaster->height();
123 mCrs = inputRaster->crs();
124 mNbCellsXProvider = mInterface->xSize();
125 mNbCellsYProvider = mInterface->ySize();
126 return true;
127}
128
129QVariantMap QgsRoundRasterValuesAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
130{
131 //prepare output dataset
132 const QString createOptions = parameterAsString( parameters, QStringLiteral( "CREATE_OPTIONS" ), context ).trimmed();
133 const QString outputFile = parameterAsOutputLayer( parameters, QStringLiteral( "OUTPUT" ), context );
134 const QFileInfo fi( outputFile );
135 const QString outputFormat = QgsRasterFileWriter::driverForExtension( fi.suffix() );
136 auto writer = std::make_unique<QgsRasterFileWriter>( outputFile );
137 writer->setOutputProviderKey( QStringLiteral( "gdal" ) );
138 if ( !createOptions.isEmpty() )
139 {
140 writer->setCreateOptions( createOptions.split( '|' ) );
141 }
142 writer->setOutputFormat( outputFormat );
143 std::unique_ptr<QgsRasterDataProvider> provider( writer->createOneBandRaster( mInterface->dataType( mBand ), mNbCellsXProvider, mNbCellsYProvider, mExtent, mCrs ) );
144 if ( !provider )
145 throw QgsProcessingException( QObject::tr( "Could not create raster output: %1" ).arg( outputFile ) );
146 if ( !provider->isValid() )
147 throw QgsProcessingException( QObject::tr( "Could not create raster output %1: %2" ).arg( outputFile, provider->error().message( QgsErrorMessage::Text ) ) );
148
149 //prepare output provider
150 QgsRasterDataProvider *destinationRasterProvider;
151 destinationRasterProvider = provider.get();
152 destinationRasterProvider->setEditable( true );
153 destinationRasterProvider->setNoDataValue( 1, mInputNoDataValue );
154
157 const int nbBlocksWidth = static_cast<int>( std::ceil( 1.0 * mLayerWidth / maxWidth ) );
158 const int nbBlocksHeight = static_cast<int>( std::ceil( 1.0 * mLayerHeight / maxHeight ) );
159 const int nbBlocks = nbBlocksWidth * nbBlocksHeight;
160
161 QgsRasterIterator iter( mInterface.get() );
162 iter.startRasterRead( mBand, mLayerWidth, mLayerHeight, mExtent );
163 int iterLeft = 0;
164 int iterTop = 0;
165 int iterCols = 0;
166 int iterRows = 0;
167 std::unique_ptr<QgsRasterBlock> analysisRasterBlock;
168 while ( iter.readNextRasterPart( mBand, iterCols, iterRows, analysisRasterBlock, iterLeft, iterTop ) )
169 {
170 if ( feedback )
171 feedback->setProgress( 100 * ( ( iterTop / maxHeight * nbBlocksWidth ) + iterLeft / maxWidth ) / nbBlocks );
172 if ( mIsInteger && mDecimalPrecision > -1 )
173 {
174 //nothing to round, just write raster block
175 analysisRasterBlock->setNoDataValue( mInputNoDataValue );
176 if ( !destinationRasterProvider->writeBlock( analysisRasterBlock.get(), mBand, iterLeft, iterTop ) )
177 {
178 throw QgsProcessingException( QObject::tr( "Could not write raster block: %1" ).arg( destinationRasterProvider->error().summary() ) );
179 }
180 }
181 else
182 {
183 for ( int row = 0; row < iterRows; row++ )
184 {
185 if ( feedback && feedback->isCanceled() )
186 break;
187 for ( int column = 0; column < iterCols; column++ )
188 {
189 bool isNoData = false;
190 const double val = analysisRasterBlock->valueAndNoData( row, column, isNoData );
191 if ( isNoData )
192 {
193 analysisRasterBlock->setValue( row, column, mInputNoDataValue );
194 }
195 else
196 {
197 double roundedVal = mInputNoDataValue;
198 if ( mRoundingDirection == 0 && mDecimalPrecision < 0 )
199 {
200 roundedVal = roundUpBaseN( val );
201 }
202 else if ( mRoundingDirection == 0 && mDecimalPrecision > -1 )
203 {
204 const double m = ( val < 0.0 ) ? -1.0 : 1.0;
205 roundedVal = roundUp( val, m );
206 }
207 else if ( mRoundingDirection == 1 && mDecimalPrecision < 0 )
208 {
209 roundedVal = roundNearestBaseN( val );
210 }
211 else if ( mRoundingDirection == 1 && mDecimalPrecision > -1 )
212 {
213 const double m = ( val < 0.0 ) ? -1.0 : 1.0;
214 roundedVal = roundNearest( val, m );
215 }
216 else if ( mRoundingDirection == 2 && mDecimalPrecision < 0 )
217 {
218 roundedVal = roundDownBaseN( val );
219 }
220 else
221 {
222 const double m = ( val < 0.0 ) ? -1.0 : 1.0;
223 roundedVal = roundDown( val, m );
224 }
225 //integer values get automatically cast to double when reading and back to int when writing
226 analysisRasterBlock->setValue( row, column, roundedVal );
227 }
228 }
229 }
230 if ( !destinationRasterProvider->writeBlock( analysisRasterBlock.get(), mBand, iterLeft, iterTop ) )
231 {
232 throw QgsProcessingException( QObject::tr( "Could not write raster block: %1" ).arg( destinationRasterProvider->error().summary() ) );
233 }
234 }
235 }
236 destinationRasterProvider->setEditable( false );
237
238 QVariantMap outputs;
239 outputs.insert( QStringLiteral( "OUTPUT" ), outputFile );
240 return outputs;
241}
242
243double QgsRoundRasterValuesAlgorithm::roundNearest( double value, double m )
244{
245 return ( std::round( value * m * mScaleFactor ) / mScaleFactor ) * m;
246}
247
248double QgsRoundRasterValuesAlgorithm::roundUp( double value, double m )
249{
250 return ( std::ceil( value * m * mScaleFactor ) / mScaleFactor ) * m;
251}
252
253double QgsRoundRasterValuesAlgorithm::roundDown( double value, double m )
254{
255 return ( std::floor( value * m * mScaleFactor ) / mScaleFactor ) * m;
256}
257
258double QgsRoundRasterValuesAlgorithm::roundNearestBaseN( double value )
259{
260 return static_cast<double>( mMultipleOfBaseN * round( value / mMultipleOfBaseN ) );
261}
262
263double QgsRoundRasterValuesAlgorithm::roundUpBaseN( double value )
264{
265 return static_cast<double>( mMultipleOfBaseN * ceil( value / mMultipleOfBaseN ) );
266}
267
268double QgsRoundRasterValuesAlgorithm::roundDownBaseN( double value )
269{
270 return static_cast<double>( mMultipleOfBaseN * floor( value / mMultipleOfBaseN ) );
271}
272
@ 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)
@ 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:83
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.