QGIS API Documentation 3.99.0-Master (357b655ed83)
Loading...
Searching...
No Matches
qgsalgorithmrasterrank.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsalgorithmrasterrank.cpp
3 ---------------------
4 begin : February 2024
5 copyright : (C) 2024 by Mathieu Pellerin
6 email : mathieu at opengis dot ch
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
20#include <memory>
21
22#include "qgsrasterfilewriter.h"
23#include "qgsrasterprojector.h"
24
25#include <QString>
26
27using namespace Qt::StringLiterals;
28
30
31QString QgsRasterRankAlgorithm::name() const
32{
33 return u"rasterrank"_s;
34}
35
36QString QgsRasterRankAlgorithm::displayName() const
37{
38 return QObject::tr( "Raster rank" );
39}
40
41QStringList QgsRasterRankAlgorithm::tags() const
42{
43 return QObject::tr( "raster,rank" ).split( ',' );
44}
45
46QString QgsRasterRankAlgorithm::group() const
47{
48 return QObject::tr( "Raster analysis" );
49}
50
51QString QgsRasterRankAlgorithm::groupId() const
52{
53 return u"rasteranalysis"_s;
54}
55
56QString QgsRasterRankAlgorithm::shortHelpString() const
57{
58 return QObject::tr( "This algorithm performs a cell-by-cell analysis in which output values match the rank of a "
59 "sorted list of overlapping cell values from input layers. The output raster "
60 "will be multi-band if multiple ranks are provided.\n"
61 "If multiband rasters are used in the data raster stack, the algorithm will always "
62 "perform the analysis on the first band of the rasters." );
63}
64
65QString QgsRasterRankAlgorithm::shortDescription() const
66{
67 return QObject::tr( "Performs a cell-by-cell analysis in which output values match the rank of a "
68 "sorted list of overlapping cell values from input layers." );
69}
70
71QgsRasterRankAlgorithm *QgsRasterRankAlgorithm::createInstance() const
72{
73 return new QgsRasterRankAlgorithm();
74}
75
76void QgsRasterRankAlgorithm::initAlgorithm( const QVariantMap & )
77{
78 addParameter( new QgsProcessingParameterMultipleLayers( u"INPUT_RASTERS"_s, QObject::tr( "Input raster layers" ), Qgis::ProcessingSourceType::Raster ) );
79 auto ranksParameter = std::make_unique<QgsProcessingParameterString>( u"RANKS"_s, QObject::tr( "Rank(s) (separate multiple ranks using commas)" ), 1 );
80 ranksParameter->setHelp( QObject::tr( "A rank value must be numerical, with multiple ranks separated by commas. The rank will be used to "
81 "generate output values from sorted lists of input layers’ cell values. A rank value of 1 will pick "
82 "the first value from a given sorted input layers’ cell values list (i.e. the minimum value). "
83 "Negative rank values are supported, and will behave like a negative index. A rank value of -2 will "
84 "pick the second to last value in sorted input values lists, while a rank value of -1 will pick the "
85 "last value (i.e. the maximum value)." ) );
86 addParameter( ranksParameter.release() );
87 addParameter( new QgsProcessingParameterEnum( u"NODATA_HANDLING"_s, QObject::tr( "NoData value handling" ), QStringList() << QObject::tr( "Exclude NoData from values lists" ) << QObject::tr( "Presence of NoData in a values list results in NoData output cell" ), false, 0 ) );
88
89 auto extentParam = std::make_unique<QgsProcessingParameterExtent>( u"EXTENT"_s, QObject::tr( "Output extent" ), QVariant(), true );
90 extentParam->setHelp( QObject::tr( "Extent of the output layer. If not specified, the extent will be the overall extent of all input layers" ) );
91 extentParam->setFlags( extentParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
92 addParameter( extentParam.release() );
93 auto cellSizeParam = std::make_unique<QgsProcessingParameterNumber>( u"CELL_SIZE"_s, QObject::tr( "Output cell size" ), Qgis::ProcessingNumberParameterType::Double, QVariant(), true, 0.0 );
94 cellSizeParam->setHelp( QObject::tr( "Cell size of the output layer. If not specified, the smallest cell size from the input layers will be used" ) );
95 cellSizeParam->setFlags( cellSizeParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
96 addParameter( cellSizeParam.release() );
97 auto crsParam = std::make_unique<QgsProcessingParameterCrs>( u"CRS"_s, QObject::tr( "Output CRS" ), QVariant(), true );
98 crsParam->setHelp( QObject::tr( "CRS of the output layer. If not specified, the CRS of the first input layer will be used" ) );
99 crsParam->setFlags( crsParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
100 addParameter( crsParam.release() );
101
102 addParameter( new QgsProcessingParameterRasterDestination( u"OUTPUT"_s, QObject::tr( "Ranked" ) ) );
103}
104
105bool QgsRasterRankAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
106{
107 const QStringList rankStrings = parameterAsString( parameters, u"RANKS"_s, context ).split( ","_L1 );
108 for ( const QString &rankString : rankStrings )
109 {
110 bool ok = false;
111 const int rank = rankString.toInt( &ok );
112 if ( ok && rank != 0 )
113 {
114 mRanks << rank;
115 }
116 else
117 {
118 throw QgsProcessingException( QObject::tr( "Rank values must be integers (found \"%1\")" ).arg( rankString ) );
119 }
120 }
121
122 if ( mRanks.isEmpty() )
123 {
124 throw QgsProcessingException( QObject::tr( "No valid non-zero rank value(s) provided" ) );
125 return false;
126 }
127
128 const QList<QgsMapLayer *> layers = parameterAsLayerList( parameters, u"INPUT_RASTERS"_s, context );
129 for ( const QgsMapLayer *layer : std::as_const( layers ) )
130 {
131 if ( !qobject_cast<const QgsRasterLayer *>( layer ) || !layer->dataProvider() )
132 continue;
133
134 std::unique_ptr<QgsMapLayer> clonedLayer;
135 clonedLayer.reset( layer->clone() );
136 clonedLayer->moveToThread( nullptr );
137 mLayers.push_back( std::move( clonedLayer ) );
138 }
139
140 if ( mLayers.empty() )
141 {
142 throw QgsProcessingException( QObject::tr( "At least one raster input layer must be specified" ) );
143 return false;
144 }
145
146 return true;
147}
148
149
150QVariantMap QgsRasterRankAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
151{
152 QList<QgsMapLayer *> layers; // Needed for QgsProcessingUtils::combineLayerExtents
153 for ( auto &layer : mLayers )
154 {
155 layer->moveToThread( QThread::currentThread() );
156 layers << layer.get();
157 }
158
160 if ( parameters.value( u"CRS"_s ).isValid() )
161 {
162 outputCrs = parameterAsCrs( parameters, u"CRS"_s, context );
163 }
164 else
165 {
166 outputCrs = mLayers[0]->crs();
167 }
168
169
170 QgsRasterLayer *templateRasterLayer = qobject_cast<QgsRasterLayer *>( mLayers[0].get() );
171 const Qgis::DataType outputDataType = templateRasterLayer->dataProvider()->dataType( 1 );
172 double outputNoData = 0.0;
173 if ( templateRasterLayer->dataProvider()->sourceHasNoDataValue( 1 ) )
174 {
175 outputNoData = templateRasterLayer->dataProvider()->sourceNoDataValue( 1 );
176 }
177 else
178 {
179 outputNoData = -FLT_MAX;
180 }
181 const bool outputNoDataOverride = parameterAsInt( parameters, u"NODATA_HANDLING"_s, context ) == 1;
182
183 QgsRectangle outputExtent;
184 if ( parameters.value( u"EXTENT"_s ).isValid() )
185 {
186 outputExtent = parameterAsExtent( parameters, u"EXTENT"_s, context, outputCrs );
187 }
188 else
189 {
190 outputExtent = QgsProcessingUtils::combineLayerExtents( layers, outputCrs, context );
191 }
192
193 double minCellSizeX = 1e9;
194 double minCellSizeY = 1e9;
195 for ( auto &layer : mLayers )
196 {
197 QgsRasterLayer *rasterLayer = qobject_cast<QgsRasterLayer *>( layer.get() );
198
199 QgsRectangle extent = rasterLayer->extent();
200 if ( rasterLayer->crs() != outputCrs )
201 {
202 QgsCoordinateTransform ct( rasterLayer->crs(), outputCrs, context.transformContext() );
203 extent = ct.transformBoundingBox( extent );
204 }
205
206 const int width = rasterLayer->width();
207 const int height = rasterLayer->height();
208 if ( width <= 0 || height <= 0 )
209 throw QgsProcessingException( QObject::tr( "%1: raster is empty, cannot proceed" ).arg( rasterLayer->name() ) );
210
211 minCellSizeX = std::min( minCellSizeX, ( extent.xMaximum() - extent.xMinimum() ) / width );
212 minCellSizeY = std::min( minCellSizeY, ( extent.yMaximum() - extent.yMinimum() ) / height );
213 }
214
215 double outputCellSizeX = parameterAsDouble( parameters, u"CELL_SIZE"_s, context );
216 double outputCellSizeY = outputCellSizeX;
217 if ( outputCellSizeX == 0 )
218 {
219 outputCellSizeX = minCellSizeX;
220 outputCellSizeY = minCellSizeY;
221 }
222
223 qgssize cols = static_cast<qgssize>( std::round( ( outputExtent.xMaximum() - outputExtent.xMinimum() ) / outputCellSizeX ) );
224 qgssize rows = static_cast<qgssize>( std::round( ( outputExtent.yMaximum() - outputExtent.yMinimum() ) / outputCellSizeY ) );
225
226 const QString outputFile = parameterAsOutputLayer( parameters, u"OUTPUT"_s, context );
227 const QString outputFormat = parameterAsOutputRasterFormat( parameters, u"OUTPUT"_s, context );
228
229 auto writer = std::make_unique<QgsRasterFileWriter>( outputFile );
230 writer->setOutputFormat( outputFormat );
231 std::unique_ptr<QgsRasterDataProvider> provider( writer->createMultiBandRaster( outputDataType, cols, rows, outputExtent, outputCrs, mRanks.size() ) );
232 if ( !provider )
233 throw QgsProcessingException( QObject::tr( "Could not create raster output: %1" ).arg( outputFile ) );
234 if ( !provider->isValid() )
235 throw QgsProcessingException( QObject::tr( "Could not create raster output %1: %2" ).arg( outputFile, provider->error().message( QgsErrorMessage::Text ) ) );
236 provider->setNoDataValue( 1, outputNoData );
237
238 std::map<QString, std::unique_ptr<QgsRasterInterface>> newProjectorInterfaces;
239 std::map<QString, QgsRasterInterface *> inputInterfaces;
240 std::map<QString, std::unique_ptr<QgsRasterBlock>> inputBlocks;
241 std::vector<std::unique_ptr<QgsRasterBlock>> outputBlocks;
242 outputBlocks.resize( mRanks.size() );
243 for ( auto &layer : mLayers )
244 {
245 QgsRasterLayer *rasterLayer = qobject_cast<QgsRasterLayer *>( layer.get() );
246 if ( rasterLayer->crs() != outputCrs )
247 {
248 QgsRasterProjector *projector = new QgsRasterProjector();
249 projector->setCrs( rasterLayer->crs(), outputCrs, context.transformContext() );
250 projector->setInput( rasterLayer->dataProvider() );
252 newProjectorInterfaces[rasterLayer->id()].reset( projector );
253 inputInterfaces[rasterLayer->id()] = projector;
254 }
255 else
256 {
257 inputInterfaces[rasterLayer->id()] = rasterLayer->dataProvider();
258 }
259 }
260
261 QgsRasterIterator rasterIterator( inputInterfaces[mLayers.at( 0 )->id()] );
262 rasterIterator.startRasterRead( 1, cols, rows, outputExtent );
263 int blockCount = static_cast<int>( rasterIterator.blockCount() );
264
265 const bool hasReportsDuringClose = provider->hasReportsDuringClose();
266 const double maxProgressDuringBlockWriting = hasReportsDuringClose ? 50.0 : 100.0;
267
268 const double step = blockCount > 0 ? maxProgressDuringBlockWriting / blockCount : 0;
269 std::vector<double> inputValues;
270 inputValues.resize( mLayers.size() );
271 for ( int currentBlock = 0; currentBlock < blockCount; currentBlock++ )
272 {
273 if ( feedback->isCanceled() )
274 {
275 break;
276 }
277 feedback->setProgress( currentBlock * step );
278
279 int iterLeft = 0;
280 int iterTop = 0;
281 int iterCols = 0;
282 int iterRows = 0;
283 QgsRectangle blockExtent;
284 rasterIterator.next( 1, iterCols, iterRows, iterLeft, iterTop, blockExtent );
285
286 for ( const auto &inputInterface : inputInterfaces )
287 {
288 inputBlocks[inputInterface.first].reset( inputInterface.second->block( 1, blockExtent, iterCols, iterRows ) );
289 }
290
291 for ( int i = 0; i < mRanks.size(); i++ )
292 {
293 outputBlocks[i] = std::make_unique<QgsRasterBlock>( outputDataType, iterCols, iterRows );
294 outputBlocks[i]->setNoDataValue( outputNoData );
295 }
296
297 for ( int row = 0; row < iterRows; row++ )
298 {
299 for ( int col = 0; col < iterCols; col++ )
300 {
301 int valuesCount = 0;
302 for ( const auto &inputBlock : inputBlocks )
303 {
304 bool isNoData = false;
305 const double value = inputBlock.second->valueAndNoData( row, col, isNoData );
306 if ( !isNoData )
307 {
308 inputValues[valuesCount] = value;
309 valuesCount++;
310 }
311 else if ( outputNoDataOverride )
312 {
313 valuesCount = 0;
314 break;
315 }
316 }
317 std::sort( inputValues.begin(), inputValues.begin() + valuesCount );
318
319 for ( int i = 0; i < mRanks.size(); i++ )
320 {
321 if ( valuesCount >= std::abs( mRanks[i] ) )
322 {
323 outputBlocks[i]->setValue( row, col, inputValues[mRanks[i] > 0 ? mRanks[i] - 1 : valuesCount + mRanks[i]] );
324 }
325 else
326 {
327 outputBlocks[i]->setValue( row, col, outputNoData );
328 }
329 }
330 }
331 }
332
333 for ( int i = 0; i < mRanks.size(); i++ )
334 {
335 if ( !provider->writeBlock( outputBlocks[i].get(), i + 1, iterLeft, iterTop ) )
336 {
337 throw QgsProcessingException( QObject::tr( "Could not write output raster block: %1" ).arg( provider->error().summary() ) );
338 }
339 }
340 }
341
342 if ( feedback && hasReportsDuringClose )
343 {
344 std::unique_ptr<QgsFeedback> scaledFeedback( QgsFeedback::createScaledFeedback( feedback, maxProgressDuringBlockWriting, 100.0 ) );
345 if ( !provider->closeWithProgress( scaledFeedback.get() ) )
346 {
347 if ( feedback->isCanceled() )
348 return {};
349 throw QgsProcessingException( QObject::tr( "Could not write raster dataset" ) );
350 }
351 }
352
353 QVariantMap outputs;
354 outputs.insert( u"OUTPUT"_s, outputFile );
355 return outputs;
356}
357
@ Raster
Raster layers.
Definition qgis.h:3608
DataType
Raster data types.
Definition qgis.h:379
@ 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
Represents a coordinate reference system (CRS).
Handles coordinate transforms between two coordinate systems.
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
QString name
Definition qgsmaplayer.h:87
virtual QgsRectangle extent() const
Returns the extent of the layer.
QgsCoordinateReferenceSystem crs
Definition qgsmaplayer.h:90
QString id
Definition qgsmaplayer.h:86
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.
An enum based parameter for processing algorithms, allowing for selection from predefined values.
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 ...
static QgsRectangle combineLayerExtents(const QList< QgsMapLayer * > &layers, const QgsCoordinateReferenceSystem &crs, QgsProcessingContext &context)
Combines the extent of several map layers.
virtual bool sourceHasNoDataValue(int bandNo) const
Returns true if source band has no data value.
virtual double sourceNoDataValue(int bandNo) const
Value representing no data value.
Qgis::DataType dataType(int bandNo) const override=0
Returns data type for the band specified by number.
virtual bool setInput(QgsRasterInterface *input)
Set input.
Iterator for sequentially processing raster cells.
Represents a raster layer.
int height() const
Returns the height of the (unclipped) raster.
QgsRasterDataProvider * dataProvider() override
Returns the source data provider.
int width() const
Returns the width of the (unclipped) raster.
Implements approximate projection support for optimised raster transformation.
void setPrecision(Precision precision)
@ Exact
Exact, precise but slow.
Q_DECL_DEPRECATED void setCrs(const QgsCoordinateReferenceSystem &srcCRS, const QgsCoordinateReferenceSystem &destCRS, int srcDatumTransform=-1, int destDatumTransform=-1)
Sets the source and destination CRS.
A rectangle specified with double values.
double xMinimum
double yMinimum
double xMaximum
double yMaximum
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