QGIS API Documentation 4.3.0-Master (7c7bd4d7018)
Loading...
Searching...
No Matches
qgsrelief.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsrelief.cpp - description
3 ---------------------------
4 begin : November 2011
5 copyright : (C) 2011 by Marco Hugentobler
6 email : marco dot hugentobler at sourcepole 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
18#include "qgsrelief.h"
19
20#include <cfloat>
21#include <cpl_string.h>
22
23#include "qgis.h"
24#include "qgsaspectfilter.h"
25#include "qgsfeedback.h"
26#include "qgsgdalutils.h"
27#include "qgshillshadefilter.h"
28#include "qgslogger.h"
29#include "qgsrasterfilewriter.h"
30#include "qgsrasterlayer.h"
31#include "qgsslopefilter.h"
32
33#include <QColor>
34#include <QFile>
35#include <QString>
36#include <QTextStream>
37#include <QVector>
38
39using namespace Qt::StringLiterals;
40
41QgsRelief::QgsRelief( const QString &inputFile, const QString &outputFile, const QString &outputFormat )
42 : mInputFile( inputFile )
43 , mOutputFile( outputFile )
44 , mOutputFormat( outputFormat )
45 , mSlopeFilter( std::make_unique<QgsSlopeFilter>( inputFile, outputFile, outputFormat ) )
46 , mAspectFilter( std::make_unique<QgsAspectFilter>( inputFile, outputFile, outputFormat ) )
47 , mHillshadeFilter285( std::make_unique<QgsHillshadeFilter>( inputFile, outputFile, outputFormat, 285, 30 ) )
48 , mHillshadeFilter300( std::make_unique<QgsHillshadeFilter>( inputFile, outputFile, outputFormat, 300, 30 ) )
49 , mHillshadeFilter315( std::make_unique<QgsHillshadeFilter>( inputFile, outputFile, outputFormat, 315, 30 ) )
50{
51 /*mReliefColors = calculateOptimizedReliefClasses();
52 setDefaultReliefColors();*/
53}
54
55QgsRelief::~QgsRelief() = default;
56
58{
59 mReliefColors.clear();
60}
61
63{
64 mReliefColors.push_back( color );
65}
66
67void QgsRelief::setDefaultReliefColors()
68{
70 addReliefColorClass( QgsRasterReliefColor( QColor( 9, 176, 76 ), 0, 200 ) );
71 addReliefColorClass( QgsRasterReliefColor( QColor( 20, 228, 128 ), 200, 500 ) );
72 addReliefColorClass( QgsRasterReliefColor( QColor( 167, 239, 153 ), 500, 1000 ) );
73 addReliefColorClass( QgsRasterReliefColor( QColor( 218, 188, 143 ), 1000, 2000 ) );
74 addReliefColorClass( QgsRasterReliefColor( QColor( 233, 158, 91 ), 2000, 4000 ) );
75 addReliefColorClass( QgsRasterReliefColor( QColor( 255, 255, 255 ), 4000, 9000 ) );
76}
77
79{
80 auto inputLayer = std::make_unique< QgsRasterLayer >( mInputFile, u"relief"_s, u"gdal"_s );
81 if ( !inputLayer->isValid() )
82 {
84 }
85 QgsRasterDataProvider *inputProvider = inputLayer->dataProvider();
86 if ( !inputProvider )
87 {
89 }
90
91 const int xSize = inputProvider->xSize();
92 const int ySize = inputProvider->ySize();
93 mCellSizeX = std::fabs( inputLayer->rasterUnitsPerPixelX() );
94 mCellSizeY = std::fabs( inputLayer->rasterUnitsPerPixelY() );
95
96 //output driver
97 QgsRasterFileWriter writer( mOutputFile );
98 writer.setOutputFormat( mOutputFormat );
99 std::unique_ptr<QgsRasterDataProvider> outputProvider( writer.createMultiBandRaster( Qgis::DataType::Byte, xSize, ySize, inputProvider->extent(), inputProvider->crs(), 3 ) );
100 if ( !outputProvider )
101 {
103 }
104
105 //initialize dependency filters with cell sizes
106 mHillshadeFilter285->setCellSizeX( mCellSizeX );
107 mHillshadeFilter285->setCellSizeY( mCellSizeY );
108 mHillshadeFilter285->setZFactor( mZFactor );
109 mHillshadeFilter300->setCellSizeX( mCellSizeX );
110 mHillshadeFilter300->setCellSizeY( mCellSizeY );
111 mHillshadeFilter300->setZFactor( mZFactor );
112 mHillshadeFilter315->setCellSizeX( mCellSizeX );
113 mHillshadeFilter315->setCellSizeY( mCellSizeY );
114 mHillshadeFilter315->setZFactor( mZFactor );
115 mSlopeFilter->setCellSizeX( mCellSizeX );
116 mSlopeFilter->setCellSizeY( mCellSizeY );
117 mSlopeFilter->setZFactor( mZFactor );
118 mAspectFilter->setCellSizeX( mCellSizeX );
119 mAspectFilter->setCellSizeY( mCellSizeY );
120 mAspectFilter->setZFactor( mZFactor );
121
122 if ( inputProvider->sourceHasNoDataValue( 1 ) )
123 {
124 mInputNodataValue = inputProvider->sourceNoDataValue( 1 );
125 }
126 else
127 {
128 mInputNodataValue = -9999;
129 }
130
131 mSlopeFilter->setInputNodataValue( mInputNodataValue );
132 mAspectFilter->setInputNodataValue( mInputNodataValue );
133 mHillshadeFilter285->setInputNodataValue( mInputNodataValue );
134 mHillshadeFilter300->setInputNodataValue( mInputNodataValue );
135 mHillshadeFilter315->setInputNodataValue( mInputNodataValue );
136
137 mOutputNodataValue = -9999;
138 outputProvider->setNoDataValue( 1, mOutputNodataValue );
139 outputProvider->setNoDataValue( 2, mOutputNodataValue );
140 outputProvider->setNoDataValue( 3, mOutputNodataValue );
141 mSlopeFilter->setOutputNodataValue( mOutputNodataValue );
142 mAspectFilter->setOutputNodataValue( mOutputNodataValue );
143 mHillshadeFilter285->setOutputNodataValue( mOutputNodataValue );
144 mHillshadeFilter300->setOutputNodataValue( mOutputNodataValue );
145 mHillshadeFilter315->setOutputNodataValue( mOutputNodataValue );
146
147 if ( ySize < 3 ) //we require at least three rows (should be true for most datasets)
148 {
150 }
151
152 // iterate row-by-row over the raster
153 QgsRasterIterator iter( inputProvider );
154 iter.setMaximumTileWidth( xSize );
155 iter.setMaximumTileHeight( 1 );
156 iter.startRasterRead( 1, xSize, ySize, inputProvider->extent() );
157
158 //keep only three scanlines in memory at a time
159 std::vector<float> scanLine1( xSize );
160 std::vector<float> scanLine2( xSize );
161 std::vector<float> scanLine3( xSize );
162 std::vector<unsigned char> resultRedLine( xSize );
163 std::vector<unsigned char> resultGreenLine( xSize );
164 std::vector<unsigned char> resultBlueLine( xSize );
165
166 const double maxProgressDuringBlockWriting = outputProvider->hasReportsDuringClose() ? 50.0 : 100.0;
167
168 auto readRow = [&iter, this]( std::vector<float> &scanLine ) {
169 int iterCols = 0;
170 int iterRows = 0;
171 int iterLeft = 0;
172 int iterTop = 0;
173 std::unique_ptr<QgsRasterBlock> block;
174 if ( iter.readNextRasterPart( 1, iterCols, iterRows, block, iterLeft, iterTop ) && block && block->isValid() )
175 {
176 bool isNoData = false;
177 for ( int j = 0; j < iterCols; ++j )
178 {
179 const double val = block->valueAndNoData( 0, j, isNoData );
180 scanLine[j] = isNoData ? mInputNodataValue : static_cast<float>( val );
181 }
182 }
183 else
184 {
185 std::fill( scanLine.begin(), scanLine.end(), mInputNodataValue );
186 }
187 };
188
189 //values outside the layer extent (if the 3x3 window is on the border) are sent to the processing method as (input) nodata values
190 for ( int i = 0; i < ySize; ++i )
191 {
192 if ( feedback )
193 {
194 feedback->setProgress( maxProgressDuringBlockWriting * i / static_cast<double>( ySize ) );
195 }
196
197 if ( feedback && feedback->isCanceled() )
198 {
199 return Result::Canceled;
200 }
201
202 if ( i == 0 )
203 {
204 //fill scanline 1 with (input) nodata for the values above the first row and feed scanline2 with the first row
205 std::fill( scanLine1.begin(), scanLine1.end(), mInputNodataValue );
206 readRow( scanLine2 );
207 }
208 else
209 {
210 //normally fetch only scanLine3 and release scanline 1 if we move forward one row
211 std::swap( scanLine1, scanLine2 );
212 std::swap( scanLine2, scanLine3 );
213 }
214
215 if ( i == ySize - 1 ) //fill the row below the bottom with nodata values
216 {
217 std::fill( scanLine3.begin(), scanLine3.end(), mInputNodataValue );
218 }
219 else
220 {
221 readRow( scanLine3 );
222 }
223
224 for ( int j = 0; j < xSize; ++j )
225 {
226 bool resultOk = false;
227 if ( j == 0 )
228 {
229 resultOk = processNineCellWindow(
230 &mInputNodataValue,
231 &scanLine1[j],
232 &scanLine1[j + 1],
233 &mInputNodataValue,
234 &scanLine2[j],
235 &scanLine2[j + 1],
236 &mInputNodataValue,
237 &scanLine3[j],
238 &scanLine3[j + 1],
239 &resultRedLine[j],
240 &resultGreenLine[j],
241 &resultBlueLine[j]
242 );
243 }
244 else if ( j == xSize - 1 )
245 {
246 resultOk = processNineCellWindow(
247 &scanLine1[j - 1],
248 &scanLine1[j],
249 &mInputNodataValue,
250 &scanLine2[j - 1],
251 &scanLine2[j],
252 &mInputNodataValue,
253 &scanLine3[j - 1],
254 &scanLine3[j],
255 &mInputNodataValue,
256 &resultRedLine[j],
257 &resultGreenLine[j],
258 &resultBlueLine[j]
259 );
260 }
261 else
262 {
263 resultOk = processNineCellWindow(
264 &scanLine1[j - 1],
265 &scanLine1[j],
266 &scanLine1[j + 1],
267 &scanLine2[j - 1],
268 &scanLine2[j],
269 &scanLine2[j + 1],
270 &scanLine3[j - 1],
271 &scanLine3[j],
272 &scanLine3[j + 1],
273 &resultRedLine[j],
274 &resultGreenLine[j],
275 &resultBlueLine[j]
276 );
277 }
278
279 if ( !resultOk )
280 {
281 resultRedLine[j] = mOutputNodataValue;
282 resultGreenLine[j] = mOutputNodataValue;
283 resultBlueLine[j] = mOutputNodataValue;
284 }
285 }
286
287 auto redBlock = std::make_unique<QgsRasterBlock>( Qgis::DataType::Byte, xSize, 1 );
288 auto greenBlock = std::make_unique<QgsRasterBlock>( Qgis::DataType::Byte, xSize, 1 );
289 auto blueBlock = std::make_unique<QgsRasterBlock>( Qgis::DataType::Byte, xSize, 1 );
290 for ( int j = 0; j < xSize; ++j )
291 {
292 redBlock->setValue( 0, j, resultRedLine[j] );
293 greenBlock->setValue( 0, j, resultGreenLine[j] );
294 blueBlock->setValue( 0, j, resultBlueLine[j] );
295 }
296
297 if ( !outputProvider->writeBlock( redBlock.get(), 1, 0, i ) )
298 {
299 QgsDebugError( u"Raster IO Error"_s );
300 }
301 if ( !outputProvider->writeBlock( greenBlock.get(), 2, 0, i ) )
302 {
303 QgsDebugError( u"Raster IO Error"_s );
304 }
305 if ( !outputProvider->writeBlock( blueBlock.get(), 3, 0, i ) )
306 {
307 QgsDebugError( u"Raster IO Error"_s );
308 }
309 }
310
311 if ( feedback && outputProvider->hasReportsDuringClose() )
312 {
313 std::unique_ptr<QgsFeedback> scaledFeedback( QgsFeedback::createScaledFeedback( feedback, maxProgressDuringBlockWriting, 100.0 ) );
314 if ( !outputProvider->closeWithProgress( scaledFeedback.get() ) )
315 {
316 if ( feedback->isCanceled() )
317 return Result::Canceled;
319 }
320 }
321
322 if ( feedback )
323 {
324 feedback->setProgress( 100 );
325 }
326
327 if ( feedback && feedback->isCanceled() )
328 {
329 return Result::Canceled;
330 }
331
332 return Result::Success;
333}
334
335bool QgsRelief::processNineCellWindow( float *x1, float *x2, float *x3, float *x4, float *x5, float *x6, float *x7, float *x8, float *x9, unsigned char *red, unsigned char *green, unsigned char *blue )
336{
337 //1. component: color and hillshade from 300 degrees
338 int r = 0;
339 int g = 0;
340 int b = 0;
341
342 const float hillShadeValue300 = mHillshadeFilter300->processNineCellWindow( x1, x2, x3, x4, x5, x6, x7, x8, x9 );
343 if ( hillShadeValue300 != mOutputNodataValue )
344 {
345 if ( !getElevationColor( *x5, &r, &g, &b ) )
346 {
347 r = hillShadeValue300;
348 g = hillShadeValue300;
349 b = hillShadeValue300;
350 }
351 else
352 {
353 r = r / 2.0 + hillShadeValue300 / 2.0;
354 g = g / 2.0 + hillShadeValue300 / 2.0;
355 b = b / 2.0 + hillShadeValue300 / 2.0;
356 }
357 }
358
359 //2. component: hillshade and slope
360 const float hillShadeValue315 = mHillshadeFilter315->processNineCellWindow( x1, x2, x3, x4, x5, x6, x7, x8, x9 );
361 const float slope = mSlopeFilter->processNineCellWindow( x1, x2, x3, x4, x5, x6, x7, x8, x9 );
362 if ( hillShadeValue315 != mOutputNodataValue && slope != mOutputNodataValue )
363 {
364 int r2, g2, b2;
365 if ( slope > 15 )
366 {
367 r2 = 0 / 2.0 + hillShadeValue315 / 2.0;
368 g2 = 0 / 2.0 + hillShadeValue315 / 2.0;
369 b2 = 0 / 2.0 + hillShadeValue315 / 2.0;
370 }
371 else if ( slope >= 1 )
372 {
373 const int slopeValue = 255 - ( slope / 15.0 * 255.0 );
374 r2 = slopeValue / 2.0 + hillShadeValue315 / 2.0;
375 g2 = slopeValue / 2.0 + hillShadeValue315 / 2.0;
376 b2 = slopeValue / 2.0 + hillShadeValue315 / 2.0;
377 }
378 else
379 {
380 r2 = hillShadeValue315;
381 g2 = hillShadeValue315;
382 b2 = hillShadeValue315;
383 }
384
385 //combine with r,g,b with 70 percentage coverage
386 r = r * 0.7 + r2 * 0.3;
387 g = g * 0.7 + g2 * 0.3;
388 b = b * 0.7 + b2 * 0.3;
389 }
390
391 //3. combine yellow aspect with 10% transparency, illumination from 285 degrees
392 const float hillShadeValue285 = mHillshadeFilter285->processNineCellWindow( x1, x2, x3, x4, x5, x6, x7, x8, x9 );
393 const float aspect = mAspectFilter->processNineCellWindow( x1, x2, x3, x4, x5, x6, x7, x8, x9 );
394 if ( hillShadeValue285 != mOutputNodataValue && aspect != mOutputNodataValue )
395 {
396 double angle_diff = std::fabs( 285 - aspect );
397 if ( angle_diff > 180 )
398 {
399 angle_diff -= 180;
400 }
401
402 int r3, g3, b3;
403 if ( angle_diff < 90 )
404 {
405 const int aspectVal = ( 1 - std::cos( angle_diff * M_PI / 180 ) ) * 255;
406 r3 = 0.5 * 255 + hillShadeValue315 * 0.5;
407 g3 = 0.5 * 255 + hillShadeValue315 * 0.5;
408 b3 = 0.5 * aspectVal + hillShadeValue315 * 0.5;
409 }
410 else //white
411 {
412 r3 = 0.5 * 255 + hillShadeValue315 * 0.5;
413 g3 = 0.5 * 255 + hillShadeValue315 * 0.5;
414 b3 = 0.5 * 255 + hillShadeValue315 * 0.5;
415 }
416
417 r = r3 * 0.1 + r * 0.9;
418 g = g3 * 0.1 + g * 0.9;
419 b = b3 * 0.1 + b * 0.9;
420 }
421
422 *red = ( unsigned char ) r;
423 *green = ( unsigned char ) g;
424 *blue = ( unsigned char ) b;
425 return true;
426}
427
428bool QgsRelief::getElevationColor( double elevation, int *red, int *green, int *blue ) const
429{
430 QList<QgsRasterReliefColor>::const_iterator reliefColorIt = mReliefColors.constBegin();
431 for ( ; reliefColorIt != mReliefColors.constEnd(); ++reliefColorIt )
432 {
433 if ( elevation >= reliefColorIt->minElevation && elevation <= reliefColorIt->maxElevation )
434 {
435 const QColor &c = reliefColorIt->color;
436 *red = c.red();
437 *green = c.green();
438 *blue = c.blue();
439
440 return true;
441 }
442 }
443 return false;
444}
445
446//this function is mainly there for debugging
448{
449 auto inputLayer = std::make_unique< QgsRasterLayer >( mInputFile, u"relief"_s, u"gdal"_s );
450 if ( !inputLayer->isValid() )
451 {
452 return false;
453 }
454
455 QgsRasterDataProvider *inputProvider = inputLayer->dataProvider();
456 if ( !inputProvider )
457 {
458 return false;
459 }
460
461 //open first raster band for reading (elevation raster is always single band)
462 // get minimum and maximum of elevation raster -> 252 elevation classes
464
465 //go through raster cells and get frequency of classes
466
467 //store elevation frequency in 252 elevation classes
468 std::vector<double> frequency( 252, 0.0 );
469 const double frequencyClassRange = ( stats.maximumValue - stats.minimumValue ) / 252.0;
470
471 QgsRasterIterator iter( inputProvider );
472 iter.startRasterRead( 1, inputProvider->xSize(), inputProvider->ySize(), inputProvider->extent() );
473
474 int iterCols = 0;
475 int iterRows = 0;
476 int iterLeft = 0;
477 int iterTop = 0;
478 std::unique_ptr<QgsRasterBlock> block;
479
480 bool isNoData = false;
481 int elevationClass = -1;
482 while ( iter.readNextRasterPart( 1, iterCols, iterRows, block, iterLeft, iterTop ) )
483 {
484 for ( int row = 0; row < iterRows; ++row )
485 {
486 for ( int col = 0; col < iterCols; ++col )
487 {
488 const double value = block->valueAndNoData( row, col, isNoData );
489 if ( isNoData )
490 {
491 continue;
492 }
493
494 elevationClass = frequencyClassForElevation( value, stats.minimumValue, frequencyClassRange );
495 if ( elevationClass >= 0 && elevationClass < 252 )
496 {
497 frequency[elevationClass] += 1.0;
498 }
499 }
500 }
501 }
502
503 //log10 transformation for all frequency values
504 for ( int i = 0; i < 252; ++i )
505 {
506 frequency[i] = std::log10( frequency[i] );
507 }
508
509 //write out frequency values to csv file for debugging
510 QFile outFile( file );
511 if ( !outFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
512 {
513 return false;
514 }
515
516 QTextStream outstream( &outFile );
517 for ( int i = 0; i < 252; ++i )
518 {
519 outstream << QString::number( i ) + ',' + QString::number( frequency[i] ) << Qt::endl;
520 }
521 outFile.close();
522 return true;
523}
524
526{
527 auto inputLayer = std::make_unique< QgsRasterLayer >( mInputFile, u"relief"_s, u"gdal"_s );
528 if ( !inputLayer->isValid() )
529 {
530 return {};
531 }
532 return QgsRasterLayerUtils::calculateOptimizedReliefClasses( inputLayer->dataProvider(), 1 );
533}
534
535int QgsRelief::frequencyClassForElevation( double elevation, double minElevation, double elevationClassRange )
536{
537 return ( elevation - minElevation ) / elevationClassRange;
538}
@ Byte
Eight bit unsigned integer (quint8).
Definition qgis.h:395
Calculates aspect values in a window of 3x3 cells based on first order derivatives in x- and y- direc...
virtual QgsCoordinateReferenceSystem crs() const =0
Returns the coordinate system for the data source.
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition qgsfeedback.h:44
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:56
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:65
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...
A hillshade filter.
The RasterBandStats struct is a container for statistics about a single raster band.
double minimumValue
The minimum cell value in the raster band.
double maximumValue
The maximum cell value in the raster band.
Base class for raster data providers.
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.
QgsRectangle extent() const override=0
Returns the extent of the layer.
The raster file writer which allows you to save a raster to a new file.
QgsRasterDataProvider * createMultiBandRaster(Qgis::DataType dataType, int width, int height, const QgsRectangle &extent, const QgsCoordinateReferenceSystem &crs, int nBands) SIP_FACTORY
Create a raster file with given number of bands without initializing the pixel data.
void setOutputFormat(const QString &format)
Sets the output format.
virtual int xSize() const
Gets raster size.
Q_DECL_DEPRECATED QgsRasterBandStats bandStatistics(int bandNo, int stats, const QgsRectangle &extent=QgsRectangle(), int sampleSize=0, QgsRasterBlockFeedback *feedback=nullptr)
Returns the band statistics.
virtual int ySize() const
Iterator for sequentially processing raster cells.
void setMaximumTileWidth(int w)
Sets the maximum tile width returned during iteration.
bool readNextRasterPart(int bandNumber, int &nCols, int &nRows, QgsRasterBlock **block, int &topLeftCol, int &topLeftRow)
Fetches next part of raster data, caller takes ownership of the block and caller should delete the bl...
void startRasterRead(int bandNumber, qgssize nCols, qgssize nRows, const QgsRectangle &extent, QgsRasterBlockFeedback *feedback=nullptr)
Start reading of raster band.
void setMaximumTileHeight(int h)
Sets the minimum tile height returned during iteration.
static QList< QgsRasterReliefColor > calculateOptimizedReliefClasses(QgsRasterDataProvider *provider, int band)
Calculates optimized relief class breaks according with the method of Buenzli (2011) using an iterati...
Defines elevation range and color for raster relief coloring.
void clearReliefColors()
Clears all existing relief colors.
Definition qgsrelief.cpp:57
Result
Calculation results.
Definition qgsrelief.h:59
@ Canceled
Operation was canceled.
Definition qgsrelief.h:64
@ Success
Calculation succeeded.
Definition qgsrelief.h:60
@ OutputCreationFailed
Creation of output layer failed.
Definition qgsrelief.h:62
@ InvalidInput
Invalid input layer.
Definition qgsrelief.h:61
bool exportFrequencyDistributionToCsv(const QString &file)
Writes frequency of elevation values to a file for manual inspection.
QgsRelief(const QString &inputFile, const QString &outputFile, const QString &outputFormat)
Constructor for QgsRelief.
Definition qgsrelief.cpp:41
void addReliefColorClass(const QgsRasterReliefColor &color)
Adds a relief color.
Definition qgsrelief.cpp:62
QList< QgsRasterReliefColor > calculateOptimizedReliefClasses()
Calculates class breaks according with the method of Buenzli (2011) using an iterative algorithm for ...
QgsRelief::Result processRaster(QgsFeedback *feedback=nullptr)
Starts the calculation.
Definition qgsrelief.cpp:78
Calculates slope values in a window of 3x3 cells based on first order derivatives in x- and y- direct...
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
#define QgsDebugError(str)
Definition qgslogger.h:71