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