Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgsalgorithmcellstatistics.cpp
3 : : ---------------------
4 : : begin : May 2020
5 : : copyright : (C) 2020 by Clemens Raffler
6 : : email : clemens dot raffler at gmail dot com
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 : :
18 : : #include "qgsalgorithmcellstatistics.h"
19 : : #include "qgsrasterprojector.h"
20 : : #include "qgsrasterfilewriter.h"
21 : : #include "qgsrasteranalysisutils.h"
22 : :
23 : : ///@cond PRIVATE
24 : :
25 : :
26 : 0 : QString QgsCellStatisticsAlgorithmBase::group() const
27 : : {
28 : 0 : return QObject::tr( "Raster analysis" );
29 : : }
30 : :
31 : 0 : QString QgsCellStatisticsAlgorithmBase::groupId() const
32 : : {
33 : 0 : return QStringLiteral( "rasteranalysis" );
34 : : }
35 : :
36 : 0 : void QgsCellStatisticsAlgorithmBase::initAlgorithm( const QVariantMap & )
37 : : {
38 : 0 : addParameter( new QgsProcessingParameterMultipleLayers( QStringLiteral( "INPUT" ),
39 : 0 : QObject::tr( "Input layers" ), QgsProcessing::TypeRaster ) );
40 : :
41 : 0 : addSpecificAlgorithmParams();
42 : :
43 : 0 : addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "IGNORE_NODATA" ), QObject::tr( "Ignore NoData values" ), true ) );
44 : :
45 : 0 : addParameter( new QgsProcessingParameterRasterLayer( QStringLiteral( "REFERENCE_LAYER" ), QObject::tr( "Reference layer" ) ) );
46 : :
47 : 0 : std::unique_ptr< QgsProcessingParameterNumber > output_nodata_parameter = std::make_unique< QgsProcessingParameterNumber >( QStringLiteral( "OUTPUT_NODATA_VALUE" ), QObject::tr( "Output NoData value" ), QgsProcessingParameterNumber::Double, -9999, false );
48 : 0 : output_nodata_parameter->setFlags( output_nodata_parameter->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
49 : 0 : addParameter( output_nodata_parameter.release() );
50 : :
51 : 0 : addParameter( new QgsProcessingParameterRasterDestination( QStringLiteral( "OUTPUT" ),
52 : 0 : QObject::tr( "Output layer" ) ) );
53 : :
54 : 0 : addOutput( new QgsProcessingOutputString( QStringLiteral( "EXTENT" ), QObject::tr( "Extent" ) ) );
55 : 0 : addOutput( new QgsProcessingOutputString( QStringLiteral( "CRS_AUTHID" ), QObject::tr( "CRS authority identifier" ) ) );
56 : 0 : addOutput( new QgsProcessingOutputNumber( QStringLiteral( "WIDTH_IN_PIXELS" ), QObject::tr( "Width in pixels" ) ) );
57 : 0 : addOutput( new QgsProcessingOutputNumber( QStringLiteral( "HEIGHT_IN_PIXELS" ), QObject::tr( "Height in pixels" ) ) );
58 : 0 : addOutput( new QgsProcessingOutputNumber( QStringLiteral( "TOTAL_PIXEL_COUNT" ), QObject::tr( "Total pixel count" ) ) );
59 : 0 : }
60 : :
61 : 0 : bool QgsCellStatisticsAlgorithmBase::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
62 : : {
63 : 0 : QgsRasterLayer *referenceLayer = parameterAsRasterLayer( parameters, QStringLiteral( "REFERENCE_LAYER" ), context );
64 : 0 : if ( !referenceLayer )
65 : 0 : throw QgsProcessingException( invalidRasterError( parameters, QStringLiteral( "REFERENCE_LAYER" ) ) );
66 : :
67 : 0 : mIgnoreNoData = parameterAsBool( parameters, QStringLiteral( "IGNORE_NODATA" ), context );
68 : 0 : mNoDataValue = parameterAsDouble( parameters, QStringLiteral( "OUTPUT_NODATA_VALUE" ), context );
69 : 0 : mCrs = referenceLayer->crs();
70 : 0 : mRasterUnitsPerPixelX = referenceLayer->rasterUnitsPerPixelX();
71 : 0 : mRasterUnitsPerPixelY = referenceLayer->rasterUnitsPerPixelY();
72 : 0 : mLayerWidth = referenceLayer->width();
73 : 0 : mLayerHeight = referenceLayer->height();
74 : 0 : mExtent = referenceLayer->extent();
75 : :
76 : 0 : const QList< QgsMapLayer * > layers = parameterAsLayerList( parameters, QStringLiteral( "INPUT" ), context );
77 : 0 : QList< QgsRasterLayer * > rasterLayers;
78 : 0 : rasterLayers.reserve( layers.count() );
79 : 0 : for ( QgsMapLayer *l : layers )
80 : : {
81 : 0 : if ( feedback->isCanceled() )
82 : 0 : break; //in case some slow data sources are loaded
83 : :
84 : 0 : if ( l->type() == QgsMapLayerType::RasterLayer )
85 : : {
86 : 0 : QgsRasterLayer *layer = qobject_cast< QgsRasterLayer * >( l );
87 : 0 : QgsRasterAnalysisUtils::RasterLogicInput input;
88 : 0 : const int band = 1; //could be made dynamic
89 : 0 : input.hasNoDataValue = layer->dataProvider()->sourceHasNoDataValue( band );
90 : 0 : input.sourceDataProvider.reset( layer->dataProvider()->clone() );
91 : 0 : input.interface = input.sourceDataProvider.get();
92 : : // add projector if necessary
93 : 0 : if ( layer->crs() != mCrs )
94 : : {
95 : 0 : input.projector = std::make_unique< QgsRasterProjector >();
96 : 0 : input.projector->setInput( input.sourceDataProvider.get() );
97 : 0 : input.projector->setCrs( layer->crs(), mCrs, context.transformContext() );
98 : 0 : input.interface = input.projector.get();
99 : 0 : }
100 : 0 : mInputs.emplace_back( std::move( input ) );
101 : 0 : }
102 : : }
103 : :
104 : : //determine output raster data type
105 : : //initially raster data type to most primitive data type that is possible
106 : 0 : mDataType = Qgis::Byte;
107 : 0 : for ( const QgsRasterAnalysisUtils::RasterLogicInput &i : std::as_const( mInputs ) )
108 : : {
109 : 0 : for ( int band : i.bands )
110 : : {
111 : 0 : Qgis::DataType inputDataType = i.interface->dataType( band );
112 : 0 : if ( static_cast<int>( mDataType ) < static_cast<int>( inputDataType ) )
113 : 0 : mDataType = inputDataType; //if raster data type is more potent, set it as new data type
114 : : }
115 : : }
116 : :
117 : 0 : prepareSpecificAlgorithmParameters( parameters, context, feedback );
118 : :
119 : : return true;
120 : 0 : }
121 : :
122 : :
123 : 0 : QVariantMap QgsCellStatisticsAlgorithmBase::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
124 : : {
125 : 0 : const QString outputFile = parameterAsOutputLayer( parameters, QStringLiteral( "OUTPUT" ), context );
126 : 0 : QFileInfo fi( outputFile );
127 : 0 : const QString outputFormat = QgsRasterFileWriter::driverForExtension( fi.suffix() );
128 : :
129 : 0 : std::unique_ptr< QgsRasterFileWriter > writer = std::make_unique< QgsRasterFileWriter >( outputFile );
130 : 0 : writer->setOutputProviderKey( QStringLiteral( "gdal" ) );
131 : 0 : writer->setOutputFormat( outputFormat );
132 : 0 : mOutputRasterDataProvider = writer->createOneBandRaster( mDataType, mLayerWidth, mLayerHeight, mExtent, mCrs );
133 : 0 : if ( !mOutputRasterDataProvider )
134 : 0 : throw QgsProcessingException( QObject::tr( "Could not create raster output: %1" ).arg( outputFile ) );
135 : 0 : if ( !mOutputRasterDataProvider->isValid() )
136 : 0 : throw QgsProcessingException( QObject::tr( "Could not create raster output %1: %2" ).arg( outputFile, mOutputRasterDataProvider->error().message( QgsErrorMessage::Text ) ) );
137 : :
138 : 0 : mOutputRasterDataProvider->setNoDataValue( 1, mNoDataValue );
139 : 0 : qgssize layerSize = static_cast< qgssize >( mLayerWidth ) * static_cast< qgssize >( mLayerHeight );
140 : :
141 : : //call child statistics method
142 : 0 : processRasterStack( feedback );
143 : :
144 : 0 : QVariantMap outputs;
145 : 0 : outputs.insert( QStringLiteral( "EXTENT" ), mExtent.toString() );
146 : 0 : outputs.insert( QStringLiteral( "CRS_AUTHID" ), mCrs.authid() );
147 : 0 : outputs.insert( QStringLiteral( "WIDTH_IN_PIXELS" ), mLayerWidth );
148 : 0 : outputs.insert( QStringLiteral( "HEIGHT_IN_PIXELS" ), mLayerHeight );
149 : 0 : outputs.insert( QStringLiteral( "TOTAL_PIXEL_COUNT" ), layerSize );
150 : 0 : outputs.insert( QStringLiteral( "OUTPUT" ), outputFile );
151 : :
152 : 0 : return outputs;
153 : 0 : }
154 : :
155 : :
156 : : //
157 : : //QgsCellStatisticsAlgorithm
158 : : //
159 : 0 : QString QgsCellStatisticsAlgorithm::displayName() const
160 : : {
161 : 0 : return QObject::tr( "Cell statistics" );
162 : : }
163 : :
164 : 0 : QString QgsCellStatisticsAlgorithm::name() const
165 : : {
166 : 0 : return QStringLiteral( "cellstatistics" );
167 : : }
168 : :
169 : 0 : QStringList QgsCellStatisticsAlgorithm::tags() const
170 : : {
171 : 0 : return QObject::tr( "cell,pixel,statistic,count,mean,sum,majority,minority,variance,variety,range,median,minimum,maximum" ).split( ',' );
172 : 0 : }
173 : :
174 : 0 : QString QgsCellStatisticsAlgorithm::shortHelpString() const
175 : : {
176 : 0 : return QObject::tr( "The Cell statistics algorithm computes a value for each cell of the "
177 : : "output raster. At each cell location, "
178 : : "the output value is defined as a function of all overlaid cell values of the "
179 : : "input rasters.\n\n"
180 : : "The output raster's extent and resolution is defined by a reference "
181 : : "raster. The following functions can be applied on the input "
182 : : "raster cells per output raster cell location:\n"
183 : : "<ul> "
184 : : " <li>Sum</li>"
185 : : " <li>Count</li>"
186 : : " <li>Mean</li>"
187 : : " <li>Median</li>"
188 : : " <li>Standard deviation</li>"
189 : : " <li>Variance</li>"
190 : : " <li>Minimum</li>"
191 : : " <li>Maximum</li>"
192 : : " <li>Minority (least frequent value)</li>"
193 : : " <li>Majority (most frequent value)</li>"
194 : : " <li>Range (max-min)</li>"
195 : : " <li>Variety (count of unique values)</li>"
196 : : "</ul> "
197 : : "Input raster layers that do not match the cell size of the reference raster layer will be "
198 : : "resampled using nearest neighbor resampling. The output raster data type will be set to "
199 : : "the most complex data type present in the input datasets except when using the functions "
200 : : "Mean, Standard deviation and Variance (data type is always Float32/Float64 depending on input float type) or Count and Variety (data type is always Int32).\n"
201 : : "<i>Calculation details - general:</i> NoData values in any of the input layers will result in a NoData cell output if the Ignore NoData parameter is not set.\n"
202 : : "<i>Calculation details - Count:</i> Count will always result in the number of cells without NoData values at the current cell location.\n"
203 : : "<i>Calculation details - Median:</i> If the number of input layers is even, the median will be calculated as the "
204 : : "arithmetic mean of the two middle values of the ordered cell input values. In this case the output data type is Float32.\n"
205 : : "<i>Calculation details - Minority/Majority:</i> If no unique minority or majority could be found, the result is NoData, except all "
206 : : "input cell values are equal." );
207 : : }
208 : :
209 : 0 : QgsCellStatisticsAlgorithm *QgsCellStatisticsAlgorithm::createInstance() const
210 : : {
211 : 0 : return new QgsCellStatisticsAlgorithm();
212 : 0 : }
213 : :
214 : 0 : void QgsCellStatisticsAlgorithm::addSpecificAlgorithmParams()
215 : : {
216 : 0 : QStringList statistics = QStringList();
217 : 0 : statistics << QObject::tr( "Sum" )
218 : 0 : << QObject::tr( "Count" )
219 : 0 : << QObject::tr( "Mean" )
220 : 0 : << QObject::tr( "Median" )
221 : 0 : << QObject::tr( "Standard deviation" )
222 : 0 : << QObject::tr( "Variance" )
223 : 0 : << QObject::tr( "Minimum" )
224 : 0 : << QObject::tr( "Maximum" )
225 : 0 : << QObject::tr( "Minority" )
226 : 0 : << QObject::tr( "Majority" )
227 : 0 : << QObject::tr( "Range" )
228 : 0 : << QObject::tr( "Variety" );
229 : :
230 : 0 : addParameter( new QgsProcessingParameterEnum( QStringLiteral( "STATISTIC" ), QObject::tr( "Statistic" ), statistics, false, 0, false ) );
231 : 0 : }
232 : :
233 : 0 : bool QgsCellStatisticsAlgorithm::prepareSpecificAlgorithmParameters( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
234 : : {
235 : : Q_UNUSED( feedback )
236 : : //obtain statistic method
237 : 0 : mMethod = static_cast<QgsRasterAnalysisUtils::CellValueStatisticMethods>( parameterAsEnum( parameters, QStringLiteral( "STATISTIC" ), context ) );
238 : :
239 : : //force data types on specific functions in the cellstatistics alg if input data types don't match
240 : : if (
241 : 0 : mMethod == QgsRasterAnalysisUtils::Mean ||
242 : 0 : mMethod == QgsRasterAnalysisUtils::StandardDeviation ||
243 : 0 : mMethod == QgsRasterAnalysisUtils::Variance ||
244 : 0 : ( mMethod == QgsRasterAnalysisUtils::Median && ( mInputs.size() % 2 == 0 ) )
245 : : )
246 : : {
247 : 0 : if ( static_cast<int>( mDataType ) < 6 )
248 : 0 : mDataType = Qgis::Float32; //force float on mean, stddev and median with equal number of input layers if all inputs are integer
249 : 0 : }
250 : 0 : else if ( mMethod == QgsRasterAnalysisUtils::Count || mMethod == QgsRasterAnalysisUtils::Variety ) //count, variety
251 : : {
252 : 0 : if ( static_cast<int>( mDataType ) > 5 ) //if is floating point type
253 : 0 : mDataType = Qgis::Int32; //force integer on variety if all inputs are float or complex
254 : 0 : }
255 : 0 : return true;
256 : 0 : }
257 : :
258 : 0 : void QgsCellStatisticsAlgorithm::processRasterStack( QgsProcessingFeedback *feedback )
259 : : {
260 : 0 : int maxWidth = QgsRasterIterator::DEFAULT_MAXIMUM_TILE_WIDTH;
261 : 0 : int maxHeight = QgsRasterIterator::DEFAULT_MAXIMUM_TILE_HEIGHT;
262 : 0 : int nbBlocksWidth = static_cast< int>( std::ceil( 1.0 * mLayerWidth / maxWidth ) );
263 : 0 : int nbBlocksHeight = static_cast< int >( std::ceil( 1.0 * mLayerHeight / maxHeight ) );
264 : 0 : int nbBlocks = nbBlocksWidth * nbBlocksHeight;
265 : 0 : mOutputRasterDataProvider->setEditable( true );
266 : 0 : QgsRasterIterator outputIter( mOutputRasterDataProvider );
267 : 0 : outputIter.startRasterRead( 1, mLayerWidth, mLayerHeight, mExtent );
268 : :
269 : 0 : int iterLeft = 0;
270 : 0 : int iterTop = 0;
271 : 0 : int iterCols = 0;
272 : 0 : int iterRows = 0;
273 : 0 : QgsRectangle blockExtent;
274 : 0 : std::unique_ptr< QgsRasterBlock > outputBlock;
275 : 0 : while ( outputIter.readNextRasterPart( 1, iterCols, iterRows, outputBlock, iterLeft, iterTop, &blockExtent ) )
276 : : {
277 : 0 : std::vector< std::unique_ptr< QgsRasterBlock > > inputBlocks;
278 : 0 : for ( const QgsRasterAnalysisUtils::RasterLogicInput &i : std::as_const( mInputs ) )
279 : : {
280 : 0 : if ( feedback->isCanceled() )
281 : 0 : break; //in case some slow data sources are loaded
282 : 0 : for ( int band : i.bands )
283 : : {
284 : 0 : if ( feedback->isCanceled() )
285 : 0 : break; //in case some slow data sources are loaded
286 : 0 : std::unique_ptr< QgsRasterBlock > b( i.interface->block( band, blockExtent, iterCols, iterRows ) );
287 : 0 : inputBlocks.emplace_back( std::move( b ) );
288 : 0 : }
289 : : }
290 : :
291 : 0 : feedback->setProgress( 100 * ( ( iterTop / maxHeight * nbBlocksWidth ) + iterLeft / maxWidth ) / nbBlocks );
292 : 0 : for ( int row = 0; row < iterRows; row++ )
293 : : {
294 : 0 : if ( feedback->isCanceled() )
295 : 0 : break;
296 : :
297 : 0 : for ( int col = 0; col < iterCols; col++ )
298 : : {
299 : 0 : double result = 0;
300 : 0 : bool noDataInStack = false;
301 : 0 : std::vector<double> cellValues = QgsRasterAnalysisUtils::getCellValuesFromBlockStack( inputBlocks, row, col, noDataInStack );
302 : 0 : int cellValueStackSize = cellValues.size();
303 : :
304 : 0 : if ( noDataInStack && !mIgnoreNoData )
305 : : {
306 : : //output cell will always be NoData if NoData occurs in cellValueStack and NoData is not ignored
307 : : //this saves unnecessary iterations on the cellValueStack
308 : 0 : if ( mMethod == QgsRasterAnalysisUtils::Count )
309 : 0 : outputBlock->setValue( row, col, cellValueStackSize );
310 : : else
311 : : {
312 : 0 : outputBlock->setValue( row, col, mNoDataValue );
313 : : }
314 : 0 : }
315 : 0 : else if ( !noDataInStack || ( mIgnoreNoData && cellValueStackSize > 0 ) )
316 : : {
317 : 0 : switch ( mMethod )
318 : : {
319 : : case QgsRasterAnalysisUtils::Sum:
320 : 0 : result = std::accumulate( cellValues.begin(), cellValues.end(), 0.0 );
321 : 0 : break;
322 : : case QgsRasterAnalysisUtils::Count:
323 : 0 : result = cellValueStackSize;
324 : 0 : break;
325 : : case QgsRasterAnalysisUtils::Mean:
326 : 0 : result = QgsRasterAnalysisUtils::meanFromCellValues( cellValues, cellValueStackSize );
327 : 0 : break;
328 : : case QgsRasterAnalysisUtils::Median:
329 : 0 : result = QgsRasterAnalysisUtils::medianFromCellValues( cellValues, cellValueStackSize );
330 : 0 : break;
331 : : case QgsRasterAnalysisUtils::StandardDeviation:
332 : 0 : result = QgsRasterAnalysisUtils::stddevFromCellValues( cellValues, cellValueStackSize );
333 : 0 : break;
334 : : case QgsRasterAnalysisUtils::Variance:
335 : 0 : result = QgsRasterAnalysisUtils::varianceFromCellValues( cellValues, cellValueStackSize );
336 : 0 : break;
337 : : case QgsRasterAnalysisUtils::Minimum:
338 : 0 : result = QgsRasterAnalysisUtils::minimumFromCellValues( cellValues );
339 : 0 : break;
340 : : case QgsRasterAnalysisUtils::Maximum:
341 : 0 : result = QgsRasterAnalysisUtils::maximumFromCellValues( cellValues );
342 : 0 : break;
343 : : case QgsRasterAnalysisUtils::Minority:
344 : 0 : result = QgsRasterAnalysisUtils::minorityFromCellValues( cellValues, mNoDataValue, cellValueStackSize );
345 : 0 : break;
346 : : case QgsRasterAnalysisUtils::Majority:
347 : 0 : result = QgsRasterAnalysisUtils::majorityFromCellValues( cellValues, mNoDataValue, cellValueStackSize );
348 : 0 : break;
349 : : case QgsRasterAnalysisUtils::Range:
350 : 0 : result = QgsRasterAnalysisUtils::rangeFromCellValues( cellValues );
351 : 0 : break;
352 : : case QgsRasterAnalysisUtils::Variety:
353 : 0 : result = QgsRasterAnalysisUtils::varietyFromCellValues( cellValues );
354 : 0 : break;
355 : : }
356 : 0 : outputBlock->setValue( row, col, result );
357 : 0 : }
358 : : else
359 : : {
360 : : //result is NoData if cellValueStack contains no valid values, eg. all cellValues are NoData
361 : 0 : outputBlock->setValue( row, col, mNoDataValue );
362 : : }
363 : 0 : }
364 : 0 : }
365 : 0 : mOutputRasterDataProvider->writeBlock( outputBlock.get(), 1, iterLeft, iterTop );
366 : 0 : }
367 : 0 : mOutputRasterDataProvider->setEditable( false );
368 : 0 : }
369 : :
370 : : //
371 : : //QgsCellStatisticsPercentileAlgorithm
372 : : //
373 : 0 : QString QgsCellStatisticsPercentileAlgorithm::displayName() const
374 : : {
375 : 0 : return QObject::tr( "Cell stack percentile" );
376 : : }
377 : :
378 : 0 : QString QgsCellStatisticsPercentileAlgorithm::name() const
379 : : {
380 : 0 : return QStringLiteral( "cellstackpercentile" );
381 : : }
382 : :
383 : 0 : QStringList QgsCellStatisticsPercentileAlgorithm::tags() const
384 : : {
385 : 0 : return QObject::tr( "cell,pixel,statistic,percentile,quantile,quartile" ).split( ',' );
386 : 0 : }
387 : :
388 : 0 : QString QgsCellStatisticsPercentileAlgorithm::shortHelpString() const
389 : : {
390 : 0 : return QObject::tr( "The Cell stack percentile algorithm returns the cell-wise percentile value of a stack of rasters "
391 : : "and writes the results to an output raster. The percentile to return is determined by the percentile input value (ranges between 0 and 1). "
392 : : "At each cell location, the specified percentile is obtained using the respective value from "
393 : : "the stack of all overlaid and sorted cell values of the input rasters.\n\n"
394 : : "There are three methods for percentile calculation:"
395 : : "<ul> "
396 : : " <li>Nearest rank</li>"
397 : : " <li>Inclusive linear interpolation (PERCENTILE.INC)</li>"
398 : : " <li>Exclusive linear interpolation (PERCENTILE.EXC)</li>"
399 : : "</ul> "
400 : : "While the output value can stay the same for the nearest rank method (obtains the value that is nearest to the "
401 : : "specified percentile), the linear interpolation method return unique values for different percentiles. Both interpolation "
402 : : "methods follow their counterpart methods implemented by LibreOffice or Microsoft Excel. \n\n"
403 : : "The output raster's extent and resolution is defined by a reference "
404 : : "raster. If the input raster layers that do not match the cell size of the reference raster layer will be "
405 : : "resampled using nearest neighbor resampling. NoData values in any of the input layers will result in a NoData cell output if the Ignore NoData parameter is not set. "
406 : : "The output raster data type will be set to the most complex data type present in the input datasets. " );
407 : : }
408 : :
409 : 0 : QgsCellStatisticsPercentileAlgorithm *QgsCellStatisticsPercentileAlgorithm::createInstance() const
410 : : {
411 : 0 : return new QgsCellStatisticsPercentileAlgorithm();
412 : 0 : }
413 : :
414 : 0 : void QgsCellStatisticsPercentileAlgorithm::addSpecificAlgorithmParams()
415 : : {
416 : 0 : addParameter( new QgsProcessingParameterEnum( QStringLiteral( "METHOD" ), QObject::tr( "Method" ), QStringList() << QObject::tr( "Nearest rank" ) << QObject::tr( "Inclusive linear interpolation (PERCENTILE.INC)" ) << QObject::tr( "Exclusive linear interpolation (PERCENTILE.EXC)" ), false, 0, false ) );
417 : 0 : addParameter( new QgsProcessingParameterNumber( QStringLiteral( "PERCENTILE" ), QObject::tr( "Percentile" ), QgsProcessingParameterNumber::Double, 0.25, false, 0.0, 1.0 ) );
418 : 0 : }
419 : :
420 : 0 : bool QgsCellStatisticsPercentileAlgorithm::prepareSpecificAlgorithmParameters( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
421 : : {
422 : : Q_UNUSED( feedback )
423 : 0 : mMethod = static_cast< QgsRasterAnalysisUtils::CellValuePercentileMethods >( parameterAsEnum( parameters, QStringLiteral( "METHOD" ), context ) );
424 : 0 : mPercentile = parameterAsDouble( parameters, QStringLiteral( "PERCENTILE" ), context );
425 : :
426 : : //default percentile output data type to float32 raster if interpolation method is chosen
427 : : //otherwise use the most potent data type in the intput raster stack (see prepareAlgorithm() in base class)
428 : 0 : if ( mMethod != QgsRasterAnalysisUtils::CellValuePercentileMethods::NearestRankPercentile && mDataType < 6 )
429 : 0 : mDataType = Qgis::DataType::Float32;
430 : :
431 : 0 : return true;
432 : 0 : }
433 : :
434 : 0 : void QgsCellStatisticsPercentileAlgorithm::processRasterStack( QgsProcessingFeedback *feedback )
435 : : {
436 : :
437 : 0 : int maxWidth = QgsRasterIterator::DEFAULT_MAXIMUM_TILE_WIDTH;
438 : 0 : int maxHeight = QgsRasterIterator::DEFAULT_MAXIMUM_TILE_HEIGHT;
439 : 0 : int nbBlocksWidth = static_cast< int>( std::ceil( 1.0 * mLayerWidth / maxWidth ) );
440 : 0 : int nbBlocksHeight = static_cast< int >( std::ceil( 1.0 * mLayerHeight / maxHeight ) );
441 : 0 : int nbBlocks = nbBlocksWidth * nbBlocksHeight;
442 : 0 : mOutputRasterDataProvider->setEditable( true );
443 : 0 : QgsRasterIterator outputIter( mOutputRasterDataProvider );
444 : 0 : outputIter.startRasterRead( 1, mLayerWidth, mLayerHeight, mExtent );
445 : :
446 : 0 : int iterLeft = 0;
447 : 0 : int iterTop = 0;
448 : 0 : int iterCols = 0;
449 : 0 : int iterRows = 0;
450 : 0 : QgsRectangle blockExtent;
451 : 0 : std::unique_ptr< QgsRasterBlock > outputBlock;
452 : 0 : while ( outputIter.readNextRasterPart( 1, iterCols, iterRows, outputBlock, iterLeft, iterTop, &blockExtent ) )
453 : : {
454 : 0 : std::vector< std::unique_ptr< QgsRasterBlock > > inputBlocks;
455 : 0 : for ( const QgsRasterAnalysisUtils::RasterLogicInput &i : std::as_const( mInputs ) )
456 : : {
457 : 0 : if ( feedback->isCanceled() )
458 : 0 : break; //in case some slow data sources are loaded
459 : 0 : for ( int band : i.bands )
460 : : {
461 : 0 : if ( feedback->isCanceled() )
462 : 0 : break; //in case some slow data sources are loaded
463 : 0 : std::unique_ptr< QgsRasterBlock > b( i.interface->block( band, blockExtent, iterCols, iterRows ) );
464 : 0 : inputBlocks.emplace_back( std::move( b ) );
465 : 0 : }
466 : : }
467 : :
468 : 0 : feedback->setProgress( 100 * ( ( iterTop / maxHeight * nbBlocksWidth ) + iterLeft / maxWidth ) / nbBlocks );
469 : 0 : for ( int row = 0; row < iterRows; row++ )
470 : : {
471 : 0 : if ( feedback->isCanceled() )
472 : 0 : break;
473 : :
474 : 0 : for ( int col = 0; col < iterCols; col++ )
475 : : {
476 : 0 : double result = 0;
477 : 0 : bool noDataInStack = false;
478 : 0 : std::vector<double> cellValues = QgsRasterAnalysisUtils::getCellValuesFromBlockStack( inputBlocks, row, col, noDataInStack );
479 : 0 : int cellValueStackSize = cellValues.size();
480 : :
481 : 0 : if ( noDataInStack && !mIgnoreNoData )
482 : : {
483 : 0 : outputBlock->setValue( row, col, mNoDataValue );
484 : 0 : }
485 : 0 : else if ( !noDataInStack || ( mIgnoreNoData && cellValueStackSize > 0 ) )
486 : : {
487 : 0 : switch ( mMethod )
488 : : {
489 : : case QgsRasterAnalysisUtils::NearestRankPercentile:
490 : 0 : result = QgsRasterAnalysisUtils::nearestRankPercentile( cellValues, cellValueStackSize, mPercentile );
491 : 0 : break;
492 : : case QgsRasterAnalysisUtils::InterpolatedPercentileInc:
493 : 0 : result = QgsRasterAnalysisUtils::interpolatedPercentileInc( cellValues, cellValueStackSize, mPercentile );
494 : 0 : break;
495 : : case QgsRasterAnalysisUtils::InterpolatedPercentileExc:
496 : 0 : result = QgsRasterAnalysisUtils::interpolatedPercentileExc( cellValues, cellValueStackSize, mPercentile, mNoDataValue );
497 : 0 : break;
498 : : }
499 : 0 : outputBlock->setValue( row, col, result );
500 : 0 : }
501 : : else
502 : : {
503 : : //result is NoData if cellValueStack contains no valid values, eg. all cellValues are NoData
504 : 0 : outputBlock->setValue( row, col, mNoDataValue );
505 : : }
506 : 0 : }
507 : 0 : }
508 : 0 : mOutputRasterDataProvider->writeBlock( outputBlock.get(), 1, iterLeft, iterTop );
509 : 0 : }
510 : 0 : mOutputRasterDataProvider->setEditable( false );
511 : 0 : }
512 : :
513 : : //
514 : : //QgsCellStatisticsPercentRankFromValueAlgorithm
515 : : //
516 : 0 : QString QgsCellStatisticsPercentRankFromValueAlgorithm::displayName() const
517 : : {
518 : 0 : return QObject::tr( "Cell stack percent rank from value" );
519 : : }
520 : :
521 : 0 : QString QgsCellStatisticsPercentRankFromValueAlgorithm::name() const
522 : : {
523 : 0 : return QStringLiteral( "cellstackpercentrankfromvalue" );
524 : : }
525 : :
526 : 0 : QStringList QgsCellStatisticsPercentRankFromValueAlgorithm::tags() const
527 : : {
528 : 0 : return QObject::tr( "cell,pixel,statistic,percentrank,rank,percent,value" ).split( ',' );
529 : 0 : }
530 : :
531 : 0 : QString QgsCellStatisticsPercentRankFromValueAlgorithm::shortHelpString() const
532 : : {
533 : 0 : return QObject::tr( "The Cell stack percentrank from value algorithm calculates the cell-wise percentrank value of a stack of rasters based on a single input value "
534 : : "and writes them to an output raster.\n\n"
535 : : "At each cell location, the specified value is ranked among the respective values in the stack of all overlaid and sorted cell values from the input rasters. "
536 : : "For values outside of the stack value distribution, the algorithm returns NoData because the value cannot be ranked among the cell values.\n\n"
537 : : "There are two methods for percentile calculation:"
538 : : "<ul> "
539 : : " <li>Inclusive linearly interpolated percent rank (PERCENTRANK.INC)</li>"
540 : : " <li>Exclusive linearly interpolated percent rank (PERCENTRANK.EXC)</li>"
541 : : "</ul> "
542 : : "The linear interpolation method return the unique percent rank for different values. Both interpolation "
543 : : "methods follow their counterpart methods implemented by LibreOffice or Microsoft Excel. \n\n"
544 : : "The output raster's extent and resolution is defined by a reference "
545 : : "raster. If the input raster layers that do not match the cell size of the reference raster layer will be "
546 : : "resampled using nearest neighbor resampling. NoData values in any of the input layers will result in a NoData cell output if the Ignore NoData parameter is not set. "
547 : : "The output raster data type will always be Float32." );
548 : : }
549 : :
550 : 0 : QgsCellStatisticsPercentRankFromValueAlgorithm *QgsCellStatisticsPercentRankFromValueAlgorithm::createInstance() const
551 : : {
552 : 0 : return new QgsCellStatisticsPercentRankFromValueAlgorithm();
553 : 0 : }
554 : :
555 : 0 : void QgsCellStatisticsPercentRankFromValueAlgorithm::addSpecificAlgorithmParams()
556 : : {
557 : 0 : addParameter( new QgsProcessingParameterEnum( QStringLiteral( "METHOD" ), QObject::tr( "Method" ), QStringList() << QObject::tr( "Inclusive linear interpolation (PERCENTRANK.INC)" ) << QObject::tr( "Exclusive linear interpolation (PERCENTRANK.EXC)" ), false, 0, false ) );
558 : 0 : addParameter( new QgsProcessingParameterNumber( QStringLiteral( "VALUE" ), QObject::tr( "Value" ), QgsProcessingParameterNumber::Double, 10, false ) );
559 : 0 : }
560 : :
561 : 0 : bool QgsCellStatisticsPercentRankFromValueAlgorithm::prepareSpecificAlgorithmParameters( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
562 : : {
563 : : Q_UNUSED( feedback )
564 : 0 : mMethod = static_cast< QgsRasterAnalysisUtils::CellValuePercentRankMethods >( parameterAsEnum( parameters, QStringLiteral( "METHOD" ), context ) );
565 : 0 : mValue = parameterAsDouble( parameters, QStringLiteral( "VALUE" ), context );
566 : :
567 : : //output data type always defaults to Float32 because result only ranges between 0 and 1
568 : 0 : mDataType = Qgis::DataType::Float32;
569 : 0 : return true;
570 : 0 : }
571 : :
572 : 0 : void QgsCellStatisticsPercentRankFromValueAlgorithm::processRasterStack( QgsProcessingFeedback *feedback )
573 : : {
574 : :
575 : 0 : int maxWidth = QgsRasterIterator::DEFAULT_MAXIMUM_TILE_WIDTH;
576 : 0 : int maxHeight = QgsRasterIterator::DEFAULT_MAXIMUM_TILE_HEIGHT;
577 : 0 : int nbBlocksWidth = static_cast< int>( std::ceil( 1.0 * mLayerWidth / maxWidth ) );
578 : 0 : int nbBlocksHeight = static_cast< int >( std::ceil( 1.0 * mLayerHeight / maxHeight ) );
579 : 0 : int nbBlocks = nbBlocksWidth * nbBlocksHeight;
580 : 0 : mOutputRasterDataProvider->setEditable( true );
581 : 0 : QgsRasterIterator outputIter( mOutputRasterDataProvider );
582 : 0 : outputIter.startRasterRead( 1, mLayerWidth, mLayerHeight, mExtent );
583 : :
584 : 0 : int iterLeft = 0;
585 : 0 : int iterTop = 0;
586 : 0 : int iterCols = 0;
587 : 0 : int iterRows = 0;
588 : 0 : QgsRectangle blockExtent;
589 : 0 : std::unique_ptr< QgsRasterBlock > outputBlock;
590 : 0 : while ( outputIter.readNextRasterPart( 1, iterCols, iterRows, outputBlock, iterLeft, iterTop, &blockExtent ) )
591 : : {
592 : 0 : std::vector< std::unique_ptr< QgsRasterBlock > > inputBlocks;
593 : 0 : for ( const QgsRasterAnalysisUtils::RasterLogicInput &i : std::as_const( mInputs ) )
594 : : {
595 : 0 : if ( feedback->isCanceled() )
596 : 0 : break; //in case some slow data sources are loaded
597 : 0 : for ( int band : i.bands )
598 : : {
599 : 0 : if ( feedback->isCanceled() )
600 : 0 : break; //in case some slow data sources are loaded
601 : 0 : std::unique_ptr< QgsRasterBlock > b( i.interface->block( band, blockExtent, iterCols, iterRows ) );
602 : 0 : inputBlocks.emplace_back( std::move( b ) );
603 : 0 : }
604 : : }
605 : :
606 : 0 : feedback->setProgress( 100 * ( ( iterTop / maxHeight * nbBlocksWidth ) + iterLeft / maxWidth ) / nbBlocks );
607 : 0 : for ( int row = 0; row < iterRows; row++ )
608 : : {
609 : 0 : if ( feedback->isCanceled() )
610 : 0 : break;
611 : :
612 : 0 : for ( int col = 0; col < iterCols; col++ )
613 : : {
614 : 0 : double result = 0;
615 : 0 : bool noDataInStack = false;
616 : 0 : std::vector<double> cellValues = QgsRasterAnalysisUtils::getCellValuesFromBlockStack( inputBlocks, row, col, noDataInStack );
617 : 0 : int cellValueStackSize = cellValues.size();
618 : :
619 : 0 : if ( noDataInStack && !mIgnoreNoData )
620 : : {
621 : 0 : outputBlock->setValue( row, col, mNoDataValue );
622 : 0 : }
623 : 0 : else if ( !noDataInStack || ( mIgnoreNoData && cellValueStackSize > 0 ) )
624 : : {
625 : 0 : switch ( mMethod )
626 : : {
627 : : case QgsRasterAnalysisUtils::InterpolatedPercentRankInc:
628 : 0 : result = QgsRasterAnalysisUtils::interpolatedPercentRankInc( cellValues, cellValueStackSize, mValue, mNoDataValue );
629 : 0 : break;
630 : : case QgsRasterAnalysisUtils::InterpolatedPercentRankExc:
631 : 0 : result = QgsRasterAnalysisUtils::interpolatedPercentRankExc( cellValues, cellValueStackSize, mValue, mNoDataValue );
632 : 0 : break;
633 : : }
634 : 0 : outputBlock->setValue( row, col, result );
635 : 0 : }
636 : : else
637 : : {
638 : : //result is NoData if cellValueStack contains no valid values, eg. all cellValues are NoData
639 : 0 : outputBlock->setValue( row, col, mNoDataValue );
640 : : }
641 : 0 : }
642 : 0 : }
643 : 0 : mOutputRasterDataProvider->writeBlock( outputBlock.get(), 1, iterLeft, iterTop );
644 : 0 : }
645 : 0 : mOutputRasterDataProvider->setEditable( false );
646 : 0 : }
647 : :
648 : :
649 : : //
650 : : //QgsCellStatisticsPercentRankFromRasterAlgorithm
651 : : //
652 : 0 : QString QgsCellStatisticsPercentRankFromRasterAlgorithm::displayName() const
653 : : {
654 : 0 : return QObject::tr( "Cell stack percentrank from raster layer" );
655 : : }
656 : :
657 : 0 : QString QgsCellStatisticsPercentRankFromRasterAlgorithm::name() const
658 : : {
659 : 0 : return QStringLiteral( "cellstackpercentrankfromrasterlayer" );
660 : : }
661 : :
662 : 0 : QStringList QgsCellStatisticsPercentRankFromRasterAlgorithm::tags() const
663 : : {
664 : 0 : return QObject::tr( "cell,pixel,statistic,percentrank,rank,percent,value,raster" ).split( ',' );
665 : 0 : }
666 : :
667 : 0 : QString QgsCellStatisticsPercentRankFromRasterAlgorithm::shortHelpString() const
668 : : {
669 : 0 : return QObject::tr( "The Cell stack percentrank from raster layer algorithm calculates the cell-wise percentrank value of a stack of rasters based on an input value raster "
670 : : "and writes them to an output raster.\n\n"
671 : : "At each cell location, the current value of the value raster is used ranked among the respective values in the stack of all overlaid and sorted cell values of the input rasters. "
672 : : "For values outside of the the stack value distribution, the algorithm returns NoData because the value cannot be ranked among the cell values.\n\n"
673 : : "There are two methods for percentile calculation:"
674 : : "<ul> "
675 : : " <li>Inclusive linearly interpolated percent rank (PERCENTRANK.INC)</li>"
676 : : " <li>Exclusive linearly interpolated percent rank (PERCENTRANK.EXC)</li>"
677 : : "</ul> "
678 : : "The linear interpolation method return the unique percent rank for different values. Both interpolation "
679 : : "methods follow their counterpart methods implemented by LibreOffice or Microsoft Excel. \n\n"
680 : : "The output raster's extent and resolution is defined by a reference "
681 : : "raster. If the input raster layers that do not match the cell size of the reference raster layer will be "
682 : : "resampled using nearest neighbor resampling. NoData values in any of the input layers will result in a NoData cell output if the Ignore NoData parameter is not set. "
683 : : "The output raster data type will always be Float32." );
684 : : }
685 : :
686 : 0 : QgsCellStatisticsPercentRankFromRasterAlgorithm *QgsCellStatisticsPercentRankFromRasterAlgorithm::createInstance() const
687 : : {
688 : 0 : return new QgsCellStatisticsPercentRankFromRasterAlgorithm();
689 : 0 : }
690 : :
691 : 0 : void QgsCellStatisticsPercentRankFromRasterAlgorithm::addSpecificAlgorithmParams()
692 : : {
693 : 0 : addParameter( new QgsProcessingParameterRasterLayer( QStringLiteral( "INPUT_VALUE_RASTER" ), QObject::tr( "Value raster layer" ) ) );
694 : 0 : addParameter( new QgsProcessingParameterBand( QStringLiteral( "VALUE_RASTER_BAND" ), QObject::tr( "Value raster band" ), 1, QStringLiteral( "VALUE_LAYER" ) ) );
695 : 0 : addParameter( new QgsProcessingParameterEnum( QStringLiteral( "METHOD" ), QObject::tr( "Method" ), QStringList() << QObject::tr( "Inclusive linear interpolation (PERCENTRANK.INC)" ) << QObject::tr( "Exclusive linear interpolation (PERCENTRANK.EXC)" ), false, 0, false ) );
696 : 0 : }
697 : :
698 : 0 : bool QgsCellStatisticsPercentRankFromRasterAlgorithm::prepareSpecificAlgorithmParameters( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
699 : : {
700 : : Q_UNUSED( feedback )
701 : 0 : mMethod = static_cast< QgsRasterAnalysisUtils::CellValuePercentRankMethods >( parameterAsEnum( parameters, QStringLiteral( "METHOD" ), context ) );
702 : :
703 : 0 : QgsRasterLayer *inputValueRaster = parameterAsRasterLayer( parameters, QStringLiteral( "INPUT_VALUE_RASTER" ), context );
704 : 0 : if ( !inputValueRaster )
705 : 0 : throw QgsProcessingException( invalidRasterError( parameters, QStringLiteral( "INPUT_VALUE_RASTER" ) ) );
706 : :
707 : 0 : mValueRasterInterface.reset( inputValueRaster->dataProvider()->clone() );
708 : :
709 : 0 : mValueRasterBand = parameterAsInt( parameters, QStringLiteral( "VALUE_RASTER_BAND" ), context );
710 : :
711 : : //output data type always defaults to Float32 because result only ranges between 0 and 1
712 : 0 : mDataType = Qgis::DataType::Float32;
713 : 0 : return true;
714 : 0 : }
715 : :
716 : 0 : void QgsCellStatisticsPercentRankFromRasterAlgorithm::processRasterStack( QgsProcessingFeedback *feedback )
717 : : {
718 : 0 : int maxWidth = QgsRasterIterator::DEFAULT_MAXIMUM_TILE_WIDTH;
719 : 0 : int maxHeight = QgsRasterIterator::DEFAULT_MAXIMUM_TILE_HEIGHT;
720 : 0 : int nbBlocksWidth = static_cast< int>( std::ceil( 1.0 * mLayerWidth / maxWidth ) );
721 : 0 : int nbBlocksHeight = static_cast< int >( std::ceil( 1.0 * mLayerHeight / maxHeight ) );
722 : 0 : int nbBlocks = nbBlocksWidth * nbBlocksHeight;
723 : 0 : mOutputRasterDataProvider->setEditable( true );
724 : 0 : QgsRasterIterator outputIter( mOutputRasterDataProvider );
725 : 0 : outputIter.startRasterRead( 1, mLayerWidth, mLayerHeight, mExtent );
726 : :
727 : 0 : int iterLeft = 0;
728 : 0 : int iterTop = 0;
729 : 0 : int iterCols = 0;
730 : 0 : int iterRows = 0;
731 : 0 : QgsRectangle blockExtent;
732 : 0 : std::unique_ptr< QgsRasterBlock > outputBlock;
733 : 0 : while ( outputIter.readNextRasterPart( 1, iterCols, iterRows, outputBlock, iterLeft, iterTop, &blockExtent ) )
734 : : {
735 : 0 : std::unique_ptr< QgsRasterBlock > valueBlock( mValueRasterInterface->block( mValueRasterBand, blockExtent, iterCols, iterRows ) );
736 : :
737 : 0 : std::vector< std::unique_ptr< QgsRasterBlock > > inputBlocks;
738 : 0 : for ( const QgsRasterAnalysisUtils::RasterLogicInput &i : std::as_const( mInputs ) )
739 : : {
740 : 0 : if ( feedback->isCanceled() )
741 : 0 : break; //in case some slow data sources are loaded
742 : 0 : for ( int band : i.bands )
743 : : {
744 : 0 : if ( feedback->isCanceled() )
745 : 0 : break; //in case some slow data sources are loaded
746 : 0 : std::unique_ptr< QgsRasterBlock > b( i.interface->block( band, blockExtent, iterCols, iterRows ) );
747 : 0 : inputBlocks.emplace_back( std::move( b ) );
748 : 0 : }
749 : : }
750 : :
751 : 0 : feedback->setProgress( 100 * ( ( iterTop / maxHeight * nbBlocksWidth ) + iterLeft / maxWidth ) / nbBlocks );
752 : 0 : for ( int row = 0; row < iterRows; row++ )
753 : : {
754 : 0 : if ( feedback->isCanceled() )
755 : 0 : break;
756 : :
757 : 0 : for ( int col = 0; col < iterCols; col++ )
758 : : {
759 : 0 : bool percentRankValueIsNoData = false;
760 : 0 : double percentRankValue = valueBlock->valueAndNoData( row, col, percentRankValueIsNoData );
761 : :
762 : 0 : double result = 0;
763 : 0 : bool noDataInStack = false;
764 : 0 : std::vector<double> cellValues = QgsRasterAnalysisUtils::getCellValuesFromBlockStack( inputBlocks, row, col, noDataInStack );
765 : 0 : int cellValueStackSize = cellValues.size();
766 : :
767 : 0 : if ( noDataInStack && !mIgnoreNoData && !percentRankValueIsNoData )
768 : : {
769 : 0 : outputBlock->setValue( row, col, mNoDataValue );
770 : 0 : }
771 : 0 : else if ( !noDataInStack || ( !percentRankValueIsNoData && mIgnoreNoData && cellValueStackSize > 0 ) )
772 : : {
773 : 0 : switch ( mMethod )
774 : : {
775 : : case QgsRasterAnalysisUtils::InterpolatedPercentRankInc:
776 : 0 : result = QgsRasterAnalysisUtils::interpolatedPercentRankInc( cellValues, cellValueStackSize, percentRankValue, mNoDataValue );
777 : 0 : break;
778 : : case QgsRasterAnalysisUtils::InterpolatedPercentRankExc:
779 : 0 : result = QgsRasterAnalysisUtils::interpolatedPercentRankExc( cellValues, cellValueStackSize, percentRankValue, mNoDataValue );
780 : 0 : break;
781 : : }
782 : 0 : outputBlock->setValue( row, col, result );
783 : 0 : }
784 : : else
785 : : {
786 : : //result is NoData if cellValueStack contains no valid values, eg. all cellValues are NoData or percentRankValue is NoData
787 : 0 : outputBlock->setValue( row, col, mNoDataValue );
788 : : }
789 : 0 : }
790 : 0 : }
791 : 0 : mOutputRasterDataProvider->writeBlock( outputBlock.get(), 1, iterLeft, iterTop );
792 : 0 : }
793 : 0 : mOutputRasterDataProvider->setEditable( false );
794 : 0 : }
795 : :
796 : : ///@endcond
797 : :
798 : :
|