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