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