QGIS API Documentation 4.3.0-Master (ffcfc20b9b4)
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(
59 "This algorithm performs a cell-by-cell analysis in which output values match the rank of a "
60 "sorted list of overlapping cell values from input layers. The output raster "
61 "will be multi-band if multiple ranks are provided.\n"
62 "If multiband rasters are used in the data raster stack, the algorithm will always "
63 "perform the analysis on the first band of the rasters."
64 );
65}
66
67QString QgsRasterRankAlgorithm::shortDescription() const
68{
69 return QObject::tr(
70 "Performs a cell-by-cell analysis in which output values match the rank of a "
71 "sorted list of overlapping cell values from input layers."
72 );
73}
74
75QgsRasterRankAlgorithm *QgsRasterRankAlgorithm::createInstance() const
76{
77 return new QgsRasterRankAlgorithm();
78}
79
80void QgsRasterRankAlgorithm::initAlgorithm( const QVariantMap & )
81{
82 addParameter( new QgsProcessingParameterMultipleLayers( u"INPUT_RASTERS"_s, QObject::tr( "Input raster layers" ), Qgis::ProcessingSourceType::Raster ) );
83 auto ranksParameter = std::make_unique<QgsProcessingParameterString>( u"RANKS"_s, QObject::tr( "Rank(s) (separate multiple ranks using commas)" ), 1 );
84 ranksParameter->setHelp(
85 QObject::tr(
86 "A rank value must be numerical, with multiple ranks separated by commas. The rank will be used to "
87 "generate output values from sorted lists of input layers’ cell values. A rank value of 1 will pick "
88 "the first value from a given sorted input layers’ cell values list (i.e. the minimum value). "
89 "Negative rank values are supported, and will behave like a negative index. A rank value of -2 will "
90 "pick the second to last value in sorted input values lists, while a rank value of -1 will pick the "
91 "last value (i.e. the maximum value)."
92 )
93 );
94 addParameter( ranksParameter.release() );
95 addParameter(
96 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 )
97 );
98
99 auto extentParam = std::make_unique<QgsProcessingParameterExtent>( u"EXTENT"_s, QObject::tr( "Output extent" ), QVariant(), true );
100 extentParam->setHelp( QObject::tr( "Extent of the output layer. If not specified, the extent will be the overall extent of all input layers" ) );
101 extentParam->setFlags( extentParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
102 addParameter( extentParam.release() );
103 auto cellSizeParam = std::make_unique<QgsProcessingParameterNumber>( u"CELL_SIZE"_s, QObject::tr( "Output cell size" ), Qgis::ProcessingNumberParameterType::Double, QVariant(), true, 0.0 );
104 cellSizeParam->setHelp( QObject::tr( "Cell size of the output layer. If not specified, the smallest cell size from the input layers will be used" ) );
105 cellSizeParam->setFlags( cellSizeParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
106 addParameter( cellSizeParam.release() );
107 auto crsParam = std::make_unique<QgsProcessingParameterCrs>( u"CRS"_s, QObject::tr( "Output CRS" ), QVariant(), true );
108 crsParam->setHelp( QObject::tr( "CRS of the output layer. If not specified, the CRS of the first input layer will be used" ) );
109 crsParam->setFlags( crsParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
110 addParameter( crsParam.release() );
111
112 addParameter( new QgsProcessingParameterRasterDestination( u"OUTPUT"_s, QObject::tr( "Ranked" ) ) );
113}
114
115bool QgsRasterRankAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
116{
117 const QStringList rankStrings = parameterAsString( parameters, u"RANKS"_s, context ).split( ","_L1 );
118 for ( const QString &rankString : rankStrings )
119 {
120 bool ok = false;
121 const int rank = rankString.toInt( &ok );
122 if ( ok && rank != 0 )
123 {
124 mRanks << rank;
125 }
126 else
127 {
128 throw QgsProcessingException( QObject::tr( "Rank values must be integers (found \"%1\")" ).arg( rankString ) );
129 }
130 }
131
132 if ( mRanks.isEmpty() )
133 {
134 throw QgsProcessingException( QObject::tr( "No valid non-zero rank value(s) provided" ) );
135 return false;
136 }
137
138 const QList<QgsMapLayer *> layers = parameterAsLayerList( parameters, u"INPUT_RASTERS"_s, context );
139 for ( const QgsMapLayer *layer : std::as_const( layers ) )
140 {
141 if ( !qobject_cast<const QgsRasterLayer *>( layer ) || !layer->dataProvider() )
142 continue;
143
144 std::unique_ptr<QgsMapLayer> clonedLayer;
145 clonedLayer.reset( layer->clone() );
146 clonedLayer->moveToThread( nullptr );
147 mLayers.push_back( std::move( clonedLayer ) );
148 }
149
150 if ( mLayers.empty() )
151 {
152 throw QgsProcessingException( QObject::tr( "At least one raster input layer must be specified" ) );
153 return false;
154 }
155
156 return true;
157}
158
159
160QVariantMap QgsRasterRankAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
161{
162 QGS_MARK_ALGORITHM_SOURCE
163
164 QList<QgsMapLayer *> layers; // Needed for QgsProcessingUtils::combineLayerExtents
165 for ( auto &layer : mLayers )
166 {
167 layer->moveToThread( QThread::currentThread() );
168 layers << layer.get();
169 }
170
172 if ( parameters.value( u"CRS"_s ).isValid() )
173 {
174 outputCrs = parameterAsCrs( parameters, u"CRS"_s, context );
175 }
176 else
177 {
178 outputCrs = mLayers[0]->crs();
179 }
180
181
182 QgsRasterLayer *templateRasterLayer = qobject_cast<QgsRasterLayer *>( mLayers[0].get() );
183 const Qgis::DataType outputDataType = templateRasterLayer->dataProvider()->dataType( 1 );
184 double outputNoData = 0.0;
185 if ( templateRasterLayer->dataProvider()->sourceHasNoDataValue( 1 ) )
186 {
187 outputNoData = templateRasterLayer->dataProvider()->sourceNoDataValue( 1 );
188 }
189 else
190 {
191 outputNoData = -FLT_MAX;
192 }
193 const bool outputNoDataOverride = parameterAsInt( parameters, u"NODATA_HANDLING"_s, context ) == 1;
194
195 QgsRectangle outputExtent;
196 if ( parameters.value( u"EXTENT"_s ).isValid() )
197 {
198 outputExtent = parameterAsExtent( parameters, u"EXTENT"_s, context, outputCrs );
199 }
200 else
201 {
202 outputExtent = QgsProcessingUtils::combineLayerExtents( layers, outputCrs, context );
203 }
204
205 double minCellSizeX = 1e9;
206 double minCellSizeY = 1e9;
207 for ( auto &layer : mLayers )
208 {
209 QgsRasterLayer *rasterLayer = qobject_cast<QgsRasterLayer *>( layer.get() );
210
211 QgsRectangle extent = rasterLayer->extent();
212 if ( rasterLayer->crs() != outputCrs )
213 {
214 QgsCoordinateTransform ct( rasterLayer->crs(), outputCrs, context.transformContext() );
215 extent = ct.transformBoundingBox( extent );
216 }
217
218 const int width = rasterLayer->width();
219 const int height = rasterLayer->height();
220 if ( width <= 0 || height <= 0 )
221 throw QgsProcessingException( QObject::tr( "%1: raster is empty, cannot proceed" ).arg( rasterLayer->name() ) );
222
223 minCellSizeX = std::min( minCellSizeX, ( extent.xMaximum() - extent.xMinimum() ) / width );
224 minCellSizeY = std::min( minCellSizeY, ( extent.yMaximum() - extent.yMinimum() ) / height );
225 }
226
227 double outputCellSizeX = parameterAsDouble( parameters, u"CELL_SIZE"_s, context );
228 double outputCellSizeY = outputCellSizeX;
229 if ( outputCellSizeX == 0 )
230 {
231 outputCellSizeX = minCellSizeX;
232 outputCellSizeY = minCellSizeY;
233 }
234
235 qgssize cols = static_cast<qgssize>( std::round( ( outputExtent.xMaximum() - outputExtent.xMinimum() ) / outputCellSizeX ) );
236 qgssize rows = static_cast<qgssize>( std::round( ( outputExtent.yMaximum() - outputExtent.yMinimum() ) / outputCellSizeY ) );
237
238 const QString outputFile = parameterAsOutputLayer( parameters, u"OUTPUT"_s, context );
239 const QString outputFormat = parameterAsOutputRasterFormat( parameters, u"OUTPUT"_s, context );
240
241 auto writer = std::make_unique<QgsRasterFileWriter>( outputFile );
242 writer->setOutputFormat( outputFormat );
243 std::unique_ptr<QgsRasterDataProvider> provider( writer->createMultiBandRaster( outputDataType, cols, rows, outputExtent, outputCrs, mRanks.size() ) );
244 if ( !provider )
245 throw QgsProcessingException( QObject::tr( "Could not create raster output: %1" ).arg( outputFile ) );
246 if ( !provider->isValid() )
247 throw QgsProcessingException( QObject::tr( "Could not create raster output %1: %2" ).arg( outputFile, provider->error().message( QgsErrorMessage::Text ) ) );
248 provider->setNoDataValue( 1, outputNoData );
249
250 std::map<QString, std::unique_ptr<QgsRasterInterface>> newProjectorInterfaces;
251 std::map<QString, QgsRasterInterface *> inputInterfaces;
252 std::map<QString, std::unique_ptr<QgsRasterBlock>> inputBlocks;
253 std::vector<std::unique_ptr<QgsRasterBlock>> outputBlocks;
254 outputBlocks.resize( mRanks.size() );
255 for ( auto &layer : mLayers )
256 {
257 QgsRasterLayer *rasterLayer = qobject_cast<QgsRasterLayer *>( layer.get() );
258 if ( rasterLayer->crs() != outputCrs )
259 {
260 QgsRasterProjector *projector = new QgsRasterProjector();
261 projector->setCrs( rasterLayer->crs(), outputCrs, context.transformContext() );
262 projector->setInput( rasterLayer->dataProvider() );
264 newProjectorInterfaces[rasterLayer->id()].reset( projector );
265 inputInterfaces[rasterLayer->id()] = projector;
266 }
267 else
268 {
269 inputInterfaces[rasterLayer->id()] = rasterLayer->dataProvider();
270 }
271 }
272
273 QgsRasterIterator rasterIterator( inputInterfaces[mLayers.at( 0 )->id()] );
274 rasterIterator.startRasterRead( 1, cols, rows, outputExtent );
275 int blockCount = static_cast<int>( rasterIterator.blockCount() );
276
277 const bool hasReportsDuringClose = provider->hasReportsDuringClose();
278 const double maxProgressDuringBlockWriting = hasReportsDuringClose ? 50.0 : 100.0;
279
280 const double step = blockCount > 0 ? maxProgressDuringBlockWriting / blockCount : 0;
281 std::vector<double> inputValues;
282 inputValues.resize( mLayers.size() );
283 for ( int currentBlock = 0; currentBlock < blockCount; currentBlock++ )
284 {
285 if ( feedback->isCanceled() )
286 {
287 break;
288 }
289 feedback->setProgress( currentBlock * step );
290
291 int iterLeft = 0;
292 int iterTop = 0;
293 int iterCols = 0;
294 int iterRows = 0;
295 QgsRectangle blockExtent;
296 rasterIterator.next( 1, iterCols, iterRows, iterLeft, iterTop, blockExtent );
297
298 for ( const auto &inputInterface : inputInterfaces )
299 {
300 inputBlocks[inputInterface.first].reset( inputInterface.second->block( 1, blockExtent, iterCols, iterRows ) );
301 }
302
303 for ( int i = 0; i < mRanks.size(); i++ )
304 {
305 outputBlocks[i] = std::make_unique<QgsRasterBlock>( outputDataType, iterCols, iterRows );
306 outputBlocks[i]->setNoDataValue( outputNoData );
307 }
308
309 for ( int row = 0; row < iterRows; row++ )
310 {
311 for ( int col = 0; col < iterCols; col++ )
312 {
313 int valuesCount = 0;
314 for ( const auto &inputBlock : inputBlocks )
315 {
316 bool isNoData = false;
317 const double value = inputBlock.second->valueAndNoData( row, col, isNoData );
318 if ( !isNoData )
319 {
320 inputValues[valuesCount] = value;
321 valuesCount++;
322 }
323 else if ( outputNoDataOverride )
324 {
325 valuesCount = 0;
326 break;
327 }
328 }
329 std::sort( inputValues.begin(), inputValues.begin() + valuesCount );
330
331 for ( int i = 0; i < mRanks.size(); i++ )
332 {
333 if ( valuesCount >= std::abs( mRanks[i] ) )
334 {
335 outputBlocks[i]->setValue( row, col, inputValues[mRanks[i] > 0 ? mRanks[i] - 1 : valuesCount + mRanks[i]] );
336 }
337 else
338 {
339 outputBlocks[i]->setValue( row, col, outputNoData );
340 }
341 }
342 }
343 }
344
345 for ( int i = 0; i < mRanks.size(); i++ )
346 {
347 if ( !provider->writeBlock( outputBlocks[i].get(), i + 1, iterLeft, iterTop ) )
348 {
349 throw QgsProcessingException( QObject::tr( "Could not write output raster block: %1" ).arg( provider->error().summary() ) );
350 }
351 }
352 }
353
354 if ( feedback && hasReportsDuringClose )
355 {
356 std::unique_ptr<QgsFeedback> scaledFeedback( QgsFeedback::createScaledFeedback( feedback, maxProgressDuringBlockWriting, 100.0 ) );
357 if ( !provider->closeWithProgress( scaledFeedback.get() ) )
358 {
359 if ( feedback->isCanceled() )
360 return {};
361 throw QgsProcessingException( QObject::tr( "Could not write raster dataset" ) );
362 }
363 }
364
365 QVariantMap outputs;
366 outputs.insert( u"OUTPUT"_s, outputFile );
367 return outputs;
368}
369
@ Raster
Raster layers.
Definition qgis.h:3753
DataType
Raster data types.
Definition qgis.h:393
@ Advanced
Parameter is an advanced parameter which should be hidden from users by default.
Definition qgis.h:3982
@ Double
Double/float values.
Definition qgis.h:4023
Represents a coordinate reference system (CRS).
Handles coordinate transforms between two coordinate systems.
@ Text
Plain text format.
Definition qgserror.h:40
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:56
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:65
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 Q_INVOKABLE 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:8136