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