QGIS API Documentation 3.99.0-Master (e9821da5c6b)
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 "qgsogrutils.h"
30#include "qgsslopefilter.h"
31
32#include <QColor>
33#include <QFile>
34#include <QString>
35#include <QTextStream>
36#include <QVector>
37
38using namespace Qt::StringLiterals;
39
40QgsRelief::QgsRelief( const QString &inputFile, const QString &outputFile, const QString &outputFormat )
41 : mInputFile( inputFile )
42 , mOutputFile( outputFile )
43 , mOutputFormat( outputFormat )
44 , mSlopeFilter( std::make_unique<QgsSlopeFilter>( inputFile, outputFile, outputFormat ) )
45 , mAspectFilter( std::make_unique<QgsAspectFilter>( inputFile, outputFile, outputFormat ) )
46 , mHillshadeFilter285( std::make_unique<QgsHillshadeFilter>( inputFile, outputFile, outputFormat, 285, 30 ) )
47 , mHillshadeFilter300( std::make_unique<QgsHillshadeFilter>( inputFile, outputFile, outputFormat, 300, 30 ) )
48 , mHillshadeFilter315( std::make_unique<QgsHillshadeFilter>( inputFile, outputFile, outputFormat, 315, 30 ) )
49{
50 /*mReliefColors = calculateOptimizedReliefClasses();
51 setDefaultReliefColors();*/
52}
53
54QgsRelief::~QgsRelief() = default;
55
57{
58 mReliefColors.clear();
59}
60
62{
63 mReliefColors.push_back( color );
64}
65
66void QgsRelief::setDefaultReliefColors()
67{
69 addReliefColorClass( ReliefColor( QColor( 9, 176, 76 ), 0, 200 ) );
70 addReliefColorClass( ReliefColor( QColor( 20, 228, 128 ), 200, 500 ) );
71 addReliefColorClass( ReliefColor( QColor( 167, 239, 153 ), 500, 1000 ) );
72 addReliefColorClass( ReliefColor( QColor( 218, 188, 143 ), 1000, 2000 ) );
73 addReliefColorClass( ReliefColor( QColor( 233, 158, 91 ), 2000, 4000 ) );
74 addReliefColorClass( ReliefColor( QColor( 255, 255, 255 ), 4000, 9000 ) );
75}
76
78{
79 //open input file
80 int xSize, ySize;
81 const gdal::dataset_unique_ptr inputDataset = openInputFile( xSize, ySize );
82 if ( !inputDataset )
83 {
84 return 1; //opening of input file failed
85 }
86
87 //output driver
88 GDALDriverH outputDriver = openOutputDriver();
89 if ( !outputDriver )
90 {
91 return 2;
92 }
93
94 gdal::dataset_unique_ptr outputDataset = openOutputFile( inputDataset.get(), outputDriver );
95 constexpr int RET_OUTPUT_CREATION_FAILED = 3;
96 if ( !outputDataset )
97 {
98 return RET_OUTPUT_CREATION_FAILED; //create operation on output file failed
99 }
100
101 //initialize dependency filters with cell sizes
102 mHillshadeFilter285->setCellSizeX( mCellSizeX );
103 mHillshadeFilter285->setCellSizeY( mCellSizeY );
104 mHillshadeFilter285->setZFactor( mZFactor );
105 mHillshadeFilter300->setCellSizeX( mCellSizeX );
106 mHillshadeFilter300->setCellSizeY( mCellSizeY );
107 mHillshadeFilter300->setZFactor( mZFactor );
108 mHillshadeFilter315->setCellSizeX( mCellSizeX );
109 mHillshadeFilter315->setCellSizeY( mCellSizeY );
110 mHillshadeFilter315->setZFactor( mZFactor );
111 mSlopeFilter->setCellSizeX( mCellSizeX );
112 mSlopeFilter->setCellSizeY( mCellSizeY );
113 mSlopeFilter->setZFactor( mZFactor );
114 mAspectFilter->setCellSizeX( mCellSizeX );
115 mAspectFilter->setCellSizeY( mCellSizeY );
116 mAspectFilter->setZFactor( mZFactor );
117
118 //open first raster band for reading (operation is only for single band raster)
119 GDALRasterBandH rasterBand = GDALGetRasterBand( inputDataset.get(), 1 );
120 if ( !rasterBand )
121 {
122 return 4;
123 }
124 mInputNodataValue = GDALGetRasterNoDataValue( rasterBand, nullptr );
125 mSlopeFilter->setInputNodataValue( mInputNodataValue );
126 mAspectFilter->setInputNodataValue( mInputNodataValue );
127 mHillshadeFilter285->setInputNodataValue( mInputNodataValue );
128 mHillshadeFilter300->setInputNodataValue( mInputNodataValue );
129 mHillshadeFilter315->setInputNodataValue( mInputNodataValue );
130
131 GDALRasterBandH outputRedBand = GDALGetRasterBand( outputDataset.get(), 1 );
132 GDALRasterBandH outputGreenBand = GDALGetRasterBand( outputDataset.get(), 2 );
133 GDALRasterBandH outputBlueBand = GDALGetRasterBand( outputDataset.get(), 3 );
134
135 if ( !outputRedBand || !outputGreenBand || !outputBlueBand )
136 {
137 return 5;
138 }
139 //try to set -9999 as nodata value
140 GDALSetRasterNoDataValue( outputRedBand, -9999 );
141 GDALSetRasterNoDataValue( outputGreenBand, -9999 );
142 GDALSetRasterNoDataValue( outputBlueBand, -9999 );
143 mOutputNodataValue = GDALGetRasterNoDataValue( outputRedBand, nullptr );
144 mSlopeFilter->setOutputNodataValue( mOutputNodataValue );
145 mAspectFilter->setOutputNodataValue( mOutputNodataValue );
146 mHillshadeFilter285->setOutputNodataValue( mOutputNodataValue );
147 mHillshadeFilter300->setOutputNodataValue( mOutputNodataValue );
148 mHillshadeFilter315->setOutputNodataValue( mOutputNodataValue );
149
150 if ( ySize < 3 ) //we require at least three rows (should be true for most datasets)
151 {
152 return 6;
153 }
154
155 //keep only three scanlines in memory at a time
156 float *scanLine1 = ( float * ) CPLMalloc( sizeof( float ) * xSize );
157 float *scanLine2 = ( float * ) CPLMalloc( sizeof( float ) * xSize );
158 float *scanLine3 = ( float * ) CPLMalloc( sizeof( float ) * xSize );
159
160 unsigned char *resultRedLine = ( unsigned char * ) CPLMalloc( sizeof( unsigned char ) * xSize );
161 unsigned char *resultGreenLine = ( unsigned char * ) CPLMalloc( sizeof( unsigned char ) * xSize );
162 unsigned char *resultBlueLine = ( unsigned char * ) CPLMalloc( sizeof( unsigned char ) * xSize );
163
164 bool resultOk;
165
166#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION( 3, 13, 0 )
167 const bool hasReportsDuringClose = GDALDatasetGetCloseReportsProgress( outputDataset.get() );
168 const double maxProgressDuringBlockWriting = hasReportsDuringClose ? 50.0 : 100.0;
169#else
170 constexpr double maxProgressDuringBlockWriting = 100.0;
171#endif
172
173 //values outside the layer extent (if the 3x3 window is on the border) are sent to the processing method as (input) nodata values
174 for ( int i = 0; i < ySize; ++i )
175 {
176 if ( feedback )
177 {
178 feedback->setProgress( maxProgressDuringBlockWriting * i / static_cast<double>( ySize ) );
179 }
180
181 if ( feedback && feedback->isCanceled() )
182 {
183 break;
184 }
185
186 if ( i == 0 )
187 {
188 //fill scanline 1 with (input) nodata for the values above the first row and feed scanline2 with the first row
189 for ( int a = 0; a < xSize; ++a )
190 {
191 scanLine1[a] = mInputNodataValue;
192 }
193 if ( GDALRasterIO( rasterBand, GF_Read, 0, 0, xSize, 1, scanLine2, xSize, 1, GDT_Float32, 0, 0 ) != CE_None )
194 {
195 QgsDebugError( u"Raster IO Error"_s );
196 }
197 }
198 else
199 {
200 //normally fetch only scanLine3 and release scanline 1 if we move forward one row
201 CPLFree( scanLine1 );
202 scanLine1 = scanLine2;
203 scanLine2 = scanLine3;
204 scanLine3 = ( float * ) CPLMalloc( sizeof( float ) * xSize );
205 }
206
207 if ( i == ySize - 1 ) //fill the row below the bottom with nodata values
208 {
209 for ( int a = 0; a < xSize; ++a )
210 {
211 scanLine3[a] = mInputNodataValue;
212 }
213 }
214 else
215 {
216 if ( GDALRasterIO( rasterBand, GF_Read, 0, i + 1, xSize, 1, scanLine3, xSize, 1, GDT_Float32, 0, 0 ) != CE_None )
217 {
218 QgsDebugError( u"Raster IO Error"_s );
219 }
220 }
221
222 for ( int j = 0; j < xSize; ++j )
223 {
224 if ( j == 0 )
225 {
226 resultOk = processNineCellWindow( &mInputNodataValue, &scanLine1[j], &scanLine1[j + 1], &mInputNodataValue, &scanLine2[j], &scanLine2[j + 1], &mInputNodataValue, &scanLine3[j], &scanLine3[j + 1], &resultRedLine[j], &resultGreenLine[j], &resultBlueLine[j] );
227 }
228 else if ( j == xSize - 1 )
229 {
230 resultOk = processNineCellWindow( &scanLine1[j - 1], &scanLine1[j], &mInputNodataValue, &scanLine2[j - 1], &scanLine2[j], &mInputNodataValue, &scanLine3[j - 1], &scanLine3[j], &mInputNodataValue, &resultRedLine[j], &resultGreenLine[j], &resultBlueLine[j] );
231 }
232 else
233 {
234 resultOk = processNineCellWindow( &scanLine1[j - 1], &scanLine1[j], &scanLine1[j + 1], &scanLine2[j - 1], &scanLine2[j], &scanLine2[j + 1], &scanLine3[j - 1], &scanLine3[j], &scanLine3[j + 1], &resultRedLine[j], &resultGreenLine[j], &resultBlueLine[j] );
235 }
236
237 if ( !resultOk )
238 {
239 resultRedLine[j] = mOutputNodataValue;
240 resultGreenLine[j] = mOutputNodataValue;
241 resultBlueLine[j] = mOutputNodataValue;
242 }
243 }
244
245 if ( GDALRasterIO( outputRedBand, GF_Write, 0, i, xSize, 1, resultRedLine, xSize, 1, GDT_Byte, 0, 0 ) != CE_None )
246 {
247 QgsDebugError( u"Raster IO Error"_s );
248 }
249 if ( GDALRasterIO( outputGreenBand, GF_Write, 0, i, xSize, 1, resultGreenLine, xSize, 1, GDT_Byte, 0, 0 ) != CE_None )
250 {
251 QgsDebugError( u"Raster IO Error"_s );
252 }
253 if ( GDALRasterIO( outputBlueBand, GF_Write, 0, i, xSize, 1, resultBlueLine, xSize, 1, GDT_Byte, 0, 0 ) != CE_None )
254 {
255 QgsDebugError( u"Raster IO Error"_s );
256 }
257 }
258
259 if ( feedback )
260 {
261 feedback->setProgress( 100 );
262 }
263
264 CPLFree( resultRedLine );
265 CPLFree( resultBlueLine );
266 CPLFree( resultGreenLine );
267 CPLFree( scanLine1 );
268 CPLFree( scanLine2 );
269 CPLFree( scanLine3 );
270
271 constexpr int RET_CANCELED = 7;
272 if ( feedback && feedback->isCanceled() )
273 {
274 //delete the dataset without closing (because it is faster)
275 gdal::fast_delete_and_close( outputDataset, outputDriver, mOutputFile );
276 return RET_CANCELED;
277 }
278#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION( 3, 13, 0 )
279 else if ( hasReportsDuringClose && feedback )
280 {
281 QgsGdalProgressAdapter progress( feedback, maxProgressDuringBlockWriting );
282 if ( GDALDatasetRunCloseWithoutDestroyingEx(
283 outputDataset.get(), QgsGdalProgressAdapter::progressCallback, &progress
284 )
285 != CE_None )
286 {
287 return feedback->isCanceled() ? RET_CANCELED : RET_OUTPUT_CREATION_FAILED;
288 }
289 }
290#endif
291
292 return 0;
293}
294
295bool 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 )
296{
297 //1. component: color and hillshade from 300 degrees
298 int r = 0;
299 int g = 0;
300 int b = 0;
301
302 const float hillShadeValue300 = mHillshadeFilter300->processNineCellWindow( x1, x2, x3, x4, x5, x6, x7, x8, x9 );
303 if ( hillShadeValue300 != mOutputNodataValue )
304 {
305 if ( !getElevationColor( *x5, &r, &g, &b ) )
306 {
307 r = hillShadeValue300;
308 g = hillShadeValue300;
309 b = hillShadeValue300;
310 }
311 else
312 {
313 r = r / 2.0 + hillShadeValue300 / 2.0;
314 g = g / 2.0 + hillShadeValue300 / 2.0;
315 b = b / 2.0 + hillShadeValue300 / 2.0;
316 }
317 }
318
319 //2. component: hillshade and slope
320 const float hillShadeValue315 = mHillshadeFilter315->processNineCellWindow( x1, x2, x3, x4, x5, x6, x7, x8, x9 );
321 const float slope = mSlopeFilter->processNineCellWindow( x1, x2, x3, x4, x5, x6, x7, x8, x9 );
322 if ( hillShadeValue315 != mOutputNodataValue && slope != mOutputNodataValue )
323 {
324 int r2, g2, b2;
325 if ( slope > 15 )
326 {
327 r2 = 0 / 2.0 + hillShadeValue315 / 2.0;
328 g2 = 0 / 2.0 + hillShadeValue315 / 2.0;
329 b2 = 0 / 2.0 + hillShadeValue315 / 2.0;
330 }
331 else if ( slope >= 1 )
332 {
333 const int slopeValue = 255 - ( slope / 15.0 * 255.0 );
334 r2 = slopeValue / 2.0 + hillShadeValue315 / 2.0;
335 g2 = slopeValue / 2.0 + hillShadeValue315 / 2.0;
336 b2 = slopeValue / 2.0 + hillShadeValue315 / 2.0;
337 }
338 else
339 {
340 r2 = hillShadeValue315;
341 g2 = hillShadeValue315;
342 b2 = hillShadeValue315;
343 }
344
345 //combine with r,g,b with 70 percentage coverage
346 r = r * 0.7 + r2 * 0.3;
347 g = g * 0.7 + g2 * 0.3;
348 b = b * 0.7 + b2 * 0.3;
349 }
350
351 //3. combine yellow aspect with 10% transparency, illumination from 285 degrees
352 const float hillShadeValue285 = mHillshadeFilter285->processNineCellWindow( x1, x2, x3, x4, x5, x6, x7, x8, x9 );
353 const float aspect = mAspectFilter->processNineCellWindow( x1, x2, x3, x4, x5, x6, x7, x8, x9 );
354 if ( hillShadeValue285 != mOutputNodataValue && aspect != mOutputNodataValue )
355 {
356 double angle_diff = std::fabs( 285 - aspect );
357 if ( angle_diff > 180 )
358 {
359 angle_diff -= 180;
360 }
361
362 int r3, g3, b3;
363 if ( angle_diff < 90 )
364 {
365 const int aspectVal = ( 1 - std::cos( angle_diff * M_PI / 180 ) ) * 255;
366 r3 = 0.5 * 255 + hillShadeValue315 * 0.5;
367 g3 = 0.5 * 255 + hillShadeValue315 * 0.5;
368 b3 = 0.5 * aspectVal + hillShadeValue315 * 0.5;
369 }
370 else //white
371 {
372 r3 = 0.5 * 255 + hillShadeValue315 * 0.5;
373 g3 = 0.5 * 255 + hillShadeValue315 * 0.5;
374 b3 = 0.5 * 255 + hillShadeValue315 * 0.5;
375 }
376
377 r = r3 * 0.1 + r * 0.9;
378 g = g3 * 0.1 + g * 0.9;
379 b = b3 * 0.1 + b * 0.9;
380 }
381
382 *red = ( unsigned char ) r;
383 *green = ( unsigned char ) g;
384 *blue = ( unsigned char ) b;
385 return true;
386}
387
388bool QgsRelief::getElevationColor( double elevation, int *red, int *green, int *blue ) const
389{
390 QList<ReliefColor>::const_iterator reliefColorIt = mReliefColors.constBegin();
391 for ( ; reliefColorIt != mReliefColors.constEnd(); ++reliefColorIt )
392 {
393 if ( elevation >= reliefColorIt->minElevation && elevation <= reliefColorIt->maxElevation )
394 {
395 const QColor &c = reliefColorIt->color;
396 *red = c.red();
397 *green = c.green();
398 *blue = c.blue();
399
400 return true;
401 }
402 }
403 return false;
404}
405
406//duplicated from QgsNineCellFilter. Todo: make common base class
407gdal::dataset_unique_ptr QgsRelief::openInputFile( int &nCellsX, int &nCellsY )
408{
409 gdal::dataset_unique_ptr inputDataset( GDALOpen( mInputFile.toUtf8().constData(), GA_ReadOnly ) );
410 if ( inputDataset )
411 {
412 nCellsX = GDALGetRasterXSize( inputDataset.get() );
413 nCellsY = GDALGetRasterYSize( inputDataset.get() );
414
415 //we need at least one band
416 if ( GDALGetRasterCount( inputDataset.get() ) < 1 )
417 {
418 return nullptr;
419 }
420 }
421 return inputDataset;
422}
423
424GDALDriverH QgsRelief::openOutputDriver()
425{
426 //open driver
427 GDALDriverH outputDriver = GDALGetDriverByName( mOutputFormat.toLocal8Bit().data() );
428
429 if ( !outputDriver )
430 {
431 return outputDriver; //return nullptr, driver does not exist
432 }
433
434 if ( !QgsGdalUtils::supportsRasterCreate( outputDriver ) )
435 {
436 return nullptr; //driver exist, but it does not support the create operation
437 }
438
439 return outputDriver;
440}
441
442gdal::dataset_unique_ptr QgsRelief::openOutputFile( GDALDatasetH inputDataset, GDALDriverH outputDriver )
443{
444 if ( !inputDataset )
445 {
446 return nullptr;
447 }
448
449 const int xSize = GDALGetRasterXSize( inputDataset );
450 const int ySize = GDALGetRasterYSize( inputDataset );
451
452 //open output file
453 char **papszOptions = nullptr;
454
455 //use PACKBITS compression for tiffs by default
456 papszOptions = CSLSetNameValue( papszOptions, "COMPRESS", "PACKBITS" );
457
458 //create three band raster (red, green, blue)
459 gdal::dataset_unique_ptr outputDataset( GDALCreate( outputDriver, mOutputFile.toUtf8().constData(), xSize, ySize, 3, GDT_Byte, papszOptions ) );
460 CSLDestroy( papszOptions );
461 papszOptions = nullptr;
462
463 if ( !outputDataset )
464 {
465 return nullptr;
466 }
467
468 //get geotransform from inputDataset
469 double geotransform[6];
470 if ( GDALGetGeoTransform( inputDataset, geotransform ) != CE_None )
471 {
472 return nullptr;
473 }
474 GDALSetGeoTransform( outputDataset.get(), geotransform );
475
476 //make sure mCellSizeX and mCellSizeY are always > 0
477 mCellSizeX = geotransform[1];
478 if ( mCellSizeX < 0 )
479 {
480 mCellSizeX = -mCellSizeX;
481 }
482 mCellSizeY = geotransform[5];
483 if ( mCellSizeY < 0 )
484 {
485 mCellSizeY = -mCellSizeY;
486 }
487
488 const char *projection = GDALGetProjectionRef( inputDataset );
489 GDALSetProjection( outputDataset.get(), projection );
490
491 return outputDataset;
492}
493
494//this function is mainly there for debugging
496{
497 int nCellsX, nCellsY;
498 const gdal::dataset_unique_ptr inputDataset = openInputFile( nCellsX, nCellsY );
499 if ( !inputDataset )
500 {
501 return false;
502 }
503
504 //open first raster band for reading (elevation raster is always single band)
505 GDALRasterBandH elevationBand = GDALGetRasterBand( inputDataset.get(), 1 );
506 if ( !elevationBand )
507 {
508 return false;
509 }
510
511 //1. get minimum and maximum of elevation raster -> 252 elevation classes
512 int minOk, maxOk;
513 double minMax[2];
514 minMax[0] = GDALGetRasterMinimum( elevationBand, &minOk );
515 minMax[1] = GDALGetRasterMaximum( elevationBand, &maxOk );
516
517 if ( !minOk || !maxOk )
518 {
519 GDALComputeRasterMinMax( elevationBand, true, minMax );
520 }
521
522 //2. go through raster cells and get frequency of classes
523
524 //store elevation frequency in 256 elevation classes
525 double frequency[252] = { 0 };
526 const double frequencyClassRange = ( minMax[1] - minMax[0] ) / 252.0;
527
528 float *scanLine = ( float * ) CPLMalloc( sizeof( float ) * nCellsX );
529 int elevationClass = -1;
530
531 for ( int i = 0; i < nCellsY; ++i )
532 {
533 if ( GDALRasterIO( elevationBand, GF_Read, 0, i, nCellsX, 1, scanLine, nCellsX, 1, GDT_Float32, 0, 0 ) != CE_None )
534 {
535 QgsDebugError( u"Raster IO Error"_s );
536 }
537
538 for ( int j = 0; j < nCellsX; ++j )
539 {
540 elevationClass = frequencyClassForElevation( scanLine[j], minMax[0], frequencyClassRange );
541 if ( elevationClass >= 0 && elevationClass < 252 )
542 {
543 frequency[elevationClass] += 1.0;
544 }
545 }
546 }
547
548 CPLFree( scanLine );
549
550 //log10 transformation for all frequency values
551 for ( int i = 0; i < 252; ++i )
552 {
553 frequency[i] = std::log10( frequency[i] );
554 }
555
556 //write out frequency values to csv file for debugging
557 QFile outFile( file );
558 if ( !outFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
559 {
560 return false;
561 }
562
563 QTextStream outstream( &outFile );
564 for ( int i = 0; i < 252; ++i )
565 {
566 outstream << QString::number( i ) + ',' + QString::number( frequency[i] ) << Qt::endl;
567 }
568 outFile.close();
569 return true;
570}
571
572QList<QgsRelief::ReliefColor> QgsRelief::calculateOptimizedReliefClasses()
573{
574 QList<QgsRelief::ReliefColor> resultList;
575
576 int nCellsX, nCellsY;
577 const gdal::dataset_unique_ptr inputDataset = openInputFile( nCellsX, nCellsY );
578 if ( !inputDataset )
579 {
580 return resultList;
581 }
582
583 //open first raster band for reading (elevation raster is always single band)
584 GDALRasterBandH elevationBand = GDALGetRasterBand( inputDataset.get(), 1 );
585 if ( !elevationBand )
586 {
587 return resultList;
588 }
589
590 //1. get minimum and maximum of elevation raster -> 252 elevation classes
591 int minOk, maxOk;
592 double minMax[2];
593 minMax[0] = GDALGetRasterMinimum( elevationBand, &minOk );
594 minMax[1] = GDALGetRasterMaximum( elevationBand, &maxOk );
595
596 if ( !minOk || !maxOk )
597 {
598 GDALComputeRasterMinMax( elevationBand, true, minMax );
599 }
600
601 //2. go through raster cells and get frequency of classes
602
603 //store elevation frequency in 256 elevation classes
604 double frequency[252] = { 0 };
605 const double frequencyClassRange = ( minMax[1] - minMax[0] ) / 252.0;
606
607 float *scanLine = ( float * ) CPLMalloc( sizeof( float ) * nCellsX );
608 int elevationClass = -1;
609
610 for ( int i = 0; i < nCellsY; ++i )
611 {
612 if ( GDALRasterIO( elevationBand, GF_Read, 0, i, nCellsX, 1, scanLine, nCellsX, 1, GDT_Float32, 0, 0 ) != CE_None )
613 {
614 QgsDebugError( u"Raster IO Error"_s );
615 }
616 for ( int j = 0; j < nCellsX; ++j )
617 {
618 elevationClass = frequencyClassForElevation( scanLine[j], minMax[0], frequencyClassRange );
619 elevationClass = std::max( std::min( elevationClass, 251 ), 0 );
620 frequency[elevationClass] += 1.0;
621 }
622 }
623
624 CPLFree( scanLine );
625
626 //log10 transformation for all frequency values
627 for ( int i = 0; i < 252; ++i )
628 {
629 frequency[i] = std::log10( frequency[i] );
630 }
631
632 //start with 9 uniformly distributed classes
633 QList<int> classBreaks;
634 classBreaks.append( 0 );
635 classBreaks.append( 28 );
636 classBreaks.append( 56 );
637 classBreaks.append( 84 );
638 classBreaks.append( 112 );
639 classBreaks.append( 140 );
640 classBreaks.append( 168 );
641 classBreaks.append( 196 );
642 classBreaks.append( 224 );
643 classBreaks.append( 252 );
644
645 for ( int i = 0; i < 10; ++i )
646 {
647 optimiseClassBreaks( classBreaks, frequency );
648 }
649
650 //debug, print out all the classbreaks
651 for ( int i = 0; i < classBreaks.size(); ++i )
652 {
653 qWarning( "%d", classBreaks[i] );
654 }
655
656 //set colors according to optimised class breaks
657 QVector<QColor> colorList;
658 colorList.reserve( 9 );
659 colorList.push_back( QColor( 7, 165, 144 ) );
660 colorList.push_back( QColor( 12, 221, 162 ) );
661 colorList.push_back( QColor( 33, 252, 183 ) );
662 colorList.push_back( QColor( 247, 252, 152 ) );
663 colorList.push_back( QColor( 252, 196, 8 ) );
664 colorList.push_back( QColor( 252, 166, 15 ) );
665 colorList.push_back( QColor( 175, 101, 15 ) );
666 colorList.push_back( QColor( 255, 133, 92 ) );
667 colorList.push_back( QColor( 204, 204, 204 ) );
668
669 resultList.reserve( classBreaks.size() );
670 for ( int i = 1; i < classBreaks.size(); ++i )
671 {
672 const double minElevation = minMax[0] + classBreaks[i - 1] * frequencyClassRange;
673 const double maxElevation = minMax[0] + classBreaks[i] * frequencyClassRange;
674 resultList.push_back( QgsRelief::ReliefColor( colorList.at( i - 1 ), minElevation, maxElevation ) );
675 }
676
677 return resultList;
678}
679
680void QgsRelief::optimiseClassBreaks( QList<int> &breaks, double *frequencies )
681{
682 const int nClasses = breaks.size() - 1;
683 double *a = new double[nClasses]; //slopes
684 double *b = new double[nClasses]; //y-offsets
685
686 for ( int i = 0; i < nClasses; ++i )
687 {
688 //get all the values between the class breaks into input
689 QList<QPair<int, double>> regressionInput;
690 regressionInput.reserve( breaks.at( i + 1 ) - breaks.at( i ) );
691 for ( int j = breaks.at( i ); j < breaks.at( i + 1 ); ++j )
692 {
693 regressionInput.push_back( qMakePair( j, frequencies[j] ) );
694 }
695
696 double aParam, bParam;
697 if ( !regressionInput.isEmpty() && calculateRegression( regressionInput, aParam, bParam ) )
698 {
699 a[i] = aParam;
700 b[i] = bParam;
701 }
702 else
703 {
704 a[i] = 0;
705 b[i] = 0; //better default value
706 }
707 }
708
709 const QList<int> classesToRemove;
710
711 //shift class boundaries or eliminate classes which fall together
712 for ( int i = 1; i < nClasses; ++i )
713 {
714 if ( breaks[i] == breaks[i - 1] )
715 {
716 continue;
717 }
718
719 if ( qgsDoubleNear( a[i - 1], a[i] ) )
720 {
721 continue;
722 }
723 else
724 {
725 int newX = ( b[i - 1] - b[i] ) / ( a[i] - a[i - 1] );
726
727 if ( newX <= breaks[i - 1] )
728 {
729 newX = breaks[i - 1];
730 // classesToRemove.push_back( i );//remove this class later as it falls together with the preceding one
731 }
732 else if ( i < nClasses - 1 && newX >= breaks[i + 1] )
733 {
734 newX = breaks[i + 1];
735 // classesToRemove.push_back( i );//remove this class later as it falls together with the next one
736 }
737
738 breaks[i] = newX;
739 }
740 }
741
742 for ( int i = classesToRemove.size() - 1; i >= 0; --i )
743 {
744 breaks.removeAt( classesToRemove.at( i ) ); // cppcheck-suppress containerOutOfBounds
745 }
746
747 delete[] a;
748 delete[] b;
749}
750
751int QgsRelief::frequencyClassForElevation( double elevation, double minElevation, double elevationClassRange )
752{
753 return ( elevation - minElevation ) / elevationClassRange;
754}
755
756bool QgsRelief::calculateRegression( const QList<QPair<int, double>> &input, double &a, double &b )
757{
758 double xMean, yMean;
759 double xSum = 0;
760 double ySum = 0;
761 QList<QPair<int, double>>::const_iterator inputIt = input.constBegin();
762 for ( ; inputIt != input.constEnd(); ++inputIt )
763 {
764 xSum += inputIt->first;
765 ySum += inputIt->second;
766 }
767 xMean = xSum / input.size();
768 yMean = ySum / input.size();
769
770 double sumCounter = 0;
771 double sumDenominator = 0;
772 inputIt = input.constBegin();
773 for ( ; inputIt != input.constEnd(); ++inputIt )
774 {
775 sumCounter += ( ( inputIt->first - xMean ) * ( inputIt->second - yMean ) );
776 sumDenominator += ( ( inputIt->first - xMean ) * ( inputIt->first - xMean ) );
777 }
778
779 a = sumCounter / sumDenominator;
780 b = yMean - a * xMean;
781
782 return true;
783}
Calculates aspect values in a window of 3x3 cells based on first order derivatives in x- and y- direc...
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:55
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:63
Utility class to map from GDALProgressFunc to QgsFeedback.
static int CPL_STDCALL progressCallback(double dfComplete, const char *pszMessage, void *pProgressArg)
GDAL progress callback.
static bool supportsRasterCreate(GDALDriverH driver)
Reads whether a driver supports GDALCreate() for raster purposes.
A hillshade filter.
void clearReliefColors()
Definition qgsrelief.cpp:56
bool exportFrequencyDistributionToCsv(const QString &file)
Write frequency of elevation values to file for manual inspection.
QgsRelief(const QString &inputFile, const QString &outputFile, const QString &outputFormat)
Definition qgsrelief.cpp:40
QList< QgsRelief::ReliefColor > calculateOptimizedReliefClasses()
Calculates class breaks according with the method of Buenzli (2011) using an iterative algorithm for ...
int processRaster(QgsFeedback *feedback=nullptr)
Starts the calculation, reads from mInputFile and stores the result in mOutputFile.
Definition qgsrelief.cpp:77
void addReliefColorClass(const QgsRelief::ReliefColor &color)
Definition qgsrelief.cpp:61
Calculates slope values in a window of 3x3 cells based on first order derivatives in x- and y- direct...
void CORE_EXPORT fast_delete_and_close(dataset_unique_ptr &dataset, GDALDriverH driver, const QString &path)
Performs a fast close of an unwanted GDAL dataset handle by deleting the underlying data store.
std::unique_ptr< std::remove_pointer< GDALDatasetH >::type, GDALDatasetCloser > dataset_unique_ptr
Scoped GDAL dataset.
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:6924
void * GDALDatasetH
#define QgsDebugError(str)
Definition qgslogger.h:59