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