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( &mInputNodataValue, &scanLine1[j], &scanLine1[j + 1], &mInputNodataValue, &scanLine2[j], &scanLine2[j + 1], &mInputNodataValue, &scanLine3[j], &scanLine3[j + 1], &resultRedLine[j], &resultGreenLine[j], &resultBlueLine[j] );
228 else if ( j == xSize - 1 )
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] );
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] );
239 resultRedLine[j] = mOutputNodataValue;
240 resultGreenLine[j] = mOutputNodataValue;
241 resultBlueLine[j] = mOutputNodataValue;
245 if ( GDALRasterIO( outputRedBand, GF_Write, 0, i, xSize, 1, resultRedLine, xSize, 1, GDT_Byte, 0, 0 ) != CE_None )
249 if ( GDALRasterIO( outputGreenBand, GF_Write, 0, i, xSize, 1, resultGreenLine, xSize, 1, GDT_Byte, 0, 0 ) != CE_None )
253 if ( GDALRasterIO( outputBlueBand, GF_Write, 0, i, xSize, 1, resultBlueLine, xSize, 1, GDT_Byte, 0, 0 ) != CE_None )
264 CPLFree( resultRedLine );
265 CPLFree( resultBlueLine );
266 CPLFree( resultGreenLine );
267 CPLFree( scanLine1 );
268 CPLFree( scanLine2 );
269 CPLFree( scanLine3 );
271 constexpr int RET_CANCELED = 7;
278#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION( 3, 13, 0 )
279 else if ( hasReportsDuringClose && feedback )
282 if ( GDALDatasetRunCloseWithoutDestroyingEx(
287 return feedback->
isCanceled() ? RET_CANCELED : RET_OUTPUT_CREATION_FAILED;
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 )
302 const float hillShadeValue300 = mHillshadeFilter300->processNineCellWindow( x1, x2, x3, x4, x5, x6, x7, x8, x9 );
303 if ( hillShadeValue300 != mOutputNodataValue )
305 if ( !getElevationColor( *x5, &r, &g, &b ) )
307 r = hillShadeValue300;
308 g = hillShadeValue300;
309 b = hillShadeValue300;
313 r = r / 2.0 + hillShadeValue300 / 2.0;
314 g = g / 2.0 + hillShadeValue300 / 2.0;
315 b = b / 2.0 + hillShadeValue300 / 2.0;
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 )
327 r2 = 0 / 2.0 + hillShadeValue315 / 2.0;
328 g2 = 0 / 2.0 + hillShadeValue315 / 2.0;
329 b2 = 0 / 2.0 + hillShadeValue315 / 2.0;
331 else if ( slope >= 1 )
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;
340 r2 = hillShadeValue315;
341 g2 = hillShadeValue315;
342 b2 = hillShadeValue315;
346 r = r * 0.7 + r2 * 0.3;
347 g = g * 0.7 + g2 * 0.3;
348 b = b * 0.7 + b2 * 0.3;
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 )
356 double angle_diff = std::fabs( 285 - aspect );
357 if ( angle_diff > 180 )
363 if ( angle_diff < 90 )
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;
372 r3 = 0.5 * 255 + hillShadeValue315 * 0.5;
373 g3 = 0.5 * 255 + hillShadeValue315 * 0.5;
374 b3 = 0.5 * 255 + hillShadeValue315 * 0.5;
377 r = r3 * 0.1 + r * 0.9;
378 g = g3 * 0.1 + g * 0.9;
379 b = b3 * 0.1 + b * 0.9;
382 *red = (
unsigned char ) r;
383 *green = (
unsigned char ) g;
384 *blue = (
unsigned char ) b;
388bool QgsRelief::getElevationColor(
double elevation,
int *red,
int *green,
int *blue )
const
390 QList<ReliefColor>::const_iterator reliefColorIt = mReliefColors.constBegin();
391 for ( ; reliefColorIt != mReliefColors.constEnd(); ++reliefColorIt )
393 if ( elevation >= reliefColorIt->minElevation && elevation <= reliefColorIt->maxElevation )
395 const QColor &
c = reliefColorIt->color;
412 nCellsX = GDALGetRasterXSize( inputDataset.get() );
413 nCellsY = GDALGetRasterYSize( inputDataset.get() );
416 if ( GDALGetRasterCount( inputDataset.get() ) < 1 )
424GDALDriverH QgsRelief::openOutputDriver()
427 GDALDriverH outputDriver = GDALGetDriverByName( mOutputFormat.toLocal8Bit().data() );
449 const int xSize = GDALGetRasterXSize( inputDataset );
450 const int ySize = GDALGetRasterYSize( inputDataset );
453 char **papszOptions =
nullptr;
456 papszOptions = CSLSetNameValue( papszOptions,
"COMPRESS",
"PACKBITS" );
459 gdal::dataset_unique_ptr outputDataset( GDALCreate( outputDriver, mOutputFile.toUtf8().constData(), xSize, ySize, 3, GDT_Byte, papszOptions ) );
460 CSLDestroy( papszOptions );
461 papszOptions =
nullptr;
463 if ( !outputDataset )
469 double geotransform[6];
470 if ( GDALGetGeoTransform( inputDataset, geotransform ) != CE_None )
474 GDALSetGeoTransform( outputDataset.get(), geotransform );
477 mCellSizeX = geotransform[1];
478 if ( mCellSizeX < 0 )
480 mCellSizeX = -mCellSizeX;
482 mCellSizeY = geotransform[5];
483 if ( mCellSizeY < 0 )
485 mCellSizeY = -mCellSizeY;
488 const char *projection = GDALGetProjectionRef( inputDataset );
489 GDALSetProjection( outputDataset.get(), projection );
491 return outputDataset;
497 int nCellsX, nCellsY;
505 GDALRasterBandH elevationBand = GDALGetRasterBand( inputDataset.get(), 1 );
506 if ( !elevationBand )
514 minMax[0] = GDALGetRasterMinimum( elevationBand, &minOk );
515 minMax[1] = GDALGetRasterMaximum( elevationBand, &maxOk );
517 if ( !minOk || !maxOk )
519 GDALComputeRasterMinMax( elevationBand,
true, minMax );
525 double frequency[252] = { 0 };
526 const double frequencyClassRange = ( minMax[1] - minMax[0] ) / 252.0;
528 float *scanLine = (
float * ) CPLMalloc(
sizeof(
float ) * nCellsX );
529 int elevationClass = -1;
531 for (
int i = 0; i < nCellsY; ++i )
533 if ( GDALRasterIO( elevationBand, GF_Read, 0, i, nCellsX, 1, scanLine, nCellsX, 1, GDT_Float32, 0, 0 ) != CE_None )
538 for (
int j = 0; j < nCellsX; ++j )
540 elevationClass = frequencyClassForElevation( scanLine[j], minMax[0], frequencyClassRange );
541 if ( elevationClass >= 0 && elevationClass < 252 )
543 frequency[elevationClass] += 1.0;
551 for (
int i = 0; i < 252; ++i )
553 frequency[i] = std::log10( frequency[i] );
557 QFile outFile( file );
558 if ( !outFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
563 QTextStream outstream( &outFile );
564 for (
int i = 0; i < 252; ++i )
566 outstream << QString::number( i ) +
',' + QString::number( frequency[i] ) << Qt::endl;
574 QList<QgsRelief::ReliefColor> resultList;
576 int nCellsX, nCellsY;
584 GDALRasterBandH elevationBand = GDALGetRasterBand( inputDataset.get(), 1 );
585 if ( !elevationBand )
593 minMax[0] = GDALGetRasterMinimum( elevationBand, &minOk );
594 minMax[1] = GDALGetRasterMaximum( elevationBand, &maxOk );
596 if ( !minOk || !maxOk )
598 GDALComputeRasterMinMax( elevationBand,
true, minMax );
604 double frequency[252] = { 0 };
605 const double frequencyClassRange = ( minMax[1] - minMax[0] ) / 252.0;
607 float *scanLine = (
float * ) CPLMalloc(
sizeof(
float ) * nCellsX );
608 int elevationClass = -1;
610 for (
int i = 0; i < nCellsY; ++i )
612 if ( GDALRasterIO( elevationBand, GF_Read, 0, i, nCellsX, 1, scanLine, nCellsX, 1, GDT_Float32, 0, 0 ) != CE_None )
616 for (
int j = 0; j < nCellsX; ++j )
618 elevationClass = frequencyClassForElevation( scanLine[j], minMax[0], frequencyClassRange );
619 elevationClass = std::max( std::min( elevationClass, 251 ), 0 );
620 frequency[elevationClass] += 1.0;
627 for (
int i = 0; i < 252; ++i )
629 frequency[i] = std::log10( frequency[i] );
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 );
645 for (
int i = 0; i < 10; ++i )
647 optimiseClassBreaks( classBreaks, frequency );
651 for (
int i = 0; i < classBreaks.size(); ++i )
653 qWarning(
"%d", classBreaks[i] );
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 ) );
669 resultList.reserve( classBreaks.size() );
670 for (
int i = 1; i < classBreaks.size(); ++i )
672 const double minElevation = minMax[0] + classBreaks[i - 1] * frequencyClassRange;
673 const double maxElevation = minMax[0] + classBreaks[i] * frequencyClassRange;
680void QgsRelief::optimiseClassBreaks( QList<int> &breaks,
double *frequencies )
682 const int nClasses = breaks.size() - 1;
683 double *a =
new double[nClasses];
684 double *b =
new double[nClasses];
686 for (
int i = 0; i < nClasses; ++i )
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 )
693 regressionInput.push_back( qMakePair( j, frequencies[j] ) );
696 double aParam, bParam;
697 if ( !regressionInput.isEmpty() && calculateRegression( regressionInput, aParam, bParam ) )
709 const QList<int> classesToRemove;
712 for (
int i = 1; i < nClasses; ++i )
714 if ( breaks[i] == breaks[i - 1] )
725 int newX = ( b[i - 1] - b[i] ) / ( a[i] - a[i - 1] );
727 if ( newX <= breaks[i - 1] )
729 newX = breaks[i - 1];
732 else if ( i < nClasses - 1 && newX >= breaks[i + 1] )
734 newX = breaks[i + 1];
742 for (
int i = classesToRemove.size() - 1; i >= 0; --i )
744 breaks.removeAt( classesToRemove.at( i ) );
751int QgsRelief::frequencyClassForElevation(
double elevation,
double minElevation,
double elevationClassRange )
753 return ( elevation - minElevation ) / elevationClassRange;
756bool QgsRelief::calculateRegression(
const QList<QPair<int, double>> &input,
double &a,
double &b )
761 QList<QPair<int, double>>::const_iterator inputIt = input.constBegin();
762 for ( ; inputIt != input.constEnd(); ++inputIt )
764 xSum += inputIt->first;
765 ySum += inputIt->second;
767 xMean = xSum / input.size();
768 yMean = ySum / input.size();
770 double sumCounter = 0;
771 double sumDenominator = 0;
772 inputIt = input.constBegin();
773 for ( ; inputIt != input.constEnd(); ++inputIt )
775 sumCounter += ( ( inputIt->first - xMean ) * ( inputIt->second - yMean ) );
776 sumDenominator += ( ( inputIt->first - xMean ) * ( inputIt->first - xMean ) );
779 a = sumCounter / sumDenominator;
780 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)