21#include <cpl_string.h>
38using namespace Qt::StringLiterals;
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 ) )
58 mReliefColors.clear();
63 mReliefColors.push_back( color );
66void QgsRelief::setDefaultReliefColors()
88 GDALDriverH outputDriver = openOutputDriver();
95 constexpr int RET_OUTPUT_CREATION_FAILED = 3;
98 return RET_OUTPUT_CREATION_FAILED;
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 );
119 GDALRasterBandH rasterBand = GDALGetRasterBand( inputDataset.get(), 1 );
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 );
131 GDALRasterBandH outputRedBand = GDALGetRasterBand( outputDataset.get(), 1 );
132 GDALRasterBandH outputGreenBand = GDALGetRasterBand( outputDataset.get(), 2 );
133 GDALRasterBandH outputBlueBand = GDALGetRasterBand( outputDataset.get(), 3 );
135 if ( !outputRedBand || !outputGreenBand || !outputBlueBand )
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 );
156 float *scanLine1 = (
float * ) CPLMalloc(
sizeof(
float ) * xSize );
157 float *scanLine2 = (
float * ) CPLMalloc(
sizeof(
float ) * xSize );
158 float *scanLine3 = (
float * ) CPLMalloc(
sizeof(
float ) * xSize );
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 );
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;
170 constexpr double maxProgressDuringBlockWriting = 100.0;
174 for (
int i = 0; i < ySize; ++i )
178 feedback->
setProgress( maxProgressDuringBlockWriting * i /
static_cast<double>( ySize ) );
189 for (
int a = 0; a < xSize; ++a )
191 scanLine1[a] = mInputNodataValue;
193 if ( GDALRasterIO( rasterBand, GF_Read, 0, 0, xSize, 1, scanLine2, xSize, 1, GDT_Float32, 0, 0 ) != CE_None )
201 CPLFree( scanLine1 );
202 scanLine1 = scanLine2;
203 scanLine2 = scanLine3;
204 scanLine3 = (
float * ) CPLMalloc(
sizeof(
float ) * xSize );
207 if ( i == ySize - 1 )
209 for (
int a = 0; a < xSize; ++a )
211 scanLine3[a] = mInputNodataValue;
216 if ( GDALRasterIO( rasterBand, GF_Read, 0, i + 1, xSize, 1, scanLine3, xSize, 1, GDT_Float32, 0, 0 ) != CE_None )
222 for (
int j = 0; j < xSize; ++j )
226 resultOk = processNineCellWindow(
241 else if ( j == xSize - 1 )
243 resultOk = processNineCellWindow(
260 resultOk = processNineCellWindow(
278 resultRedLine[j] = mOutputNodataValue;
279 resultGreenLine[j] = mOutputNodataValue;
280 resultBlueLine[j] = mOutputNodataValue;
284 if ( GDALRasterIO( outputRedBand, GF_Write, 0, i, xSize, 1, resultRedLine, xSize, 1, GDT_Byte, 0, 0 ) != CE_None )
288 if ( GDALRasterIO( outputGreenBand, GF_Write, 0, i, xSize, 1, resultGreenLine, xSize, 1, GDT_Byte, 0, 0 ) != CE_None )
292 if ( GDALRasterIO( outputBlueBand, GF_Write, 0, i, xSize, 1, resultBlueLine, xSize, 1, GDT_Byte, 0, 0 ) != CE_None )
303 CPLFree( resultRedLine );
304 CPLFree( resultBlueLine );
305 CPLFree( resultGreenLine );
306 CPLFree( scanLine1 );
307 CPLFree( scanLine2 );
308 CPLFree( scanLine3 );
310 constexpr int RET_CANCELED = 7;
317#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION( 3, 13, 0 )
318 else if ( hasReportsDuringClose && feedback )
323 return feedback->
isCanceled() ? RET_CANCELED : RET_OUTPUT_CREATION_FAILED;
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 )
338 const float hillShadeValue300 = mHillshadeFilter300->processNineCellWindow( x1, x2, x3, x4, x5, x6, x7, x8, x9 );
339 if ( hillShadeValue300 != mOutputNodataValue )
341 if ( !getElevationColor( *x5, &r, &g, &b ) )
343 r = hillShadeValue300;
344 g = hillShadeValue300;
345 b = hillShadeValue300;
349 r = r / 2.0 + hillShadeValue300 / 2.0;
350 g = g / 2.0 + hillShadeValue300 / 2.0;
351 b = b / 2.0 + hillShadeValue300 / 2.0;
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 )
363 r2 = 0 / 2.0 + hillShadeValue315 / 2.0;
364 g2 = 0 / 2.0 + hillShadeValue315 / 2.0;
365 b2 = 0 / 2.0 + hillShadeValue315 / 2.0;
367 else if ( slope >= 1 )
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;
376 r2 = hillShadeValue315;
377 g2 = hillShadeValue315;
378 b2 = hillShadeValue315;
382 r = r * 0.7 + r2 * 0.3;
383 g = g * 0.7 + g2 * 0.3;
384 b = b * 0.7 + b2 * 0.3;
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 )
392 double angle_diff = std::fabs( 285 - aspect );
393 if ( angle_diff > 180 )
399 if ( angle_diff < 90 )
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;
408 r3 = 0.5 * 255 + hillShadeValue315 * 0.5;
409 g3 = 0.5 * 255 + hillShadeValue315 * 0.5;
410 b3 = 0.5 * 255 + hillShadeValue315 * 0.5;
413 r = r3 * 0.1 + r * 0.9;
414 g = g3 * 0.1 + g * 0.9;
415 b = b3 * 0.1 + b * 0.9;
418 *red = (
unsigned char ) r;
419 *green = (
unsigned char ) g;
420 *blue = (
unsigned char ) b;
424bool QgsRelief::getElevationColor(
double elevation,
int *red,
int *green,
int *blue )
const
426 QList<ReliefColor>::const_iterator reliefColorIt = mReliefColors.constBegin();
427 for ( ; reliefColorIt != mReliefColors.constEnd(); ++reliefColorIt )
429 if ( elevation >= reliefColorIt->minElevation && elevation <= reliefColorIt->maxElevation )
431 const QColor &
c = reliefColorIt->color;
448 nCellsX = GDALGetRasterXSize( inputDataset.get() );
449 nCellsY = GDALGetRasterYSize( inputDataset.get() );
452 if ( GDALGetRasterCount( inputDataset.get() ) < 1 )
460GDALDriverH QgsRelief::openOutputDriver()
463 GDALDriverH outputDriver = GDALGetDriverByName( mOutputFormat.toLocal8Bit().data() );
485 const int xSize = GDALGetRasterXSize( inputDataset );
486 const int ySize = GDALGetRasterYSize( inputDataset );
489 char **papszOptions =
nullptr;
492 papszOptions = CSLSetNameValue( papszOptions,
"COMPRESS",
"PACKBITS" );
495 gdal::dataset_unique_ptr outputDataset( GDALCreate( outputDriver, mOutputFile.toUtf8().constData(), xSize, ySize, 3, GDT_Byte, papszOptions ) );
496 CSLDestroy( papszOptions );
497 papszOptions =
nullptr;
499 if ( !outputDataset )
505 double geotransform[6];
506 if ( GDALGetGeoTransform( inputDataset, geotransform ) != CE_None )
510 GDALSetGeoTransform( outputDataset.get(), geotransform );
513 mCellSizeX = geotransform[1];
514 if ( mCellSizeX < 0 )
516 mCellSizeX = -mCellSizeX;
518 mCellSizeY = geotransform[5];
519 if ( mCellSizeY < 0 )
521 mCellSizeY = -mCellSizeY;
524 const char *projection = GDALGetProjectionRef( inputDataset );
525 GDALSetProjection( outputDataset.get(), projection );
527 return outputDataset;
533 int nCellsX, nCellsY;
541 GDALRasterBandH elevationBand = GDALGetRasterBand( inputDataset.get(), 1 );
542 if ( !elevationBand )
550 minMax[0] = GDALGetRasterMinimum( elevationBand, &minOk );
551 minMax[1] = GDALGetRasterMaximum( elevationBand, &maxOk );
553 if ( !minOk || !maxOk )
555 GDALComputeRasterMinMax( elevationBand,
true, minMax );
561 double frequency[252] = { 0 };
562 const double frequencyClassRange = ( minMax[1] - minMax[0] ) / 252.0;
564 float *scanLine = (
float * ) CPLMalloc(
sizeof(
float ) * nCellsX );
565 int elevationClass = -1;
567 for (
int i = 0; i < nCellsY; ++i )
569 if ( GDALRasterIO( elevationBand, GF_Read, 0, i, nCellsX, 1, scanLine, nCellsX, 1, GDT_Float32, 0, 0 ) != CE_None )
574 for (
int j = 0; j < nCellsX; ++j )
576 elevationClass = frequencyClassForElevation( scanLine[j], minMax[0], frequencyClassRange );
577 if ( elevationClass >= 0 && elevationClass < 252 )
579 frequency[elevationClass] += 1.0;
587 for (
int i = 0; i < 252; ++i )
589 frequency[i] = std::log10( frequency[i] );
593 QFile outFile( file );
594 if ( !outFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
599 QTextStream outstream( &outFile );
600 for (
int i = 0; i < 252; ++i )
602 outstream << QString::number( i ) +
',' + QString::number( frequency[i] ) << Qt::endl;
610 QList<QgsRelief::ReliefColor> resultList;
612 int nCellsX, nCellsY;
620 GDALRasterBandH elevationBand = GDALGetRasterBand( inputDataset.get(), 1 );
621 if ( !elevationBand )
629 minMax[0] = GDALGetRasterMinimum( elevationBand, &minOk );
630 minMax[1] = GDALGetRasterMaximum( elevationBand, &maxOk );
632 if ( !minOk || !maxOk )
634 GDALComputeRasterMinMax( elevationBand,
true, minMax );
640 double frequency[252] = { 0 };
641 const double frequencyClassRange = ( minMax[1] - minMax[0] ) / 252.0;
643 float *scanLine = (
float * ) CPLMalloc(
sizeof(
float ) * nCellsX );
644 int elevationClass = -1;
646 for (
int i = 0; i < nCellsY; ++i )
648 if ( GDALRasterIO( elevationBand, GF_Read, 0, i, nCellsX, 1, scanLine, nCellsX, 1, GDT_Float32, 0, 0 ) != CE_None )
652 for (
int j = 0; j < nCellsX; ++j )
654 elevationClass = frequencyClassForElevation( scanLine[j], minMax[0], frequencyClassRange );
655 elevationClass = std::max( std::min( elevationClass, 251 ), 0 );
656 frequency[elevationClass] += 1.0;
663 for (
int i = 0; i < 252; ++i )
665 frequency[i] = std::log10( frequency[i] );
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 );
681 for (
int i = 0; i < 10; ++i )
683 optimiseClassBreaks( classBreaks, frequency );
687 for (
int i = 0; i < classBreaks.size(); ++i )
689 qWarning(
"%d", classBreaks[i] );
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 ) );
705 resultList.reserve( classBreaks.size() );
706 for (
int i = 1; i < classBreaks.size(); ++i )
708 const double minElevation = minMax[0] + classBreaks[i - 1] * frequencyClassRange;
709 const double maxElevation = minMax[0] + classBreaks[i] * frequencyClassRange;
716void QgsRelief::optimiseClassBreaks( QList<int> &breaks,
double *frequencies )
718 const int nClasses = breaks.size() - 1;
719 double *a =
new double[nClasses];
720 double *b =
new double[nClasses];
722 for (
int i = 0; i < nClasses; ++i )
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 )
729 regressionInput.push_back( qMakePair( j, frequencies[j] ) );
732 double aParam, bParam;
733 if ( !regressionInput.isEmpty() && calculateRegression( regressionInput, aParam, bParam ) )
745 const QList<int> classesToRemove;
748 for (
int i = 1; i < nClasses; ++i )
750 if ( breaks[i] == breaks[i - 1] )
761 int newX = ( b[i - 1] - b[i] ) / ( a[i] - a[i - 1] );
763 if ( newX <= breaks[i - 1] )
765 newX = breaks[i - 1];
768 else if ( i < nClasses - 1 && newX >= breaks[i + 1] )
770 newX = breaks[i + 1];
778 for (
int i = classesToRemove.size() - 1; i >= 0; --i )
780 breaks.removeAt( classesToRemove.at( i ) );
787int QgsRelief::frequencyClassForElevation(
double elevation,
double minElevation,
double elevationClassRange )
789 return ( elevation - minElevation ) / elevationClassRange;
792bool QgsRelief::calculateRegression(
const QList<QPair<int, double>> &input,
double &a,
double &b )
797 QList<QPair<int, double>>::const_iterator inputIt = input.constBegin();
798 for ( ; inputIt != input.constEnd(); ++inputIt )
800 xSum += inputIt->first;
801 ySum += inputIt->second;
803 xMean = xSum / input.size();
804 yMean = ySum / input.size();
806 double sumCounter = 0;
807 double sumDenominator = 0;
808 inputIt = input.constBegin();
809 for ( ; inputIt != input.constEnd(); ++inputIt )
811 sumCounter += ( ( inputIt->first - xMean ) * ( inputIt->second - yMean ) );
812 sumDenominator += ( ( inputIt->first - xMean ) * ( inputIt->first - xMean ) );
815 a = sumCounter / sumDenominator;
816 b = yMean - a * xMean;
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.
bool isCanceled() const
Tells whether the operation has been canceled already.
void setProgress(double progress)
Sets the current progress for the feedback object.
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.
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)
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.
void addReliefColorClass(const QgsRelief::ReliefColor &color)
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).
#define QgsDebugError(str)