QGIS API Documentation 3.43.0-Master (fa3a2ef2f67)
qgsalgorithmrasterstackposition.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsrasterstackposition.cpp
3 ---------------------
4 begin : July 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 "qgsrasterprojector.h"
20#include "qgsrasterfilewriter.h"
22
24
25//
26//QgsRasterFrequencyByComparisonOperatorBase
27//
28
29QString QgsRasterStackPositionAlgorithmBase::group() const
30{
31 return QObject::tr( "Raster analysis" );
32}
33
34QString QgsRasterStackPositionAlgorithmBase::groupId() const
35{
36 return QStringLiteral( "rasteranalysis" );
37}
38
39void QgsRasterStackPositionAlgorithmBase::initAlgorithm( const QVariantMap & )
40{
41 addParameter( new QgsProcessingParameterMultipleLayers( QStringLiteral( "INPUT_RASTERS" ), QObject::tr( "Input raster layers" ), Qgis::ProcessingSourceType::Raster ) );
42
43 addParameter( new QgsProcessingParameterRasterLayer( QStringLiteral( "REFERENCE_LAYER" ), QObject::tr( "Reference layer" ) ) );
44
45 addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "IGNORE_NODATA" ), QObject::tr( "Ignore NoData values" ), false ) );
46
47 auto output_nodata_parameter = std::make_unique<QgsProcessingParameterNumber>( QStringLiteral( "OUTPUT_NODATA_VALUE" ), QObject::tr( "Output NoData value" ), Qgis::ProcessingNumberParameterType::Double, -9999, true );
48 output_nodata_parameter->setFlags( output_nodata_parameter->flags() | Qgis::ProcessingParameterFlag::Advanced );
49 addParameter( output_nodata_parameter.release() );
50
51 // backwards compatibility parameter
52 // TODO QGIS 4: remove parameter and related logic
53 auto createOptsParam = std::make_unique<QgsProcessingParameterString>( QStringLiteral( "CREATE_OPTIONS" ), QObject::tr( "Creation options" ), QVariant(), false, true );
54 createOptsParam->setMetadata( QVariantMap( { { QStringLiteral( "widget_wrapper" ), QVariantMap( { { QStringLiteral( "widget_type" ), QStringLiteral( "rasteroptions" ) } } ) } } ) );
55 createOptsParam->setFlags( createOptsParam->flags() | Qgis::ProcessingParameterFlag::Hidden );
56 addParameter( createOptsParam.release() );
57
58 auto creationOptsParam = std::make_unique<QgsProcessingParameterString>( QStringLiteral( "CREATION_OPTIONS" ), QObject::tr( "Creation options" ), QVariant(), false, true );
59 creationOptsParam->setMetadata( QVariantMap( { { QStringLiteral( "widget_wrapper" ), QVariantMap( { { QStringLiteral( "widget_type" ), QStringLiteral( "rasteroptions" ) } } ) } } ) );
60 creationOptsParam->setFlags( creationOptsParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
61 addParameter( creationOptsParam.release() );
62
63 addParameter( new QgsProcessingParameterRasterDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Output layer" ) ) );
64 addOutput( new QgsProcessingOutputString( QStringLiteral( "EXTENT" ), QObject::tr( "Extent" ) ) );
65 addOutput( new QgsProcessingOutputString( QStringLiteral( "CRS_AUTHID" ), QObject::tr( "CRS authority identifier" ) ) );
66 addOutput( new QgsProcessingOutputNumber( QStringLiteral( "WIDTH_IN_PIXELS" ), QObject::tr( "Width in pixels" ) ) );
67 addOutput( new QgsProcessingOutputNumber( QStringLiteral( "HEIGHT_IN_PIXELS" ), QObject::tr( "Height in pixels" ) ) );
68 addOutput( new QgsProcessingOutputNumber( QStringLiteral( "TOTAL_PIXEL_COUNT" ), QObject::tr( "Total pixel count" ) ) );
69}
70
71bool QgsRasterStackPositionAlgorithmBase::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
72{
73 QgsRasterLayer *referenceLayer = parameterAsRasterLayer( parameters, QStringLiteral( "REFERENCE_LAYER" ), context );
74 if ( !referenceLayer )
75 throw QgsProcessingException( invalidRasterError( parameters, QStringLiteral( "REFERENCE_LAYER" ) ) );
76
77 mIgnoreNoData = parameterAsBool( parameters, QStringLiteral( "IGNORE_NODATA" ), context );
78 mNoDataValue = parameterAsDouble( parameters, QStringLiteral( "OUTPUT_NODATA_VALUE" ), context );
79
80 mCrs = referenceLayer->crs();
81 mRasterUnitsPerPixelX = referenceLayer->rasterUnitsPerPixelX();
82 mRasterUnitsPerPixelY = referenceLayer->rasterUnitsPerPixelY();
83 mLayerWidth = referenceLayer->width();
84 mLayerHeight = referenceLayer->height();
85 mExtent = referenceLayer->extent();
86
87 const QList<QgsMapLayer *> layers = parameterAsLayerList( parameters, QStringLiteral( "INPUT_RASTERS" ), context );
88 QList<QgsRasterLayer *> rasterLayers;
89 rasterLayers.reserve( layers.count() );
90 for ( QgsMapLayer *l : layers )
91 {
92 if ( feedback->isCanceled() )
93 break; //in case some slow data sources are loaded
94
95 if ( l->type() == Qgis::LayerType::Raster )
96 {
97 QgsRasterLayer *layer = qobject_cast<QgsRasterLayer *>( l );
98 QgsRasterAnalysisUtils::RasterLogicInput input;
99 const int band = 1; //could be made dynamic
100 input.hasNoDataValue = layer->dataProvider()->sourceHasNoDataValue( band );
101 input.sourceDataProvider.reset( layer->dataProvider()->clone() );
102 input.interface = input.sourceDataProvider.get();
103 // add projector if necessary
104 if ( layer->crs() != mCrs )
105 {
106 input.projector = std::make_unique<QgsRasterProjector>();
107 input.projector->setInput( input.sourceDataProvider.get() );
108 input.projector->setCrs( layer->crs(), mCrs, context.transformContext() );
109 input.interface = input.projector.get();
110 }
111 mInputs.emplace_back( std::move( input ) );
112 }
113 }
114
115 return true;
116}
117
118QVariantMap QgsRasterStackPositionAlgorithmBase::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
119{
120 QString creationOptions = parameterAsString( parameters, QStringLiteral( "CREATION_OPTIONS" ), context ).trimmed();
121 // handle backwards compatibility parameter CREATE_OPTIONS
122 const QString optionsString = parameterAsString( parameters, QStringLiteral( "CREATE_OPTIONS" ), context );
123 if ( !optionsString.isEmpty() )
124 creationOptions = optionsString;
125
126 const QString outputFile = parameterAsOutputLayer( parameters, QStringLiteral( "OUTPUT" ), context );
127 const QFileInfo fi( outputFile );
128 const QString outputFormat = QgsRasterFileWriter::driverForExtension( fi.suffix() );
129
130 auto writer = std::make_unique<QgsRasterFileWriter>( outputFile );
131 writer->setOutputProviderKey( QStringLiteral( "gdal" ) );
132 if ( !creationOptions.isEmpty() )
133 {
134 writer->setCreationOptions( creationOptions.split( '|' ) );
135 }
136 writer->setOutputFormat( outputFormat );
137 std::unique_ptr<QgsRasterDataProvider> provider( writer->createOneBandRaster( Qgis::DataType::Int32, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
138 if ( !provider )
139 throw QgsProcessingException( QObject::tr( "Could not create raster output: %1" ).arg( outputFile ) );
140 if ( !provider->isValid() )
141 throw QgsProcessingException( QObject::tr( "Could not create raster output %1: %2" ).arg( outputFile, provider->error().message( QgsErrorMessage::Text ) ) );
142
143 provider->setNoDataValue( 1, mNoDataValue );
144 const qgssize layerSize = static_cast<qgssize>( mLayerWidth ) * static_cast<qgssize>( mLayerHeight );
145
148 const int nbBlocksWidth = static_cast<int>( std::ceil( 1.0 * mLayerWidth / maxWidth ) );
149 const int nbBlocksHeight = static_cast<int>( std::ceil( 1.0 * mLayerHeight / maxHeight ) );
150 const int nbBlocks = nbBlocksWidth * nbBlocksHeight;
151 provider->setEditable( true );
152
153 QgsRasterIterator iter( provider.get() );
154 iter.startRasterRead( 1, mLayerWidth, mLayerHeight, mExtent );
155 int iterLeft = 0;
156 int iterTop = 0;
157 int iterCols = 0;
158 int iterRows = 0;
159 QgsRectangle blockExtent;
160
161 std::unique_ptr<QgsRasterBlock> outputBlock;
162 while ( iter.readNextRasterPart( 1, iterCols, iterRows, outputBlock, iterLeft, iterTop, &blockExtent ) )
163 {
164 std::vector<std::unique_ptr<QgsRasterBlock>> inputBlocks;
165 for ( const QgsRasterAnalysisUtils::RasterLogicInput &i : mInputs )
166 {
167 if ( feedback->isCanceled() )
168 break; //in case some slow data sources are loaded
169 for ( const int band : i.bands )
170 {
171 if ( feedback->isCanceled() )
172 break; //in case some slow data sources are loaded
173 std::unique_ptr<QgsRasterBlock> b( i.interface->block( band, blockExtent, iterCols, iterRows ) );
174 inputBlocks.emplace_back( std::move( b ) );
175 }
176 }
177
178 feedback->setProgress( 100 * ( ( iterTop / maxHeight * nbBlocksWidth ) + iterLeft / maxWidth ) / nbBlocks );
179 for ( int row = 0; row < iterRows; row++ )
180 {
181 if ( feedback->isCanceled() )
182 break;
183
184 for ( int col = 0; col < iterCols; col++ )
185 {
186 bool noDataInStack = false;
187
188 if ( !inputBlocks.empty() )
189 {
190 const int position = findPosition( inputBlocks, row, col, noDataInStack );
191
192 if ( position == -1 || ( noDataInStack && !mIgnoreNoData ) )
193 {
194 //output cell will always be NoData if NoData occurs the current raster cell
195 //of the input blocks and NoData is not ignored
196 //this saves unnecessary iterations on the cellValueStack
197 outputBlock->setValue( row, col, mNoDataValue );
198 }
199 else
200 {
201 outputBlock->setValue( row, col, position );
202 }
203 }
204 else
205 {
206 outputBlock->setValue( row, col, mNoDataValue );
207 }
208 }
209 }
210 if ( !provider->writeBlock( outputBlock.get(), 1, iterLeft, iterTop ) )
211 {
212 throw QgsProcessingException( QObject::tr( "Could not write raster block: %1" ).arg( provider->error().summary() ) );
213 }
214 }
215 provider->setEditable( false );
216
217 QVariantMap outputs;
218 outputs.insert( QStringLiteral( "EXTENT" ), mExtent.toString() );
219 outputs.insert( QStringLiteral( "CRS_AUTHID" ), mCrs.authid() );
220 outputs.insert( QStringLiteral( "WIDTH_IN_PIXELS" ), mLayerWidth );
221 outputs.insert( QStringLiteral( "HEIGHT_IN_PIXELS" ), mLayerHeight );
222 outputs.insert( QStringLiteral( "TOTAL_PIXEL_COUNT" ), layerSize );
223 outputs.insert( QStringLiteral( "OUTPUT" ), outputFile );
224
225 return outputs;
226}
227
228//
229// QgsRasterStackLowestPositionAlgorithm
230//
231QString QgsRasterStackLowestPositionAlgorithm::displayName() const
232{
233 return QObject::tr( "Lowest position in raster stack" );
234}
235
236QString QgsRasterStackLowestPositionAlgorithm::name() const
237{
238 return QStringLiteral( "lowestpositioninrasterstack" );
239}
240
241QStringList QgsRasterStackLowestPositionAlgorithm::tags() const
242{
243 return QObject::tr( "cell,lowest,position,pixel,stack" ).split( ',' );
244}
245
246QString QgsRasterStackLowestPositionAlgorithm::shortHelpString() const
247{
248 return QObject::tr( "The lowest position algorithm evaluates on a cell-by-cell basis the position "
249 "of the raster with the lowest value in a stack of rasters. Position counts start "
250 "with 1 and range to the total number of input rasters. The order of the input "
251 "rasters is relevant for the algorithm. If multiple rasters feature the lowest value, "
252 "the first raster will be used for the position value.\n "
253 "If multiband rasters are used in the data raster stack, the algorithm will always "
254 "perform the analysis on the first band of the rasters - use GDAL to use other bands in the analysis. "
255 "Any NoData cells in the raster layer stack will result in a NoData cell "
256 "in the output raster unless the \"ignore NoData\" parameter is checked. "
257 "The output NoData value can be set manually. The output rasters extent and resolution "
258 "is defined by a reference raster layer and is always of int32 type." );
259}
260
261QgsRasterStackLowestPositionAlgorithm *QgsRasterStackLowestPositionAlgorithm::createInstance() const
262{
263 return new QgsRasterStackLowestPositionAlgorithm();
264}
265
266int QgsRasterStackLowestPositionAlgorithm::findPosition( std::vector<std::unique_ptr<QgsRasterBlock>> &inputBlocks, int &row, int &col, bool &noDataInRasterBlockStack )
267{
268 int lowestPosition = 0;
269
270 //auxiliary variables
271 const int inputBlocksCount = inputBlocks.size();
272 int currentPosition = 0;
273 int noDataCount = 0;
274 double firstValue = mNoDataValue;
275 bool firstValueIsNoData = true;
276
277 while ( firstValueIsNoData && ( currentPosition < inputBlocksCount ) )
278 {
279 //check if all blocks are nodata/invalid
280 std::unique_ptr<QgsRasterBlock> &firstBlock = inputBlocks.at( currentPosition );
281 firstValue = firstBlock->valueAndNoData( row, col, firstValueIsNoData );
282
283 if ( !firstBlock->isValid() || firstValueIsNoData )
284 {
285 noDataInRasterBlockStack = true;
286 noDataCount++;
287 }
288 else
289 {
290 lowestPosition = currentPosition;
291 }
292 currentPosition++;
293 }
294
295 if ( noDataCount == inputBlocksCount )
296 {
297 noDataInRasterBlockStack = true;
298 return -1; //all blocks are NoData
299 }
300 else
301 {
302 //scan for the lowest value
303 while ( currentPosition < inputBlocksCount )
304 {
305 std::unique_ptr<QgsRasterBlock> &currentBlock = inputBlocks.at( currentPosition );
306
307 bool currentValueIsNoData = false;
308 const double currentValue = currentBlock->valueAndNoData( row, col, currentValueIsNoData );
309
310 if ( !currentBlock->isValid() || currentValueIsNoData )
311 {
312 noDataInRasterBlockStack = true;
313 noDataCount++;
314 }
315 else
316 {
317 if ( currentValue < firstValue )
318 {
319 firstValue = currentValue;
320 lowestPosition = currentPosition;
321 }
322 }
323 currentPosition++;
324 }
325 }
326 //the ArcGIS implementation uses 1 for first position value instead of 0 as in standard c++
327 return ++lowestPosition; //therefore ++
328}
329
330//
331// QgsRasterStackHighestPositionAlgorithmAlgorithm
332//
333
334QString QgsRasterStackHighestPositionAlgorithm::displayName() const
335{
336 return QObject::tr( "Highest position in raster stack" );
337}
338
339QString QgsRasterStackHighestPositionAlgorithm::name() const
340{
341 return QStringLiteral( "highestpositioninrasterstack" );
342}
343
344QStringList QgsRasterStackHighestPositionAlgorithm::tags() const
345{
346 return QObject::tr( "cell,highest,position,pixel,stack" ).split( ',' );
347}
348
349QString QgsRasterStackHighestPositionAlgorithm::shortHelpString() const
350{
351 return QObject::tr( "The highest position algorithm evaluates on a cell-by-cell basis the position "
352 "of the raster with the highest value in a stack of rasters. Position counts start "
353 "with 1 and range to the total number of input rasters. The order of the input "
354 "rasters is relevant for the algorithm. If multiple rasters feature the highest value, "
355 "the first raster will be used for the position value.\n "
356 "If multiband rasters are used in the data raster stack, the algorithm will always "
357 "perform the analysis on the first band of the rasters - use GDAL to use other bands in the analysis. "
358 "Any NoData cells in the raster layer stack will result in a NoData cell "
359 "in the output raster unless the \"ignore NoData\" parameter is checked. "
360 "The output NoData value can be set manually. The output rasters extent and resolution "
361 "is defined by a reference raster layer and is always of int32 type." );
362}
363
364QgsRasterStackHighestPositionAlgorithm *QgsRasterStackHighestPositionAlgorithm::createInstance() const
365{
366 return new QgsRasterStackHighestPositionAlgorithm();
367}
368
369int QgsRasterStackHighestPositionAlgorithm::findPosition( std::vector<std::unique_ptr<QgsRasterBlock>> &inputBlocks, int &row, int &col, bool &noDataInRasterBlockStack )
370{
371 int highestPosition = 0;
372
373 //auxiliary variables
374 const int inputBlocksCount = inputBlocks.size();
375 int currentPosition = 0;
376 int noDataCount = 0;
377 double firstValue = mNoDataValue;
378 bool firstValueIsNoData = true;
379
380 while ( firstValueIsNoData && ( currentPosition < inputBlocksCount ) )
381 {
382 //check if all blocks are nodata/invalid
383 std::unique_ptr<QgsRasterBlock> &firstBlock = inputBlocks.at( currentPosition );
384 firstValue = firstBlock->valueAndNoData( row, col, firstValueIsNoData );
385
386 if ( !firstBlock->isValid() || firstValueIsNoData )
387 {
388 noDataInRasterBlockStack = true;
389 noDataCount++;
390 }
391 else
392 {
393 highestPosition = currentPosition;
394 }
395
396 currentPosition++;
397 }
398
399 if ( noDataCount == inputBlocksCount )
400 {
401 noDataInRasterBlockStack = true;
402 return -1; //all blocks are NoData
403 }
404 else
405 {
406 //scan for the lowest value
407 while ( currentPosition < inputBlocksCount )
408 {
409 std::unique_ptr<QgsRasterBlock> &currentBlock = inputBlocks.at( currentPosition );
410
411 bool currentValueIsNoData = false;
412 const double currentValue = currentBlock->valueAndNoData( row, col, currentValueIsNoData );
413
414 if ( !currentBlock->isValid() || currentValueIsNoData )
415 {
416 noDataInRasterBlockStack = true;
417 noDataCount++;
418 }
419 else
420 {
421 if ( currentValue > firstValue )
422 {
423 firstValue = currentValue;
424 highestPosition = currentPosition;
425 }
426 }
427 currentPosition++;
428 }
429 }
430 //the ArcGIS implementation uses 1 for first position value instead of 0 as in standard c++
431 return ++highestPosition; //therefore ++
432}
433
@ Int32
Thirty two bit signed integer (qint32)
@ Raster
Raster layer.
@ 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.
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
Base class for all map layer types.
Definition qgsmaplayer.h:77
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.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context.
Custom exception class for processing related exceptions.
Base class for providing feedback from a processing algorithm.
A numeric output for processing algorithms.
A string output for processing algorithms.
A boolean parameter for processing algorithms.
A parameter for processing algorithms which accepts multiple map layers.
A raster layer destination parameter, for specifying the destination path for a raster layer created ...
A raster layer parameter for processing algorithms.
QgsRasterDataProvider * clone() const override=0
Clone itself, create deep copy.
virtual bool sourceHasNoDataValue(int bandNo) const
Returns true if source band has no data value.
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.
double rasterUnitsPerPixelX() const
Returns the number of raster units per each raster pixel in X axis.
QgsRasterDataProvider * dataProvider() override
Returns the source data provider.
double rasterUnitsPerPixelY() const
Returns the number of raster units per each raster pixel in Y axis.
int width() const
Returns the width of the (unclipped) raster.
A rectangle specified with double values.
unsigned long long qgssize
Qgssize is used instead of size_t, because size_t is stdlib type, unknown by SIP, and it would be har...
Definition qgis.h:6791