QGIS API Documentation 3.99.0-Master (357b655ed83)
Loading...
Searching...
No Matches
qgsalgorithmrasterfeaturepreservingsmoothing.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsalgorithmrasterfeaturepreservingsmoothing.cpp
3 ---------------------
4 begin : December 2025
5 copyright : (C) 2025 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
19
20#include "qgsrasterfilewriter.h"
21
22#include <QString>
23
24using namespace Qt::StringLiterals;
25
27
28struct Vector2D
29{
30 double x = 0;
31 double y = 0;
32
33 Vector2D( double x = 0, double y = 0 )
34 : x( x )
35 , y( y )
36 {}
37
38 double angleBetweenCos( const Vector2D &other ) const
39 {
40 const double dot = x * other.x + y * other.y + 1.0;
41 const double magSelf = ( x * x + y * y + 1.0 );
42 const double magOther = ( other.x * other.x + other.y * other.y + 1.0 );
43 return dot / std::sqrt( magSelf * magOther );
44 }
45};
46
47QString QgsRasterFeaturePreservingSmoothingAlgorithm::name() const
48{
49 return u"rasterfeaturepreservingsmoothing"_s;
50}
51
52QString QgsRasterFeaturePreservingSmoothingAlgorithm::displayName() const
53{
54 return QObject::tr( "Feature preserving DEM smoothing" );
55}
56
57QStringList QgsRasterFeaturePreservingSmoothingAlgorithm::tags() const
58{
59 return QObject::tr( "smooth,filter,denoise,fpdems,blur" ).split( ',' );
60}
61
62QString QgsRasterFeaturePreservingSmoothingAlgorithm::group() const
63{
64 return QObject::tr( "Raster analysis" );
65}
66
67QString QgsRasterFeaturePreservingSmoothingAlgorithm::groupId() const
68{
69 return u"rasteranalysis"_s;
70}
71
72QString QgsRasterFeaturePreservingSmoothingAlgorithm::shortHelpString() const
73{
74 return QObject::tr( "This algorithm applies the Feature-Preserving DEM Smoothing (FPDEMS) method, as described by Lindsay et al. (2019).\n\n"
75 "It is effective at removing surface roughness from Digital Elevation Models (DEMs) without significantly altering sharp features such as breaks-in-slope, stream banks, or terrace scarps. "
76 "This makes it superior to standard low-pass filters (e.g., mean, median, Gaussian) or resampling, which often blur distinct topographic features.\n\n"
77 "The algorithm works in three steps:\n"
78 "1. Calculating surface normal 3D vectors for each grid cell.\n"
79 "2. Smoothing the normal vector field using a filter that applies more weight to neighbors with similar surface normals (preserving edges).\n"
80 "3. Iteratively updating the elevations in the DEM to match the smoothed normal field.\n\n"
81 "References: Lindsay, John et al (2019): LiDAR DEM Smoothing and the Preservation of Drainage Features. Remote Sensing, Vol. 11, Issue 16, https://doi.org/10.3390/rs11161926"
82 );
83}
84
85QString QgsRasterFeaturePreservingSmoothingAlgorithm::shortDescription() const
86{
87 return QObject::tr( "Smooths a DEM while preserving topographic features." );
88}
89
90void QgsRasterFeaturePreservingSmoothingAlgorithm::initAlgorithm( const QVariantMap & )
91{
92 addParameter( new QgsProcessingParameterRasterLayer( u"INPUT"_s, QObject::tr( "Input layer" ) ) );
93
94 addParameter( new QgsProcessingParameterBand( u"BAND"_s, QObject::tr( "Band number" ), 1, u"INPUT"_s ) );
95
96 auto radiusParam = std::make_unique<QgsProcessingParameterNumber>( u"RADIUS"_s, QObject::tr( "Filter radius (pixels)" ), Qgis::ProcessingNumberParameterType::Integer, 5, false, 1, 50 );
97 radiusParam->setHelp( QObject::tr( "Radius of the filter kernel. A radius of 5 results in an 11x11 kernel." ) );
98 addParameter( radiusParam.release() );
99
100 auto thresholdParam = std::make_unique<QgsProcessingParameterNumber>( u"THRESHOLD"_s, QObject::tr( "Normal difference threshold (degrees)" ), Qgis::ProcessingNumberParameterType::Double, 15.0, false, 0.0, 90.0 );
101 thresholdParam->setHelp( QObject::tr( "Maximum angular difference (in degrees) between the normal vector of the center cell and a neighbor for the neighbor to be included in the filter. Higher values result in more neighbors being included, producing smoother surfaces. A range of 10-20 degrees is typically optimal." ) );
102 addParameter( thresholdParam.release() );
103
104 auto iterParam = std::make_unique<QgsProcessingParameterNumber>( u"ITERATIONS"_s, QObject::tr( "Elevation update iterations" ), Qgis::ProcessingNumberParameterType::Integer, 3, false, 1, 50 );
105 iterParam->setHelp( QObject::tr( "Number of times the smoothing process (elevation update) is repeated. Increasing this value from the default of 3 will result in significantly greater smoothing." ) );
106 addParameter( iterParam.release() );
107
108 auto maxDiffParam = std::make_unique<QgsProcessingParameterNumber>( u"MAX_ELEVATION_CHANGE"_s, QObject::tr( "Maximum elevation change" ), Qgis::ProcessingNumberParameterType::Double, QVariant(), true, 0.0 );
109 maxDiffParam->setHelp( QObject::tr( "The allowed maximum height change of any cell in one iteration. If the calculated change exceeds this value, the elevation remains unchanged. This prevents excessive deviation from the original surface." ) );
110 addParameter( maxDiffParam.release() );
111
112 auto zFactorParam = std::make_unique<QgsProcessingParameterNumber>( u"Z_FACTOR"_s, QObject::tr( "Z factor" ), Qgis::ProcessingNumberParameterType::Double, 1.0, false, 0.00000001 );
113 zFactorParam->setHelp( QObject::tr( "Multiplication factor to convert vertical Z units to horizontal XY units." ) );
114 zFactorParam->setFlags( zFactorParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
115 zFactorParam->setMetadata(
116 { QVariantMap( { { u"widget_wrapper"_s, QVariantMap( { { u"decimals"_s, 12 } } ) } } )
117 }
118 );
119 addParameter( zFactorParam.release() );
120
121 auto creationOptsParam = std::make_unique<QgsProcessingParameterString>( u"CREATION_OPTIONS"_s, QObject::tr( "Creation options" ), QVariant(), false, true );
122 creationOptsParam->setMetadata( QVariantMap( { { u"widget_wrapper"_s, QVariantMap( { { u"widget_type"_s, u"rasteroptions"_s } } ) } } ) );
123 creationOptsParam->setFlags( creationOptsParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
124 addParameter( creationOptsParam.release() );
125
126 auto outputLayerParam = std::make_unique<QgsProcessingParameterRasterDestination>( u"OUTPUT"_s, QObject::tr( "Output layer" ) );
127 addParameter( outputLayerParam.release() );
128}
129
130QgsRasterFeaturePreservingSmoothingAlgorithm *QgsRasterFeaturePreservingSmoothingAlgorithm::createInstance() const
131{
132 return new QgsRasterFeaturePreservingSmoothingAlgorithm();
133}
134
135bool QgsRasterFeaturePreservingSmoothingAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
136{
137 QgsRasterLayer *layer = parameterAsRasterLayer( parameters, u"INPUT"_s, context );
138 if ( !layer )
139 throw QgsProcessingException( invalidRasterError( parameters, u"INPUT"_s ) );
140
141 mBand = parameterAsInt( parameters, u"BAND"_s, context );
142 if ( mBand < 1 || mBand > layer->bandCount() )
143 throw QgsProcessingException( QObject::tr( "Invalid band number for BAND (%1): Valid values for input raster are 1 to %2" ).arg( mBand ).arg( layer->bandCount() ) );
144
145 mInterface.reset( layer->dataProvider()->clone() );
146 mLayerWidth = layer->width();
147 mLayerHeight = layer->height();
148 mExtent = layer->extent();
149 mCrs = layer->crs();
150 mRasterUnitsPerPixelX = layer->rasterUnitsPerPixelX();
151 mRasterUnitsPerPixelY = layer->rasterUnitsPerPixelY();
152 mDataType = layer->dataProvider()->dataType( mBand );
153 mNoData = layer->dataProvider()->sourceNoDataValue( mBand );
154 return true;
155}
156
157QVariantMap QgsRasterFeaturePreservingSmoothingAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
158{
159 const int radius = parameterAsInt( parameters, u"RADIUS"_s, context );
160
161 const double thresholdDegrees = parameterAsDouble( parameters, u"THRESHOLD"_s, context );
162 const double cosThreshold = std::cos( thresholdDegrees * M_PI / 180.0 );
163
164 const int iterations = parameterAsInt( parameters, u"ITERATIONS"_s, context );
165 const bool hasMaxElevChange = parameters.value( u"MAX_ELEVATION_CHANGE"_s ).isValid();
166 const double maxElevChange = hasMaxElevChange ? parameterAsDouble( parameters, u"MAX_ELEVATION_CHANGE"_s, context ) : 0;
167 const double zFactor = parameterAsDouble( parameters, u"Z_FACTOR"_s, context );
168
169 const double oneOver8ResX = 1 / ( 8.0 * mRasterUnitsPerPixelX );
170 const double oneOver8ResY = 1 / ( 8.0 * mRasterUnitsPerPixelY );
171
172 const QString creationOptions = parameterAsString( parameters, u"CREATION_OPTIONS"_s, context ).trimmed();
173 const QString outputFile = parameterAsOutputLayer( parameters, u"OUTPUT"_s, context );
174 const QString outputFormat = parameterAsOutputRasterFormat( parameters, u"OUTPUT"_s, context );
175
176 auto outputWriter = std::make_unique<QgsRasterFileWriter>( outputFile );
177 outputWriter->setOutputProviderKey( u"gdal"_s );
178 if ( !creationOptions.isEmpty() )
179 {
180 outputWriter->setCreationOptions( creationOptions.split( '|' ) );
181 }
182 outputWriter->setOutputFormat( outputFormat );
183
184 std::unique_ptr<QgsRasterDataProvider> destProvider( outputWriter->createOneBandRaster( mDataType, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
185 if ( !destProvider )
186 throw QgsProcessingException( QObject::tr( "Could not create raster output: %1" ).arg( outputFile ) );
187 if ( !destProvider->isValid() )
188 throw QgsProcessingException( QObject::tr( "Could not create raster output %1: %2" ).arg( outputFile, destProvider->error().message( QgsErrorMessage::Text ) ) );
189
190 destProvider->setNoDataValue( 1, mNoData );
191 destProvider->setEditable( true );
192
193 // block iteration padding must be large enough to cover all 3 steps, i.e. 1px (for normal calculation) + radius in pixels (for smoothing) + 1px for each iteration
194 const int blockPadding = 1 + radius + iterations;
195 QgsRasterIterator iter( mInterface.get(), blockPadding );
196 iter.startRasterRead( mBand, mLayerWidth, mLayerHeight, mExtent );
197
198 int iterCols = 0;
199 int iterRows = 0;
200 int iterLeft = 0;
201 int iterTop = 0;
202 int tileCols = 0;
203 int tileRows = 0;
204 int tileLeft = 0;
205 int tileTop = 0;
206 QgsRectangle blockExtent;
207
208 std::unique_ptr<QgsRasterBlock> inputBlock;
209
210 // buffers are re-used across different tiles to minimize allocations.
211 // Resize them in advance to the largest possible required sizes.
212 const size_t maxBufferSize = static_cast<std::size_t>( iter.maximumTileWidth() + 2 * blockPadding ) * static_cast< std::size_t >( iter.maximumTileHeight() + 2 * blockPadding );
213 // pixel normals, using SCALED z values (accounting for z-factor)
214 std::vector<Vector2D> normalBuffer( maxBufferSize );
215 std::vector<Vector2D> smoothedNormalBuffer( maxBufferSize );
216 std::vector<double> zBuffer( maxBufferSize );
217 std::vector<qint8> noDataBuffer( maxBufferSize );
218
219 bool isNoData = false;
220
221 const bool hasReportsDuringClose = destProvider->hasReportsDuringClose();
222 const double maxProgressDuringBlockWriting = hasReportsDuringClose ? 50.0 : 100.0;
223
224 while ( iter.readNextRasterPart( mBand, iterCols, iterRows, inputBlock, iterLeft, iterTop, &blockExtent, &tileCols, &tileRows, &tileLeft, &tileTop ) )
225 {
226 if ( feedback->isCanceled() )
227 break;
228 feedback->setProgress( maxProgressDuringBlockWriting * iter.progress( mBand, 0 ) );
229 feedback->setProgressText( QObject::tr( "Calculating surface normals" ) );
230
231 // copy raster values and NoData to buffers once in advance -- we will be retrieving
232 // each individual value many times during surface normal calculation, so the cost
233 // of this upfront step pays off...
234 double *zBufferData = zBuffer.data();
235 qint8 *noDataBufferData = noDataBuffer.data();
236 for ( int r = 0; r < iterRows; ++r )
237 {
238 for ( int c = 0; c < iterCols; ++c )
239 {
240 *zBufferData++ = inputBlock->valueAndNoData( r, c, isNoData ) * zFactor;
241 *noDataBufferData++ = isNoData ? 1 : 0;
242 }
243 }
244
245 // step 1: calculate surface normals
246 // we follow Lindsay's rust implementation of FPDEMS, and consider out-of-range pixels and no-data pixels
247 // as the center pixel value when calculating the normal over the 3x3 matrix window.
248
249 // if neighbor is out of range or is NoData, follow Lindsay and use center z
250 auto getZ = [iterRows, iterCols, &noDataBuffer, &zBuffer]( int row, int col, double z ) -> double {
251 if ( row < 0 || row >= iterRows || col < 0 || col >= iterCols )
252 return z;
253 std::size_t idx = static_cast<std::size_t>( row ) * iterCols + col;
254 if ( noDataBuffer[idx] )
255 return z;
256 return zBuffer[idx];
257 };
258
259 for ( int r = 0; r < iterRows; ++r )
260 {
261 if ( feedback->isCanceled() )
262 break;
263
264 feedback->setProgress( maxProgressDuringBlockWriting * iter.progress( mBand, r / static_cast< double >( iterRows ) / 3.0 ) );
265
266 for ( int c = 0; c < iterCols; ++c )
267 {
268 const std::size_t idx = static_cast<std::size_t>( r ) * iterCols + c;
269 if ( noDataBuffer[idx] )
270 {
271 normalBuffer[idx] = Vector2D( 0, 0 );
272 continue;
273 }
274
275 const double z = zBuffer[idx];
276 const double z1 = getZ( r - 1, c - 1, z );
277 const double z2 = getZ( r - 1, c, z );
278 const double z3 = getZ( r - 1, c + 1, z );
279 const double z4 = getZ( r, c - 1, z );
280 const double z6 = getZ( r, c + 1, z );
281 const double z7 = getZ( r + 1, c - 1, z );
282 const double z8 = getZ( r + 1, c, z );
283 const double z9 = getZ( r + 1, c + 1, z );
284
285 // Horn 1981, adjusting for raster units per pixel
286 const double dx = ( ( z3 - z1 + 2 * ( z6 - z4 ) + z9 - z7 ) * oneOver8ResX );
287 const double dy = ( ( z7 - z1 + 2 * ( z8 - z2 ) + z9 - z3 ) * oneOver8ResY );
288 normalBuffer[idx] = Vector2D( -dx, dy );
289 }
290 }
291 if ( feedback->isCanceled() )
292 break;
293
294 // step 2: smooth normals
295 feedback->setProgressText( QObject::tr( "Smoothing surface normals" ) );
296 for ( int r = 0; r < iterRows; ++r )
297 {
298 if ( feedback->isCanceled() )
299 break;
300
301 feedback->setProgress( maxProgressDuringBlockWriting * iter.progress( mBand, 1.0 / 3.0 + r / static_cast< double >( iterRows ) / 3.0 ) );
302
303 for ( int c = 0; c < iterCols; ++c )
304 {
305 const std::size_t idx = static_cast<std::size_t>( r ) * iterCols + c;
306 if ( noDataBuffer[idx] )
307 continue;
308
309 const Vector2D &centerNormal = normalBuffer[idx];
310 double summedWeights = 0.0;
311 double summedX = 0;
312 double summedY = 0;
313
314 for ( int kernelY = -radius; kernelY <= radius; ++kernelY )
315 {
316 for ( int kernelX = -radius; kernelX <= radius; ++kernelX )
317 {
318 const int pixelRow = r + kernelY;
319 const int pixelCol = c + kernelX;
320
321 // skip pixels outside range, nodata pixels
322 if ( pixelRow < 0 || pixelRow >= iterRows || pixelCol < 0 || pixelCol >= iterCols )
323 continue;
324 const std::size_t pixelIdx = static_cast<std::size_t>( pixelRow ) * iterCols + pixelCol;
325 if ( noDataBuffer[pixelIdx] )
326 continue;
327
328 const Vector2D &neighNormal = normalBuffer[pixelIdx];
329
330 const double cosAngle = centerNormal.angleBetweenCos( neighNormal );
331 if ( cosAngle > cosThreshold )
332 {
333 const double w = ( cosAngle - cosThreshold ) * ( cosAngle - cosThreshold );
334 summedWeights += w;
335 summedX += w * neighNormal.x;
336 summedY += w * neighNormal.y;
337 }
338 }
339 }
340 if ( !qgsDoubleNear( summedWeights, 0.0 ) )
341 {
342 summedX /= summedWeights;
343 summedY /= summedWeights;
344 }
345 smoothedNormalBuffer[idx] = Vector2D( summedX, summedY );
346 }
347 }
348 if ( feedback->isCanceled() )
349 break;
350
351 // step 3: update elevation
352 // pixel offsets
353 const int dx[8] = { 1, 1, 1, 0, -1, -1, -1, 0 };
354 const int dy[8] = { -1, 0, 1, 1, 1, 0, -1, -1 };
355
356 // world offsets (accounting for units per pixel)
357 const double worldX[8] = { -mRasterUnitsPerPixelX, -mRasterUnitsPerPixelX, -mRasterUnitsPerPixelX, 0.0, mRasterUnitsPerPixelX, mRasterUnitsPerPixelX, mRasterUnitsPerPixelX, 0.0 };
358 const double worldY[8] = { -mRasterUnitsPerPixelY, 0.0, mRasterUnitsPerPixelY, mRasterUnitsPerPixelY, mRasterUnitsPerPixelY, 0.0, -mRasterUnitsPerPixelY, -mRasterUnitsPerPixelY };
359
360 for ( int iteration = 0; iteration < iterations; ++iteration )
361 {
362 feedback->setProgressText( QObject::tr( "Updating elevation (iteration %1/%2)" ).arg( iteration + 1 ).arg( iterations ) );
363 if ( feedback->isCanceled() )
364 break;
365
366 for ( int r = 0; r < iterRows; ++r )
367 {
368 feedback->setProgress( maxProgressDuringBlockWriting * iter.progress( mBand, 2.0 / 3.0 + ( ( static_cast< double >( iteration ) / iterations ) + ( r / static_cast< double >( iterRows ) ) / iterations ) / 3.0 ) );
369 for ( int c = 0; c < iterCols; ++c )
370 {
371 const std::size_t idx = static_cast<std::size_t>( r ) * iterCols + c;
372 if ( noDataBuffer[idx] )
373 continue;
374
375 const double originalZ = inputBlock->value( r, c );
376
377 const Vector2D &centerNormal = smoothedNormalBuffer[idx];
378 double sumWeights = 0.0;
379 double sumZ = 0.0;
380 for ( int n = 0; n < 8; ++n )
381 {
382 const int pixelX = c + dx[n];
383 const int pixelY = r + dy[n];
384 if ( pixelX < 0 || pixelX >= iterCols || pixelY < 0 || pixelY >= iterRows )
385 continue;
386
387 const std::size_t nIdx = static_cast<std::size_t>( pixelY ) * iterCols + pixelX;
388 if ( noDataBuffer[nIdx] )
389 continue;
390
391 const double pixelZ = zBuffer[nIdx];
392
393 const Vector2D &neighNormal = smoothedNormalBuffer[nIdx];
394 const double cosAngle = centerNormal.angleBetweenCos( neighNormal );
395 if ( cosAngle > cosThreshold )
396 {
397 const double w = ( cosAngle - cosThreshold ) * ( cosAngle - cosThreshold );
398 sumWeights += w;
399 sumZ += -( neighNormal.x * worldX[n] + neighNormal.y * worldY[n] - pixelZ ) * w;
400 }
401 }
402
403 if ( sumWeights > 0.0 )
404 {
405 const double newZScaled = ( sumZ / sumWeights );
406 const double newZUnscaled = newZScaled / zFactor;
407 if ( !hasMaxElevChange || std::abs( newZUnscaled - originalZ ) <= maxElevChange )
408 {
409 zBuffer[idx] = newZScaled;
410 }
411 else
412 {
413 zBuffer[idx] = originalZ * zFactor;
414 }
415 }
416 else
417 {
418 zBuffer[idx] = originalZ * zFactor;
419 }
420 }
421 }
422 }
423
424 const int blockOffsetX = tileLeft - iterLeft;
425 const int blockOffsetY = tileTop - iterTop;
426
427 auto outputBlock = std::make_unique<QgsRasterBlock>( mDataType, tileCols, tileRows );
428
429 for ( int r = 0; r < tileRows; ++r )
430 {
431 for ( int c = 0; c < tileCols; ++c )
432 {
433 const int pixelRow = r + blockOffsetY;
434 const int pixelCol = c + blockOffsetX;
435
436 const std::size_t idx = static_cast<std::size_t>( pixelRow ) * iterCols + pixelCol;
437 if ( noDataBuffer[idx] )
438 outputBlock->setValue( r, c, mNoData );
439 else
440 outputBlock->setValue( r, c, zBuffer[idx] / zFactor );
441 }
442 }
443
444 if ( !destProvider->writeBlock( outputBlock.get(), 1, tileLeft, tileTop ) )
445 {
446 throw QgsProcessingException( QObject::tr( "Could not write raster block: %1" ).arg( destProvider->error().summary() ) );
447 }
448 }
449 destProvider->setEditable( false );
450
451 if ( feedback && hasReportsDuringClose )
452 {
453 std::unique_ptr<QgsFeedback> scaledFeedback( QgsFeedback::createScaledFeedback( feedback, maxProgressDuringBlockWriting, 100.0 ) );
454 if ( !destProvider->closeWithProgress( scaledFeedback.get() ) )
455 {
456 if ( feedback->isCanceled() )
457 return {};
458 throw QgsProcessingException( QObject::tr( "Could not write raster dataset" ) );
459 }
460 }
461
462 QVariantMap outputs;
463 outputs.insert( u"OUTPUT"_s, outputFile );
464 return outputs;
465}
466
467
@ Advanced
Parameter is an advanced parameter which should be hidden from users by default.
Definition qgis.h:3834
@ Double
Double/float values.
Definition qgis.h:3875
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:55
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:63
static std::unique_ptr< QgsFeedback > createScaledFeedback(QgsFeedback *parentFeedback, double startPercentage, double endPercentage)
Returns a feedback object whose [0, 100] progression range will be mapped to parentFeedback [startPer...
virtual QgsRectangle extent() const
Returns the extent of the layer.
QgsCoordinateReferenceSystem crs
Definition qgsmaplayer.h:90
Contains information about the context in which a processing algorithm is executed.
Custom exception class for processing related exceptions.
Base class for providing feedback from a processing algorithm.
virtual void setProgressText(const QString &text)
Sets a progress report text string.
A raster band parameter for Processing algorithms.
A raster layer parameter for processing algorithms.
QgsRasterDataProvider * clone() const override=0
Clone itself, create deep copy.
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.
Iterator for sequentially processing raster cells.
Represents a raster layer.
int height() const
Returns the height of the (unclipped) raster.
int bandCount() const
Returns the number of bands in this layer.
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.
A rectangle specified with double values.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6935