QGIS API Documentation 3.41.0-Master (af5edcb665c)
Loading...
Searching...
No Matches
qgsalgorithmrasterlogicalop.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsalgorithmrasterlogicalop.cpp
3 ---------------------
4 begin : March 2019
5 copyright : (C) 2019 by Nyall Dawson
6 email : nyall dot dawson 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 "gdal.h"
20#include "qgsrasterprojector.h"
21#include "qgsrasterfilewriter.h"
23#include <algorithm>
24
26
27
28QStringList QgsRasterBooleanLogicAlgorithmBase::tags() const
29{
30 return QObject::tr( "logical,boolean" ).split( ',' );
31}
32
33QString QgsRasterBooleanLogicAlgorithmBase::group() const
34{
35 return QObject::tr( "Raster analysis" );
36}
37
38QString QgsRasterBooleanLogicAlgorithmBase::groupId() const
39{
40 return QStringLiteral( "rasteranalysis" );
41}
42
43void QgsRasterBooleanLogicAlgorithmBase::initAlgorithm( const QVariantMap & )
44{
45 addParameter( new QgsProcessingParameterMultipleLayers( QStringLiteral( "INPUT" ), QObject::tr( "Input layers" ), Qgis::ProcessingSourceType::Raster ) );
46
47 addParameter( new QgsProcessingParameterRasterLayer( QStringLiteral( "REF_LAYER" ), QObject::tr( "Reference layer" ) ) );
48 addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "NODATA_AS_FALSE" ), QObject::tr( "Treat NoData values as false" ), false ) );
49
50 std::unique_ptr<QgsProcessingParameterNumber> noDataValueParam = std::make_unique<QgsProcessingParameterNumber>( QStringLiteral( "NO_DATA" ), QObject::tr( "Output NoData value" ), Qgis::ProcessingNumberParameterType::Double, -9999 );
51 noDataValueParam->setFlags( Qgis::ProcessingParameterFlag::Advanced );
52 addParameter( noDataValueParam.release() );
53
54 std::unique_ptr<QgsProcessingParameterDefinition> typeChoice = QgsRasterAnalysisUtils::createRasterTypeParameter( QStringLiteral( "DATA_TYPE" ), QObject::tr( "Output data type" ), Qgis::DataType::Float32 );
55 typeChoice->setFlags( Qgis::ProcessingParameterFlag::Advanced );
56 addParameter( typeChoice.release() );
57
58 std::unique_ptr<QgsProcessingParameterString> createOptsParam = std::make_unique<QgsProcessingParameterString>( QStringLiteral( "CREATE_OPTIONS" ), QObject::tr( "Creation options" ), QVariant(), false, true );
59 createOptsParam->setMetadata( QVariantMap( { { QStringLiteral( "widget_wrapper" ), QVariantMap( { { QStringLiteral( "widget_type" ), QStringLiteral( "rasteroptions" ) } } ) } } ) );
60 createOptsParam->setFlags( createOptsParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
61 addParameter( createOptsParam.release() );
62
63 addParameter( new QgsProcessingParameterRasterDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Output layer" ) ) );
64
65 addOutput( new QgsProcessingOutputString( QStringLiteral( "EXTENT" ), QObject::tr( "Extent" ) ) );
66 addOutput( new QgsProcessingOutputString( QStringLiteral( "CRS_AUTHID" ), QObject::tr( "CRS authority identifier" ) ) );
67 addOutput( new QgsProcessingOutputNumber( QStringLiteral( "WIDTH_IN_PIXELS" ), QObject::tr( "Width in pixels" ) ) );
68 addOutput( new QgsProcessingOutputNumber( QStringLiteral( "HEIGHT_IN_PIXELS" ), QObject::tr( "Height in pixels" ) ) );
69 addOutput( new QgsProcessingOutputNumber( QStringLiteral( "TOTAL_PIXEL_COUNT" ), QObject::tr( "Total pixel count" ) ) );
70 addOutput( new QgsProcessingOutputNumber( QStringLiteral( "NODATA_PIXEL_COUNT" ), QObject::tr( "NoData pixel count" ) ) );
71 addOutput( new QgsProcessingOutputNumber( QStringLiteral( "TRUE_PIXEL_COUNT" ), QObject::tr( "True pixel count" ) ) );
72 addOutput( new QgsProcessingOutputNumber( QStringLiteral( "FALSE_PIXEL_COUNT" ), QObject::tr( "False pixel count" ) ) );
73}
74
75bool QgsRasterBooleanLogicAlgorithmBase::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
76{
77 QgsRasterLayer *referenceLayer = parameterAsRasterLayer( parameters, QStringLiteral( "REF_LAYER" ), context );
78 if ( !referenceLayer )
79 throw QgsProcessingException( invalidRasterError( parameters, QStringLiteral( "REF_LAYER" ) ) );
80 mCrs = referenceLayer->crs();
81 mRasterUnitsPerPixelX = referenceLayer->rasterUnitsPerPixelX();
82 mRasterUnitsPerPixelY = referenceLayer->rasterUnitsPerPixelY();
83 mLayerWidth = referenceLayer->width();
84 mLayerHeight = referenceLayer->height();
85 mExtent = referenceLayer->extent();
86 mNoDataValue = parameterAsDouble( parameters, QStringLiteral( "NO_DATA" ), context );
87 mDataType = QgsRasterAnalysisUtils::rasterTypeChoiceToDataType( parameterAsEnum( parameters, QStringLiteral( "DATA_TYPE" ), context ) );
88 if ( mDataType == Qgis::DataType::Int8 && atoi( GDALVersionInfo( "VERSION_NUM" ) ) < GDAL_COMPUTE_VERSION( 3, 7, 0 ) )
89 throw QgsProcessingException( QObject::tr( "Int8 data type requires GDAL version 3.7 or later" ) );
90
91 mTreatNodataAsFalse = parameterAsBoolean( parameters, QStringLiteral( "NODATA_AS_FALSE" ), context );
92
93 const QList<QgsMapLayer *> layers = parameterAsLayerList( parameters, QStringLiteral( "INPUT" ), context );
94 QList<QgsRasterLayer *> rasterLayers;
95 rasterLayers.reserve( layers.count() );
96 for ( QgsMapLayer *l : layers )
97 {
98 if ( l->type() == Qgis::LayerType::Raster )
99 {
100 QgsRasterLayer *layer = qobject_cast<QgsRasterLayer *>( l );
101 QgsRasterAnalysisUtils::RasterLogicInput input;
102 const int band = 1; // hardcoded for now - needs a way to supply this in the processing gui
103 input.hasNoDataValue = layer->dataProvider()->sourceHasNoDataValue( band );
104 input.sourceDataProvider.reset( layer->dataProvider()->clone() );
105 input.interface = input.sourceDataProvider.get();
106 // add projector if necessary
107 if ( layer->crs() != mCrs )
108 {
109 input.projector = std::make_unique<QgsRasterProjector>();
110 input.projector->setInput( input.sourceDataProvider.get() );
111 input.projector->setCrs( layer->crs(), mCrs, context.transformContext() );
112 input.interface = input.projector.get();
113 }
114 mInputs.emplace_back( std::move( input ) );
115 }
116 }
117
118 return true;
119}
120
121QVariantMap QgsRasterBooleanLogicAlgorithmBase::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
122{
123 const QString createOptions = parameterAsString( parameters, QStringLiteral( "CREATE_OPTIONS" ), context ).trimmed();
124 const QString outputFile = parameterAsOutputLayer( parameters, QStringLiteral( "OUTPUT" ), context );
125 const QFileInfo fi( outputFile );
126 const QString outputFormat = QgsRasterFileWriter::driverForExtension( fi.suffix() );
127
128 std::unique_ptr<QgsRasterFileWriter> writer = std::make_unique<QgsRasterFileWriter>( outputFile );
129 writer->setOutputProviderKey( QStringLiteral( "gdal" ) );
130 if ( !createOptions.isEmpty() )
131 {
132 writer->setCreateOptions( createOptions.split( '|' ) );
133 }
134 writer->setOutputFormat( outputFormat );
135 std::unique_ptr<QgsRasterDataProvider> provider( writer->createOneBandRaster( mDataType, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
136 if ( !provider )
137 throw QgsProcessingException( QObject::tr( "Could not create raster output: %1" ).arg( outputFile ) );
138 if ( !provider->isValid() )
139 throw QgsProcessingException( QObject::tr( "Could not create raster output %1: %2" ).arg( outputFile, provider->error().message( QgsErrorMessage::Text ) ) );
140
141 provider->setNoDataValue( 1, mNoDataValue );
142 qgssize noDataCount = 0;
143 qgssize trueCount = 0;
144 qgssize falseCount = 0;
145 const qgssize layerSize = static_cast<qgssize>( mLayerWidth ) * static_cast<qgssize>( mLayerHeight );
146
147 QgsRasterAnalysisUtils::applyRasterLogicOperator( mInputs, provider.get(), mNoDataValue, mTreatNodataAsFalse, mLayerWidth, mLayerHeight, mExtent, feedback, mExtractValFunc, noDataCount, trueCount, falseCount );
148
149 QVariantMap outputs;
150 outputs.insert( QStringLiteral( "EXTENT" ), mExtent.toString() );
151 outputs.insert( QStringLiteral( "CRS_AUTHID" ), mCrs.authid() );
152 outputs.insert( QStringLiteral( "WIDTH_IN_PIXELS" ), mLayerWidth );
153 outputs.insert( QStringLiteral( "HEIGHT_IN_PIXELS" ), mLayerHeight );
154 outputs.insert( QStringLiteral( "TOTAL_PIXEL_COUNT" ), layerSize );
155 outputs.insert( QStringLiteral( "NODATA_PIXEL_COUNT" ), noDataCount );
156 outputs.insert( QStringLiteral( "TRUE_PIXEL_COUNT" ), trueCount );
157 outputs.insert( QStringLiteral( "FALSE_PIXEL_COUNT" ), falseCount );
158 outputs.insert( QStringLiteral( "OUTPUT" ), outputFile );
159
160 return outputs;
161}
162
163
164//
165// QgsRasterLogicalOrAlgorithm
166//
167
168QgsRasterLogicalOrAlgorithm::QgsRasterLogicalOrAlgorithm()
169{
170 mExtractValFunc = [=]( const std::vector<std::unique_ptr<QgsRasterBlock>> &inputs, bool &res, bool &resIsNoData, int row, int column, bool treatNoDataAsFalse ) {
171 res = false;
172 resIsNoData = false;
173 bool isNoData = false;
174 for ( auto &block : inputs )
175 {
176 double value = 0;
177 if ( !block || !block->isValid() )
178 {
179 if ( treatNoDataAsFalse )
180 continue;
181 else
182 {
183 resIsNoData = true;
184 break;
185 }
186 }
187 else
188 {
189 value = block->valueAndNoData( row, column, isNoData );
190 if ( isNoData && !treatNoDataAsFalse )
191 {
192 resIsNoData = true;
193 break;
194 }
195 else if ( !qgsDoubleNear( value, 0.0 ) && !isNoData )
196 {
197 res = true;
198 if ( treatNoDataAsFalse ) // otherwise we need to check all remaining rasters for nodata
199 break;
200 }
201 }
202 }
203 };
204}
205
206QString QgsRasterLogicalOrAlgorithm::name() const
207{
208 return QStringLiteral( "rasterlogicalor" );
209}
210
211QString QgsRasterLogicalOrAlgorithm::displayName() const
212{
213 return QObject::tr( "Raster boolean OR" );
214}
215
216
217QString QgsRasterLogicalOrAlgorithm::shortDescription() const
218{
219 return QObject::tr( "Calculates the boolean OR for a set of input raster layers" );
220}
221
222QString QgsRasterLogicalOrAlgorithm::shortHelpString() const
223{
224 return QObject::tr( "This algorithm calculates the boolean OR for a set of input rasters. If any of the input rasters have a non-zero value for a pixel, "
225 "that pixel will be set to 1 in the output raster. If all the input rasters have 0 values for the pixel it will be set to 0 in the output raster.\n\n"
226 "The reference layer parameter specifies an existing raster layer to use as a reference when creating the output raster. The output raster "
227 "will have the same extent, CRS, and pixel dimensions as this layer.\n\n"
228 "By default, a NoData pixel in ANY of the input layers will result in a NoData pixel in the output raster. If the "
229 "'Treat NoData values as false' option is checked, then NoData inputs will be treated the same as a 0 input value." );
230}
231
232QgsRasterLogicalOrAlgorithm *QgsRasterLogicalOrAlgorithm::createInstance() const
233{
234 return new QgsRasterLogicalOrAlgorithm();
235}
236
237//
238// QgsRasterLogicalAndAlgorithm
239//
240
241QgsRasterLogicalAndAlgorithm::QgsRasterLogicalAndAlgorithm()
242{
243 mExtractValFunc = [=]( const std::vector<std::unique_ptr<QgsRasterBlock>> &inputs, bool &res, bool &resIsNoData, int row, int column, bool treatNoDataAsFalse ) {
244 res = true;
245 resIsNoData = false;
246 bool isNoData = false;
247 for ( auto &block : inputs )
248 {
249 double value = 0;
250 if ( !block || !block->isValid() )
251 {
252 if ( treatNoDataAsFalse )
253 {
254 res = false;
255 break;
256 }
257 else
258 {
259 resIsNoData = true;
260 break;
261 }
262 }
263 else
264 {
265 value = block->valueAndNoData( row, column, isNoData );
266 if ( isNoData && !treatNoDataAsFalse )
267 {
268 resIsNoData = true;
269 break;
270 }
271 else if ( qgsDoubleNear( value, 0.0 ) || isNoData )
272 {
273 res = false;
274 if ( treatNoDataAsFalse ) // otherwise we need to check remaining rasters for nodata
275 break;
276 }
277 }
278 }
279 };
280}
281
282QString QgsRasterLogicalAndAlgorithm::name() const
283{
284 return QStringLiteral( "rasterbooleanand" );
285}
286
287QString QgsRasterLogicalAndAlgorithm::displayName() const
288{
289 return QObject::tr( "Raster boolean AND" );
290}
291
292
293QString QgsRasterLogicalAndAlgorithm::shortDescription() const
294{
295 return QObject::tr( "Calculates the boolean AND for a set of input raster layers" );
296}
297
298QString QgsRasterLogicalAndAlgorithm::shortHelpString() const
299{
300 return QObject::tr( "This algorithm calculates the boolean AND for a set of input rasters. If all of the input rasters have a non-zero value for a pixel, "
301 "that pixel will be set to 1 in the output raster. If any of the input rasters have 0 values for the pixel it will be set to 0 in the output raster.\n\n"
302 "The reference layer parameter specifies an existing raster layer to use as a reference when creating the output raster. The output raster "
303 "will have the same extent, CRS, and pixel dimensions as this layer.\n\n"
304 "By default, a NoData pixel in ANY of the input layers will result in a NoData pixel in the output raster. If the "
305 "'Treat NoData values as false' option is checked, then NoData inputs will be treated the same as a 0 input value." );
306}
307
308QgsRasterLogicalAndAlgorithm *QgsRasterLogicalAndAlgorithm::createInstance() const
309{
310 return new QgsRasterLogicalAndAlgorithm();
311}
312
@ Float32
Thirty two bit floating point (float)
@ Int8
Eight bit signed integer (qint8) (added in QGIS 3.30)
@ Raster
Raster layer.
@ Advanced
Parameter is an advanced parameter which should be hidden from users by default.
Base class for all map layer types.
Definition qgsmaplayer.h:76
virtual QgsRectangle extent() const
Returns the extent of the layer.
QgsCoordinateReferenceSystem crs
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.
A numeric output for processing algorithms.
A string output for processing algorithms.
A boolean parameter for processing algorithms.
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 ...
A raster layer parameter for processing algorithms.
QgsRasterDataProvider * clone() const override=0
Clone itself, create deep copy.
virtual bool sourceHasNoDataValue(int bandNo) const
Returns true if source band has no data value.
static QString driverForExtension(const QString &extension)
Returns the GDAL driver name for a specified file extension.
Represents a raster layer.
int height() const
Returns the height of the (unclipped) raster.
double rasterUnitsPerPixelX() const
Returns the number of raster units per each raster pixel in X axis.
QgsRasterDataProvider * dataProvider() override
Returns the source data provider.
double rasterUnitsPerPixelY() const
Returns the number of raster units per each raster pixel in Y axis.
int width() const
Returns the width of the (unclipped) raster.
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:6614
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:6066