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