LCOV - code coverage report
Current view: top level - analysis/raster - qgsrelief.cpp (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 0 444 0.0 %
Date: 2021-04-10 08:29:14 Functions: 0 0 -
Branches: 0 0 -

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

Generated by: LCOV version 1.14