QGIS API Documentation 4.1.0-Master (5bf3c20f3c9)
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(
227 &mInputNodataValue,
228 &scanLine1[j],
229 &scanLine1[j + 1],
230 &mInputNodataValue,
231 &scanLine2[j],
232 &scanLine2[j + 1],
233 &mInputNodataValue,
234 &scanLine3[j],
235 &scanLine3[j + 1],
236 &resultRedLine[j],
237 &resultGreenLine[j],
238 &resultBlueLine[j]
239 );
240 }
241 else if ( j == xSize - 1 )
242 {
243 resultOk = processNineCellWindow(
244 &scanLine1[j - 1],
245 &scanLine1[j],
246 &mInputNodataValue,
247 &scanLine2[j - 1],
248 &scanLine2[j],
249 &mInputNodataValue,
250 &scanLine3[j - 1],
251 &scanLine3[j],
252 &mInputNodataValue,
253 &resultRedLine[j],
254 &resultGreenLine[j],
255 &resultBlueLine[j]
256 );
257 }
258 else
259 {
260 resultOk = processNineCellWindow(
261 &scanLine1[j - 1],
262 &scanLine1[j],
263 &scanLine1[j + 1],
264 &scanLine2[j - 1],
265 &scanLine2[j],
266 &scanLine2[j + 1],
267 &scanLine3[j - 1],
268 &scanLine3[j],
269 &scanLine3[j + 1],
270 &resultRedLine[j],
271 &resultGreenLine[j],
272 &resultBlueLine[j]
273 );
274 }
275
276 if ( !resultOk )
277 {
278 resultRedLine[j] = mOutputNodataValue;
279 resultGreenLine[j] = mOutputNodataValue;
280 resultBlueLine[j] = mOutputNodataValue;
281 }
282 }
283
284 if ( GDALRasterIO( outputRedBand, GF_Write, 0, i, xSize, 1, resultRedLine, xSize, 1, GDT_Byte, 0, 0 ) != CE_None )
285 {
286 QgsDebugError( u"Raster IO Error"_s );
287 }
288 if ( GDALRasterIO( outputGreenBand, GF_Write, 0, i, xSize, 1, resultGreenLine, xSize, 1, GDT_Byte, 0, 0 ) != CE_None )
289 {
290 QgsDebugError( u"Raster IO Error"_s );
291 }
292 if ( GDALRasterIO( outputBlueBand, GF_Write, 0, i, xSize, 1, resultBlueLine, xSize, 1, GDT_Byte, 0, 0 ) != CE_None )
293 {
294 QgsDebugError( u"Raster IO Error"_s );
295 }
296 }
297
298 if ( feedback )
299 {
300 feedback->setProgress( 100 );
301 }
302
303 CPLFree( resultRedLine );
304 CPLFree( resultBlueLine );
305 CPLFree( resultGreenLine );
306 CPLFree( scanLine1 );
307 CPLFree( scanLine2 );
308 CPLFree( scanLine3 );
309
310 constexpr int RET_CANCELED = 7;
311 if ( feedback && feedback->isCanceled() )
312 {
313 //delete the dataset without closing (because it is faster)
314 gdal::fast_delete_and_close( outputDataset, outputDriver, mOutputFile );
315 return RET_CANCELED;
316 }
317#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION( 3, 13, 0 )
318 else if ( hasReportsDuringClose && feedback )
319 {
320 QgsGdalProgressAdapter progress( feedback, maxProgressDuringBlockWriting );
321 if ( GDALDatasetRunCloseWithoutDestroyingEx( outputDataset.get(), QgsGdalProgressAdapter::progressCallback, &progress ) != CE_None )
322 {
323 return feedback->isCanceled() ? RET_CANCELED : RET_OUTPUT_CREATION_FAILED;
324 }
325 }
326#endif
327
328 return 0;
329}
330
331bool 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 )
332{
333 //1. component: color and hillshade from 300 degrees
334 int r = 0;
335 int g = 0;
336 int b = 0;
337
338 const float hillShadeValue300 = mHillshadeFilter300->processNineCellWindow( x1, x2, x3, x4, x5, x6, x7, x8, x9 );
339 if ( hillShadeValue300 != mOutputNodataValue )
340 {
341 if ( !getElevationColor( *x5, &r, &g, &b ) )
342 {
343 r = hillShadeValue300;
344 g = hillShadeValue300;
345 b = hillShadeValue300;
346 }
347 else
348 {
349 r = r / 2.0 + hillShadeValue300 / 2.0;
350 g = g / 2.0 + hillShadeValue300 / 2.0;
351 b = b / 2.0 + hillShadeValue300 / 2.0;
352 }
353 }
354
355 //2. component: hillshade and slope
356 const float hillShadeValue315 = mHillshadeFilter315->processNineCellWindow( x1, x2, x3, x4, x5, x6, x7, x8, x9 );
357 const float slope = mSlopeFilter->processNineCellWindow( x1, x2, x3, x4, x5, x6, x7, x8, x9 );
358 if ( hillShadeValue315 != mOutputNodataValue && slope != mOutputNodataValue )
359 {
360 int r2, g2, b2;
361 if ( slope > 15 )
362 {
363 r2 = 0 / 2.0 + hillShadeValue315 / 2.0;
364 g2 = 0 / 2.0 + hillShadeValue315 / 2.0;
365 b2 = 0 / 2.0 + hillShadeValue315 / 2.0;
366 }
367 else if ( slope >= 1 )
368 {
369 const int slopeValue = 255 - ( slope / 15.0 * 255.0 );
370 r2 = slopeValue / 2.0 + hillShadeValue315 / 2.0;
371 g2 = slopeValue / 2.0 + hillShadeValue315 / 2.0;
372 b2 = slopeValue / 2.0 + hillShadeValue315 / 2.0;
373 }
374 else
375 {
376 r2 = hillShadeValue315;
377 g2 = hillShadeValue315;
378 b2 = hillShadeValue315;
379 }
380
381 //combine with r,g,b with 70 percentage coverage
382 r = r * 0.7 + r2 * 0.3;
383 g = g * 0.7 + g2 * 0.3;
384 b = b * 0.7 + b2 * 0.3;
385 }
386
387 //3. combine yellow aspect with 10% transparency, illumination from 285 degrees
388 const float hillShadeValue285 = mHillshadeFilter285->processNineCellWindow( x1, x2, x3, x4, x5, x6, x7, x8, x9 );
389 const float aspect = mAspectFilter->processNineCellWindow( x1, x2, x3, x4, x5, x6, x7, x8, x9 );
390 if ( hillShadeValue285 != mOutputNodataValue && aspect != mOutputNodataValue )
391 {
392 double angle_diff = std::fabs( 285 - aspect );
393 if ( angle_diff > 180 )
394 {
395 angle_diff -= 180;
396 }
397
398 int r3, g3, b3;
399 if ( angle_diff < 90 )
400 {
401 const int aspectVal = ( 1 - std::cos( angle_diff * M_PI / 180 ) ) * 255;
402 r3 = 0.5 * 255 + hillShadeValue315 * 0.5;
403 g3 = 0.5 * 255 + hillShadeValue315 * 0.5;
404 b3 = 0.5 * aspectVal + hillShadeValue315 * 0.5;
405 }
406 else //white
407 {
408 r3 = 0.5 * 255 + hillShadeValue315 * 0.5;
409 g3 = 0.5 * 255 + hillShadeValue315 * 0.5;
410 b3 = 0.5 * 255 + hillShadeValue315 * 0.5;
411 }
412
413 r = r3 * 0.1 + r * 0.9;
414 g = g3 * 0.1 + g * 0.9;
415 b = b3 * 0.1 + b * 0.9;
416 }
417
418 *red = ( unsigned char ) r;
419 *green = ( unsigned char ) g;
420 *blue = ( unsigned char ) b;
421 return true;
422}
423
424bool QgsRelief::getElevationColor( double elevation, int *red, int *green, int *blue ) const
425{
426 QList<ReliefColor>::const_iterator reliefColorIt = mReliefColors.constBegin();
427 for ( ; reliefColorIt != mReliefColors.constEnd(); ++reliefColorIt )
428 {
429 if ( elevation >= reliefColorIt->minElevation && elevation <= reliefColorIt->maxElevation )
430 {
431 const QColor &c = reliefColorIt->color;
432 *red = c.red();
433 *green = c.green();
434 *blue = c.blue();
435
436 return true;
437 }
438 }
439 return false;
440}
441
442//duplicated from QgsNineCellFilter. Todo: make common base class
443gdal::dataset_unique_ptr QgsRelief::openInputFile( int &nCellsX, int &nCellsY )
444{
445 gdal::dataset_unique_ptr inputDataset( GDALOpen( mInputFile.toUtf8().constData(), GA_ReadOnly ) );
446 if ( inputDataset )
447 {
448 nCellsX = GDALGetRasterXSize( inputDataset.get() );
449 nCellsY = GDALGetRasterYSize( inputDataset.get() );
450
451 //we need at least one band
452 if ( GDALGetRasterCount( inputDataset.get() ) < 1 )
453 {
454 return nullptr;
455 }
456 }
457 return inputDataset;
458}
459
460GDALDriverH QgsRelief::openOutputDriver()
461{
462 //open driver
463 GDALDriverH outputDriver = GDALGetDriverByName( mOutputFormat.toLocal8Bit().data() );
464
465 if ( !outputDriver )
466 {
467 return outputDriver; //return nullptr, driver does not exist
468 }
469
470 if ( !QgsGdalUtils::supportsRasterCreate( outputDriver ) )
471 {
472 return nullptr; //driver exist, but it does not support the create operation
473 }
474
475 return outputDriver;
476}
477
478gdal::dataset_unique_ptr QgsRelief::openOutputFile( GDALDatasetH inputDataset, GDALDriverH outputDriver )
479{
480 if ( !inputDataset )
481 {
482 return nullptr;
483 }
484
485 const int xSize = GDALGetRasterXSize( inputDataset );
486 const int ySize = GDALGetRasterYSize( inputDataset );
487
488 //open output file
489 char **papszOptions = nullptr;
490
491 //use PACKBITS compression for tiffs by default
492 papszOptions = CSLSetNameValue( papszOptions, "COMPRESS", "PACKBITS" );
493
494 //create three band raster (red, green, blue)
495 gdal::dataset_unique_ptr outputDataset( GDALCreate( outputDriver, mOutputFile.toUtf8().constData(), xSize, ySize, 3, GDT_Byte, papszOptions ) );
496 CSLDestroy( papszOptions );
497 papszOptions = nullptr;
498
499 if ( !outputDataset )
500 {
501 return nullptr;
502 }
503
504 //get geotransform from inputDataset
505 double geotransform[6];
506 if ( GDALGetGeoTransform( inputDataset, geotransform ) != CE_None )
507 {
508 return nullptr;
509 }
510 GDALSetGeoTransform( outputDataset.get(), geotransform );
511
512 //make sure mCellSizeX and mCellSizeY are always > 0
513 mCellSizeX = geotransform[1];
514 if ( mCellSizeX < 0 )
515 {
516 mCellSizeX = -mCellSizeX;
517 }
518 mCellSizeY = geotransform[5];
519 if ( mCellSizeY < 0 )
520 {
521 mCellSizeY = -mCellSizeY;
522 }
523
524 const char *projection = GDALGetProjectionRef( inputDataset );
525 GDALSetProjection( outputDataset.get(), projection );
526
527 return outputDataset;
528}
529
530//this function is mainly there for debugging
532{
533 int nCellsX, nCellsY;
534 const gdal::dataset_unique_ptr inputDataset = openInputFile( nCellsX, nCellsY );
535 if ( !inputDataset )
536 {
537 return false;
538 }
539
540 //open first raster band for reading (elevation raster is always single band)
541 GDALRasterBandH elevationBand = GDALGetRasterBand( inputDataset.get(), 1 );
542 if ( !elevationBand )
543 {
544 return false;
545 }
546
547 //1. get minimum and maximum of elevation raster -> 252 elevation classes
548 int minOk, maxOk;
549 double minMax[2];
550 minMax[0] = GDALGetRasterMinimum( elevationBand, &minOk );
551 minMax[1] = GDALGetRasterMaximum( elevationBand, &maxOk );
552
553 if ( !minOk || !maxOk )
554 {
555 GDALComputeRasterMinMax( elevationBand, true, minMax );
556 }
557
558 //2. go through raster cells and get frequency of classes
559
560 //store elevation frequency in 256 elevation classes
561 double frequency[252] = { 0 };
562 const double frequencyClassRange = ( minMax[1] - minMax[0] ) / 252.0;
563
564 float *scanLine = ( float * ) CPLMalloc( sizeof( float ) * nCellsX );
565 int elevationClass = -1;
566
567 for ( int i = 0; i < nCellsY; ++i )
568 {
569 if ( GDALRasterIO( elevationBand, GF_Read, 0, i, nCellsX, 1, scanLine, nCellsX, 1, GDT_Float32, 0, 0 ) != CE_None )
570 {
571 QgsDebugError( u"Raster IO Error"_s );
572 }
573
574 for ( int j = 0; j < nCellsX; ++j )
575 {
576 elevationClass = frequencyClassForElevation( scanLine[j], minMax[0], frequencyClassRange );
577 if ( elevationClass >= 0 && elevationClass < 252 )
578 {
579 frequency[elevationClass] += 1.0;
580 }
581 }
582 }
583
584 CPLFree( scanLine );
585
586 //log10 transformation for all frequency values
587 for ( int i = 0; i < 252; ++i )
588 {
589 frequency[i] = std::log10( frequency[i] );
590 }
591
592 //write out frequency values to csv file for debugging
593 QFile outFile( file );
594 if ( !outFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
595 {
596 return false;
597 }
598
599 QTextStream outstream( &outFile );
600 for ( int i = 0; i < 252; ++i )
601 {
602 outstream << QString::number( i ) + ',' + QString::number( frequency[i] ) << Qt::endl;
603 }
604 outFile.close();
605 return true;
606}
607
608QList<QgsRelief::ReliefColor> QgsRelief::calculateOptimizedReliefClasses()
609{
610 QList<QgsRelief::ReliefColor> resultList;
611
612 int nCellsX, nCellsY;
613 const gdal::dataset_unique_ptr inputDataset = openInputFile( nCellsX, nCellsY );
614 if ( !inputDataset )
615 {
616 return resultList;
617 }
618
619 //open first raster band for reading (elevation raster is always single band)
620 GDALRasterBandH elevationBand = GDALGetRasterBand( inputDataset.get(), 1 );
621 if ( !elevationBand )
622 {
623 return resultList;
624 }
625
626 //1. get minimum and maximum of elevation raster -> 252 elevation classes
627 int minOk, maxOk;
628 double minMax[2];
629 minMax[0] = GDALGetRasterMinimum( elevationBand, &minOk );
630 minMax[1] = GDALGetRasterMaximum( elevationBand, &maxOk );
631
632 if ( !minOk || !maxOk )
633 {
634 GDALComputeRasterMinMax( elevationBand, true, minMax );
635 }
636
637 //2. go through raster cells and get frequency of classes
638
639 //store elevation frequency in 256 elevation classes
640 double frequency[252] = { 0 };
641 const double frequencyClassRange = ( minMax[1] - minMax[0] ) / 252.0;
642
643 float *scanLine = ( float * ) CPLMalloc( sizeof( float ) * nCellsX );
644 int elevationClass = -1;
645
646 for ( int i = 0; i < nCellsY; ++i )
647 {
648 if ( GDALRasterIO( elevationBand, GF_Read, 0, i, nCellsX, 1, scanLine, nCellsX, 1, GDT_Float32, 0, 0 ) != CE_None )
649 {
650 QgsDebugError( u"Raster IO Error"_s );
651 }
652 for ( int j = 0; j < nCellsX; ++j )
653 {
654 elevationClass = frequencyClassForElevation( scanLine[j], minMax[0], frequencyClassRange );
655 elevationClass = std::max( std::min( elevationClass, 251 ), 0 );
656 frequency[elevationClass] += 1.0;
657 }
658 }
659
660 CPLFree( scanLine );
661
662 //log10 transformation for all frequency values
663 for ( int i = 0; i < 252; ++i )
664 {
665 frequency[i] = std::log10( frequency[i] );
666 }
667
668 //start with 9 uniformly distributed classes
669 QList<int> classBreaks;
670 classBreaks.append( 0 );
671 classBreaks.append( 28 );
672 classBreaks.append( 56 );
673 classBreaks.append( 84 );
674 classBreaks.append( 112 );
675 classBreaks.append( 140 );
676 classBreaks.append( 168 );
677 classBreaks.append( 196 );
678 classBreaks.append( 224 );
679 classBreaks.append( 252 );
680
681 for ( int i = 0; i < 10; ++i )
682 {
683 optimiseClassBreaks( classBreaks, frequency );
684 }
685
686 //debug, print out all the classbreaks
687 for ( int i = 0; i < classBreaks.size(); ++i )
688 {
689 qWarning( "%d", classBreaks[i] );
690 }
691
692 //set colors according to optimised class breaks
693 QVector<QColor> colorList;
694 colorList.reserve( 9 );
695 colorList.push_back( QColor( 7, 165, 144 ) );
696 colorList.push_back( QColor( 12, 221, 162 ) );
697 colorList.push_back( QColor( 33, 252, 183 ) );
698 colorList.push_back( QColor( 247, 252, 152 ) );
699 colorList.push_back( QColor( 252, 196, 8 ) );
700 colorList.push_back( QColor( 252, 166, 15 ) );
701 colorList.push_back( QColor( 175, 101, 15 ) );
702 colorList.push_back( QColor( 255, 133, 92 ) );
703 colorList.push_back( QColor( 204, 204, 204 ) );
704
705 resultList.reserve( classBreaks.size() );
706 for ( int i = 1; i < classBreaks.size(); ++i )
707 {
708 const double minElevation = minMax[0] + classBreaks[i - 1] * frequencyClassRange;
709 const double maxElevation = minMax[0] + classBreaks[i] * frequencyClassRange;
710 resultList.push_back( QgsRelief::ReliefColor( colorList.at( i - 1 ), minElevation, maxElevation ) );
711 }
712
713 return resultList;
714}
715
716void QgsRelief::optimiseClassBreaks( QList<int> &breaks, double *frequencies )
717{
718 const int nClasses = breaks.size() - 1;
719 double *a = new double[nClasses]; //slopes
720 double *b = new double[nClasses]; //y-offsets
721
722 for ( int i = 0; i < nClasses; ++i )
723 {
724 //get all the values between the class breaks into input
725 QList<QPair<int, double>> regressionInput;
726 regressionInput.reserve( breaks.at( i + 1 ) - breaks.at( i ) );
727 for ( int j = breaks.at( i ); j < breaks.at( i + 1 ); ++j )
728 {
729 regressionInput.push_back( qMakePair( j, frequencies[j] ) );
730 }
731
732 double aParam, bParam;
733 if ( !regressionInput.isEmpty() && calculateRegression( regressionInput, aParam, bParam ) )
734 {
735 a[i] = aParam;
736 b[i] = bParam;
737 }
738 else
739 {
740 a[i] = 0;
741 b[i] = 0; //better default value
742 }
743 }
744
745 const QList<int> classesToRemove;
746
747 //shift class boundaries or eliminate classes which fall together
748 for ( int i = 1; i < nClasses; ++i )
749 {
750 if ( breaks[i] == breaks[i - 1] )
751 {
752 continue;
753 }
754
755 if ( qgsDoubleNear( a[i - 1], a[i] ) )
756 {
757 continue;
758 }
759 else
760 {
761 int newX = ( b[i - 1] - b[i] ) / ( a[i] - a[i - 1] );
762
763 if ( newX <= breaks[i - 1] )
764 {
765 newX = breaks[i - 1];
766 // classesToRemove.push_back( i );//remove this class later as it falls together with the preceding one
767 }
768 else if ( i < nClasses - 1 && newX >= breaks[i + 1] )
769 {
770 newX = breaks[i + 1];
771 // classesToRemove.push_back( i );//remove this class later as it falls together with the next one
772 }
773
774 breaks[i] = newX;
775 }
776 }
777
778 for ( int i = classesToRemove.size() - 1; i >= 0; --i )
779 {
780 breaks.removeAt( classesToRemove.at( i ) ); // cppcheck-suppress containerOutOfBounds
781 }
782
783 delete[] a;
784 delete[] b;
785}
786
787int QgsRelief::frequencyClassForElevation( double elevation, double minElevation, double elevationClassRange )
788{
789 return ( elevation - minElevation ) / elevationClassRange;
790}
791
792bool QgsRelief::calculateRegression( const QList<QPair<int, double>> &input, double &a, double &b )
793{
794 double xMean, yMean;
795 double xSum = 0;
796 double ySum = 0;
797 QList<QPair<int, double>>::const_iterator inputIt = input.constBegin();
798 for ( ; inputIt != input.constEnd(); ++inputIt )
799 {
800 xSum += inputIt->first;
801 ySum += inputIt->second;
802 }
803 xMean = xSum / input.size();
804 yMean = ySum / input.size();
805
806 double sumCounter = 0;
807 double sumDenominator = 0;
808 inputIt = input.constBegin();
809 for ( ; inputIt != input.constEnd(); ++inputIt )
810 {
811 sumCounter += ( ( inputIt->first - xMean ) * ( inputIt->second - yMean ) );
812 sumDenominator += ( ( inputIt->first - xMean ) * ( inputIt->first - xMean ) );
813 }
814
815 a = sumCounter / sumDenominator;
816 b = yMean - a * xMean;
817
818 return true;
819}
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:56
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:65
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:6975
void * GDALDatasetH
#define QgsDebugError(str)
Definition qgslogger.h:59