Branch data Line data Source code
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 : :
18 : : #include "qgsalgorithmroundrastervalues.h"
19 : : #include "qgsrasterfilewriter.h"
20 : :
21 : : ///@cond PRIVATE
22 : :
23 : 0 : QString QgsRoundRasterValuesAlgorithm::name() const
24 : : {
25 : 0 : return QStringLiteral( "roundrastervalues" );
26 : : }
27 : :
28 : 0 : QString QgsRoundRasterValuesAlgorithm::displayName() const
29 : : {
30 : 0 : return QObject::tr( "Round raster" );
31 : : }
32 : :
33 : 0 : QStringList QgsRoundRasterValuesAlgorithm::tags() const
34 : : {
35 : 0 : return QObject::tr( "data,cells,round,truncate" ).split( ',' );
36 : 0 : }
37 : :
38 : 0 : QString QgsRoundRasterValuesAlgorithm::group() const
39 : : {
40 : 0 : return QObject::tr( "Raster analysis" );
41 : : }
42 : :
43 : 0 : QString QgsRoundRasterValuesAlgorithm::groupId() const
44 : : {
45 : 0 : return QStringLiteral( "rasteranalysis" );
46 : : }
47 : :
48 : 0 : void QgsRoundRasterValuesAlgorithm::initAlgorithm( const QVariantMap & )
49 : : {
50 : 0 : addParameter( new QgsProcessingParameterRasterLayer( QStringLiteral( "INPUT" ), QStringLiteral( "Input raster" ) ) );
51 : 0 : addParameter( new QgsProcessingParameterBand( QStringLiteral( "BAND" ), QObject::tr( "Band number" ), 1, QStringLiteral( "INPUT" ) ) );
52 : 0 : 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 : 0 : addParameter( new QgsProcessingParameterNumber( QStringLiteral( "DECIMAL_PLACES" ), QObject::tr( "Number of decimals places" ), QgsProcessingParameterNumber::Integer, 2 ) );
54 : 0 : addParameter( new QgsProcessingParameterRasterDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Output raster" ) ) );
55 : 0 : std::unique_ptr< QgsProcessingParameterDefinition > baseParameter = std::make_unique< QgsProcessingParameterNumber >( QStringLiteral( "BASE_N" ), QObject::tr( "Base n for rounding to multiples of n" ), QgsProcessingParameterNumber::Integer, 10, true, 1 );
56 : 0 : baseParameter->setFlags( QgsProcessingParameterDefinition::FlagAdvanced );
57 : 0 : addParameter( baseParameter.release() );
58 : 0 : }
59 : :
60 : 0 : QString QgsRoundRasterValuesAlgorithm::shortHelpString() const
61 : : {
62 : 0 : return QObject::tr( "This algorithm rounds the cell values of a raster dataset according to the specified number of decimals.\n "
63 : : "Alternatively, a negative number of decimal places may be used to round values to powers of a base n "
64 : : "(specified in the advanced parameter Base n). For example, with a Base value n of 10 and Decimal places of -1 "
65 : : "the algorithm rounds cell values to multiples of 10, -2 rounds to multiples of 100, and so on. Arbitrary base values "
66 : : "may be chosen, the algorithm applies the same multiplicative principle. Rounding cell values to multiples of "
67 : : "a base n may be used to generalize raster layers.\n"
68 : : "The algorithm preserves the data type of the input raster. Therefore byte/integer rasters can only be rounded "
69 : : "to multiples of a base n, otherwise a warning is raised and the raster gets copied as byte/integer raster" );
70 : : }
71 : :
72 : 0 : QgsRoundRasterValuesAlgorithm *QgsRoundRasterValuesAlgorithm::createInstance() const
73 : : {
74 : 0 : return new QgsRoundRasterValuesAlgorithm();
75 : 0 : }
76 : :
77 : 0 : bool QgsRoundRasterValuesAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
78 : : {
79 : : Q_UNUSED( feedback );
80 : 0 : QgsRasterLayer *inputRaster = parameterAsRasterLayer( parameters, QStringLiteral( "INPUT" ), context );
81 : 0 : mDecimalPrecision = parameterAsInt( parameters, QStringLiteral( "DECIMAL_PLACES" ), context );
82 : 0 : mBaseN = parameterAsInt( parameters, QStringLiteral( "BASE_N" ), context );
83 : 0 : mMultipleOfBaseN = pow( mBaseN, abs( mDecimalPrecision ) );
84 : 0 : mScaleFactor = std::pow( 10.0, mDecimalPrecision );
85 : :
86 : 0 : if ( !inputRaster )
87 : 0 : throw QgsProcessingException( invalidRasterError( parameters, QStringLiteral( "INPUT" ) ) );
88 : :
89 : 0 : mBand = parameterAsInt( parameters, QStringLiteral( "BAND" ), context );
90 : 0 : if ( mBand < 1 || mBand > inputRaster->bandCount() )
91 : 0 : throw QgsProcessingException( QObject::tr( "Invalid band number for BAND (%1): Valid values for input raster are 1 to %2" ).arg( mBand ).arg( inputRaster->bandCount() ) );
92 : :
93 : 0 : mRoundingDirection = parameterAsEnum( parameters, QStringLiteral( "ROUNDING_DIRECTION" ), context );
94 : :
95 : 0 : mInterface.reset( inputRaster->dataProvider()->clone() );
96 : 0 : mDataType = mInterface->dataType( mBand );
97 : :
98 : 0 : switch ( mDataType )
99 : : {
100 : : case Qgis::Byte:
101 : : case Qgis::Int16:
102 : : case Qgis::UInt16:
103 : : case Qgis::Int32:
104 : : case Qgis::UInt32:
105 : 0 : mIsInteger = true;
106 : 0 : if ( mDecimalPrecision > -1 )
107 : 0 : 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 );
108 : 0 : break;
109 : : default:
110 : 0 : mIsInteger = false;
111 : 0 : break;
112 : : }
113 : :
114 : 0 : mInputNoDataValue = inputRaster->dataProvider()->sourceNoDataValue( mBand );
115 : 0 : mExtent = inputRaster->extent();
116 : 0 : mLayerWidth = inputRaster->width();
117 : 0 : mLayerHeight = inputRaster->height();
118 : 0 : mCrs = inputRaster->crs();
119 : 0 : mNbCellsXProvider = mInterface->xSize();
120 : 0 : mNbCellsYProvider = mInterface->ySize();
121 : 0 : return true;
122 : 0 : }
123 : :
124 : 0 : QVariantMap QgsRoundRasterValuesAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
125 : : {
126 : : //prepare output dataset
127 : 0 : const QString outputFile = parameterAsOutputLayer( parameters, QStringLiteral( "OUTPUT" ), context );
128 : 0 : QFileInfo fi( outputFile );
129 : 0 : const QString outputFormat = QgsRasterFileWriter::driverForExtension( fi.suffix() );
130 : 0 : std::unique_ptr< QgsRasterFileWriter > writer = std::make_unique< QgsRasterFileWriter >( outputFile );
131 : 0 : writer->setOutputProviderKey( QStringLiteral( "gdal" ) );
132 : 0 : writer->setOutputFormat( outputFormat );
133 : 0 : std::unique_ptr<QgsRasterDataProvider > provider( writer->createOneBandRaster( mInterface->dataType( mBand ), mNbCellsXProvider, mNbCellsYProvider, mExtent, mCrs ) );
134 : 0 : if ( !provider )
135 : 0 : throw QgsProcessingException( QObject::tr( "Could not create raster output: %1" ).arg( outputFile ) );
136 : 0 : if ( !provider->isValid() )
137 : 0 : throw QgsProcessingException( QObject::tr( "Could not create raster output %1: %2" ).arg( outputFile, provider->error().message( QgsErrorMessage::Text ) ) );
138 : :
139 : : //prepare output provider
140 : : QgsRasterDataProvider *destinationRasterProvider;
141 : 0 : destinationRasterProvider = provider.get();
142 : 0 : destinationRasterProvider->setEditable( true );
143 : 0 : destinationRasterProvider->setNoDataValue( 1, mInputNoDataValue );
144 : :
145 : 0 : int maxWidth = QgsRasterIterator::DEFAULT_MAXIMUM_TILE_WIDTH;
146 : 0 : int maxHeight = QgsRasterIterator::DEFAULT_MAXIMUM_TILE_HEIGHT;
147 : 0 : int nbBlocksWidth = static_cast< int >( std::ceil( 1.0 * mLayerWidth / maxWidth ) );
148 : 0 : int nbBlocksHeight = static_cast< int >( std::ceil( 1.0 * mLayerHeight / maxHeight ) );
149 : 0 : int nbBlocks = nbBlocksWidth * nbBlocksHeight;
150 : :
151 : 0 : QgsRasterIterator iter( mInterface.get() );
152 : 0 : iter.startRasterRead( mBand, mLayerWidth, mLayerHeight, mExtent );
153 : 0 : int iterLeft = 0;
154 : 0 : int iterTop = 0;
155 : 0 : int iterCols = 0;
156 : 0 : int iterRows = 0;
157 : 0 : std::unique_ptr< QgsRasterBlock > analysisRasterBlock;
158 : 0 : while ( iter.readNextRasterPart( mBand, iterCols, iterRows, analysisRasterBlock, iterLeft, iterTop ) )
159 : : {
160 : 0 : if ( feedback )
161 : 0 : feedback->setProgress( 100 * ( ( iterTop / maxHeight * nbBlocksWidth ) + iterLeft / maxWidth ) / nbBlocks );
162 : 0 : if ( mIsInteger && mDecimalPrecision > -1 )
163 : : {
164 : : //nothing to round, just write raster block
165 : 0 : analysisRasterBlock->setNoDataValue( mInputNoDataValue );
166 : 0 : destinationRasterProvider->writeBlock( analysisRasterBlock.get(), mBand, iterLeft, iterTop );
167 : 0 : }
168 : : else
169 : : {
170 : 0 : for ( int row = 0; row < iterRows; row++ )
171 : : {
172 : 0 : if ( feedback && feedback->isCanceled() )
173 : 0 : break;
174 : 0 : for ( int column = 0; column < iterCols; column++ )
175 : : {
176 : 0 : bool isNoData = false;
177 : 0 : double val = analysisRasterBlock->valueAndNoData( row, column, isNoData );
178 : 0 : if ( isNoData )
179 : : {
180 : 0 : analysisRasterBlock->setValue( row, column, mInputNoDataValue );
181 : 0 : }
182 : : else
183 : : {
184 : 0 : double roundedVal = mInputNoDataValue;
185 : 0 : if ( mRoundingDirection == 0 && mDecimalPrecision < 0 )
186 : : {
187 : 0 : roundedVal = roundUpBaseN( val );
188 : 0 : }
189 : 0 : else if ( mRoundingDirection == 0 && mDecimalPrecision > -1 )
190 : : {
191 : 0 : double m = ( val < 0.0 ) ? -1.0 : 1.0;
192 : 0 : roundedVal = roundUp( val, m );
193 : 0 : }
194 : 0 : else if ( mRoundingDirection == 1 && mDecimalPrecision < 0 )
195 : : {
196 : 0 : roundedVal = roundNearestBaseN( val );
197 : 0 : }
198 : 0 : else if ( mRoundingDirection == 1 && mDecimalPrecision > -1 )
199 : : {
200 : 0 : double m = ( val < 0.0 ) ? -1.0 : 1.0;
201 : 0 : roundedVal = roundNearest( val, m );
202 : 0 : }
203 : 0 : else if ( mRoundingDirection == 2 && mDecimalPrecision < 0 )
204 : : {
205 : 0 : roundedVal = roundDownBaseN( val );
206 : 0 : }
207 : : else
208 : : {
209 : 0 : double m = ( val < 0.0 ) ? -1.0 : 1.0;
210 : 0 : roundedVal = roundDown( val, m );
211 : : }
212 : : //integer values get automatically cast to double when reading and back to int when writing
213 : 0 : analysisRasterBlock->setValue( row, column, roundedVal );
214 : : }
215 : 0 : }
216 : 0 : }
217 : 0 : destinationRasterProvider->writeBlock( analysisRasterBlock.get(), mBand, iterLeft, iterTop );
218 : : }
219 : : }
220 : 0 : destinationRasterProvider->setEditable( false );
221 : :
222 : 0 : QVariantMap outputs;
223 : 0 : outputs.insert( QStringLiteral( "OUTPUT" ), outputFile );
224 : 0 : return outputs;
225 : 0 : }
226 : :
227 : 0 : double QgsRoundRasterValuesAlgorithm::roundNearest( double value, double m )
228 : : {
229 : 0 : return ( std::round( value * m * mScaleFactor ) / mScaleFactor ) * m;
230 : : }
231 : :
232 : 0 : double QgsRoundRasterValuesAlgorithm::roundUp( double value, double m )
233 : : {
234 : 0 : return ( std::ceil( value * m * mScaleFactor ) / mScaleFactor ) * m;
235 : : }
236 : :
237 : 0 : double QgsRoundRasterValuesAlgorithm::roundDown( double value, double m )
238 : : {
239 : 0 : return ( std::floor( value * m * mScaleFactor ) / mScaleFactor ) * m;
240 : : }
241 : :
242 : :
243 : 0 : double QgsRoundRasterValuesAlgorithm::roundNearestBaseN( double value )
244 : : {
245 : 0 : return static_cast<double>( mMultipleOfBaseN * round( value / mMultipleOfBaseN ) );
246 : : }
247 : :
248 : 0 : double QgsRoundRasterValuesAlgorithm::roundUpBaseN( double value )
249 : : {
250 : 0 : return static_cast<double>( mMultipleOfBaseN * ceil( value / mMultipleOfBaseN ) );
251 : : }
252 : :
253 : 0 : double QgsRoundRasterValuesAlgorithm::roundDownBaseN( double value )
254 : : {
255 : 0 : return static_cast<double>( mMultipleOfBaseN * floor( value / mMultipleOfBaseN ) );
256 : : }
257 : :
258 : :
259 : :
260 : :
261 : : ///@endcond
|