LCOV - code coverage report
Current view: top level - analysis/raster - qgskde.cpp (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 0 213 0.0 %
Date: 2021-03-26 12:19:53 Functions: 0 0 -
Branches: 0 0 -

           Branch data     Line data    Source code
       1                 :            : /***************************************************************************
       2                 :            :   qgskde.cpp
       3                 :            :   ----------
       4                 :            :   Date                 : October 2016
       5                 :            :   Copyright            : (C) 2016 by Nyall Dawson
       6                 :            :   Email                : nyall dot dawson at gmail dot com
       7                 :            :  ***************************************************************************
       8                 :            :  *                                                                         *
       9                 :            :  *   This program is free software; you can redistribute it and/or modify  *
      10                 :            :  *   it under the terms of the GNU General Public License as published by  *
      11                 :            :  *   the Free Software Foundation; either version 2 of the License, or     *
      12                 :            :  *   (at your option) any later version.                                   *
      13                 :            :  *                                                                         *
      14                 :            :  ***************************************************************************/
      15                 :            : 
      16                 :            : #include "qgskde.h"
      17                 :            : #include "qgsfeaturesource.h"
      18                 :            : #include "qgsfeatureiterator.h"
      19                 :            : #include "qgsgeometry.h"
      20                 :            : 
      21                 :            : #define NO_DATA -9999
      22                 :            : 
      23                 :          0 : QgsKernelDensityEstimation::QgsKernelDensityEstimation( const QgsKernelDensityEstimation::Parameters &parameters, const QString &outputFile, const QString &outputFormat )
      24                 :          0 :   : mSource( parameters.source )
      25                 :          0 :   , mOutputFile( outputFile )
      26                 :          0 :   , mOutputFormat( outputFormat )
      27                 :          0 :   , mRadiusField( -1 )
      28                 :          0 :   , mWeightField( -1 )
      29                 :          0 :   , mRadius( parameters.radius )
      30                 :          0 :   , mPixelSize( parameters.pixelSize )
      31                 :          0 :   , mShape( parameters.shape )
      32                 :          0 :   , mDecay( parameters.decayRatio )
      33                 :          0 :   , mOutputValues( parameters.outputValues )
      34                 :          0 :   , mBufferSize( -1 )
      35                 :          0 :   , mDatasetH( nullptr )
      36                 :          0 :   , mRasterBandH( nullptr )
      37                 :            : {
      38                 :          0 :   if ( !parameters.radiusField.isEmpty() )
      39                 :          0 :     mRadiusField = mSource->fields().lookupField( parameters.radiusField );
      40                 :          0 :   if ( !parameters.weightField.isEmpty() )
      41                 :          0 :     mWeightField = mSource->fields().lookupField( parameters.weightField );
      42                 :          0 : }
      43                 :            : 
      44                 :          0 : QgsKernelDensityEstimation::Result QgsKernelDensityEstimation::run()
      45                 :            : {
      46                 :          0 :   Result result = prepare();
      47                 :          0 :   if ( result != Success )
      48                 :          0 :     return result;
      49                 :            : 
      50                 :          0 :   QgsAttributeList requiredAttributes;
      51                 :            : 
      52                 :          0 :   if ( mRadiusField >= 0 )
      53                 :          0 :     requiredAttributes << mRadiusField;
      54                 :            : 
      55                 :          0 :   if ( mWeightField >= 0 )
      56                 :          0 :     requiredAttributes << mWeightField;
      57                 :            : 
      58                 :          0 :   QgsFeatureIterator fit = mSource->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( requiredAttributes ) );
      59                 :            : 
      60                 :          0 :   QgsFeature f;
      61                 :          0 :   while ( fit.nextFeature( f ) )
      62                 :            :   {
      63                 :          0 :     addFeature( f );
      64                 :            :   }
      65                 :            : 
      66                 :          0 :   return finalise();
      67                 :          0 : }
      68                 :            : 
      69                 :          0 : QgsKernelDensityEstimation::Result QgsKernelDensityEstimation::prepare()
      70                 :            : {
      71                 :          0 :   GDALAllRegister();
      72                 :            : 
      73                 :          0 :   GDALDriverH driver = GDALGetDriverByName( mOutputFormat.toUtf8() );
      74                 :          0 :   if ( !driver )
      75                 :            :   {
      76                 :          0 :     return DriverError;
      77                 :            :   }
      78                 :            : 
      79                 :          0 :   if ( !mSource )
      80                 :          0 :     return InvalidParameters;
      81                 :            : 
      82                 :          0 :   mBounds = calculateBounds();
      83                 :          0 :   if ( mBounds.isNull() )
      84                 :          0 :     return InvalidParameters;
      85                 :            : 
      86                 :          0 :   int rows = std::max( std::ceil( mBounds.height() / mPixelSize ) + 1, 1.0 );
      87                 :          0 :   int cols = std::max( std::ceil( mBounds.width() / mPixelSize ) + 1, 1.0 );
      88                 :            : 
      89                 :          0 :   if ( !createEmptyLayer( driver, mBounds, rows, cols ) )
      90                 :          0 :     return FileCreationError;
      91                 :            : 
      92                 :            :   // open the raster in GA_Update mode
      93                 :          0 :   mDatasetH.reset( GDALOpen( mOutputFile.toUtf8().constData(), GA_Update ) );
      94                 :          0 :   if ( !mDatasetH )
      95                 :          0 :     return FileCreationError;
      96                 :          0 :   mRasterBandH = GDALGetRasterBand( mDatasetH.get(), 1 );
      97                 :          0 :   if ( !mRasterBandH )
      98                 :          0 :     return FileCreationError;
      99                 :            : 
     100                 :          0 :   mBufferSize = -1;
     101                 :          0 :   if ( mRadiusField < 0 )
     102                 :          0 :     mBufferSize = radiusSizeInPixels( mRadius );
     103                 :            : 
     104                 :          0 :   return Success;
     105                 :          0 : }
     106                 :            : 
     107                 :          0 : QgsKernelDensityEstimation::Result QgsKernelDensityEstimation::addFeature( const QgsFeature &feature )
     108                 :            : {
     109                 :          0 :   QgsGeometry featureGeometry = feature.geometry();
     110                 :          0 :   if ( featureGeometry.isNull() )
     111                 :            :   {
     112                 :          0 :     return Success;
     113                 :            :   }
     114                 :            : 
     115                 :            :   // convert the geometry to multipoint
     116                 :          0 :   QgsMultiPointXY multiPoints;
     117                 :          0 :   if ( !featureGeometry.isMultipart() )
     118                 :            :   {
     119                 :          0 :     QgsPointXY p = featureGeometry.asPoint();
     120                 :            :     // avoiding any empty points or out of extent points
     121                 :          0 :     if ( !mBounds.contains( p ) )
     122                 :          0 :       return Success;
     123                 :            : 
     124                 :          0 :     multiPoints << p;
     125                 :          0 :   }
     126                 :            :   else
     127                 :            :   {
     128                 :          0 :     multiPoints = featureGeometry.asMultiPoint();
     129                 :            :   }
     130                 :            : 
     131                 :            :   // if radius is variable then fetch it and calculate new pixel buffer size
     132                 :          0 :   double radius = mRadius;
     133                 :          0 :   int buffer = mBufferSize;
     134                 :          0 :   if ( mRadiusField >= 0 )
     135                 :            :   {
     136                 :          0 :     radius = feature.attribute( mRadiusField ).toDouble();
     137                 :          0 :     buffer = radiusSizeInPixels( radius );
     138                 :          0 :   }
     139                 :          0 :   int blockSize = 2 * buffer + 1; //Block SIDE would be more appropriate
     140                 :            : 
     141                 :            :   // calculate weight
     142                 :          0 :   double weight = 1.0;
     143                 :          0 :   if ( mWeightField >= 0 )
     144                 :            :   {
     145                 :          0 :     weight = feature.attribute( mWeightField ).toDouble();
     146                 :          0 :   }
     147                 :            : 
     148                 :          0 :   Result result = Success;
     149                 :            : 
     150                 :            :   //loop through all points in multipoint
     151                 :          0 :   for ( QgsMultiPointXY::const_iterator pointIt = multiPoints.constBegin(); pointIt != multiPoints.constEnd(); ++pointIt )
     152                 :            :   {
     153                 :            :     // avoiding any empty points or out of extent points
     154                 :          0 :     if ( !mBounds.contains( *pointIt ) )
     155                 :            :     {
     156                 :          0 :       continue;
     157                 :            :     }
     158                 :            : 
     159                 :            :     // calculate the pixel position
     160                 :          0 :     unsigned int xPosition = ( ( ( *pointIt ).x() - mBounds.xMinimum() ) / mPixelSize ) - buffer;
     161                 :          0 :     unsigned int yPosition = ( ( ( *pointIt ).y() - mBounds.yMinimum() ) / mPixelSize ) - buffer;
     162                 :          0 :     unsigned int yPositionIO = ( ( mBounds.yMaximum() - ( *pointIt ).y() ) / mPixelSize ) - buffer;
     163                 :            : 
     164                 :            : 
     165                 :            :     // get the data
     166                 :          0 :     float *dataBuffer = ( float * ) CPLMalloc( sizeof( float ) * blockSize * blockSize );
     167                 :          0 :     if ( GDALRasterIO( mRasterBandH, GF_Read, xPosition, yPositionIO, blockSize, blockSize,
     168                 :          0 :                        dataBuffer, blockSize, blockSize, GDT_Float32, 0, 0 ) != CE_None )
     169                 :            :     {
     170                 :          0 :       result = RasterIoError;
     171                 :          0 :     }
     172                 :            : 
     173                 :          0 :     for ( int xp = 0; xp < blockSize; xp++ )
     174                 :            :     {
     175                 :          0 :       for ( int yp = 0; yp < blockSize; yp++ )
     176                 :            :       {
     177                 :          0 :         double pixelCentroidX = ( xPosition + xp + 0.5 ) * mPixelSize + mBounds.xMinimum();
     178                 :          0 :         double pixelCentroidY = ( yPosition + yp + 0.5 ) * mPixelSize + mBounds.yMinimum();
     179                 :            : 
     180                 :          0 :         double distance = std::sqrt( std::pow( pixelCentroidX - ( *pointIt ).x(), 2.0 ) + std::pow( pixelCentroidY - ( *pointIt ).y(), 2.0 ) );
     181                 :            : 
     182                 :            :         // is pixel outside search bandwidth of feature?
     183                 :          0 :         if ( distance > radius )
     184                 :            :         {
     185                 :          0 :           continue;
     186                 :            :         }
     187                 :            : 
     188                 :          0 :         double pixelValue = weight * calculateKernelValue( distance, radius, mShape, mOutputValues );
     189                 :          0 :         int pos = xp + blockSize * yp;
     190                 :          0 :         if ( dataBuffer[ pos ] == NO_DATA )
     191                 :            :         {
     192                 :          0 :           dataBuffer[ pos ] = 0;
     193                 :          0 :         }
     194                 :          0 :         dataBuffer[ pos ] += pixelValue;
     195                 :          0 :       }
     196                 :          0 :     }
     197                 :          0 :     if ( GDALRasterIO( mRasterBandH, GF_Write, xPosition, yPositionIO, blockSize, blockSize,
     198                 :          0 :                        dataBuffer, blockSize, blockSize, GDT_Float32, 0, 0 ) != CE_None )
     199                 :            :     {
     200                 :          0 :       result = RasterIoError;
     201                 :          0 :     }
     202                 :          0 :     CPLFree( dataBuffer );
     203                 :          0 :   }
     204                 :            : 
     205                 :          0 :   return result;
     206                 :          0 : }
     207                 :            : 
     208                 :          0 : QgsKernelDensityEstimation::Result QgsKernelDensityEstimation::finalise()
     209                 :            : {
     210                 :          0 :   mDatasetH.reset();
     211                 :          0 :   mRasterBandH = nullptr;
     212                 :          0 :   return Success;
     213                 :            : }
     214                 :            : 
     215                 :          0 : int QgsKernelDensityEstimation::radiusSizeInPixels( double radius ) const
     216                 :            : {
     217                 :          0 :   int buffer = radius / mPixelSize;
     218                 :          0 :   if ( radius - ( mPixelSize * buffer ) > 0.5 )
     219                 :            :   {
     220                 :          0 :     ++buffer;
     221                 :          0 :   }
     222                 :          0 :   return buffer;
     223                 :            : }
     224                 :            : 
     225                 :          0 : bool QgsKernelDensityEstimation::createEmptyLayer( GDALDriverH driver, const QgsRectangle &bounds, int rows, int columns ) const
     226                 :            : {
     227                 :          0 :   double geoTransform[6] = { bounds.xMinimum(), mPixelSize, 0, bounds.yMaximum(), 0, -mPixelSize };
     228                 :          0 :   gdal::dataset_unique_ptr emptyDataset( GDALCreate( driver, mOutputFile.toUtf8(), columns, rows, 1, GDT_Float32, nullptr ) );
     229                 :          0 :   if ( !emptyDataset )
     230                 :          0 :     return false;
     231                 :            : 
     232                 :          0 :   if ( GDALSetGeoTransform( emptyDataset.get(), geoTransform ) != CE_None )
     233                 :          0 :     return false;
     234                 :            : 
     235                 :            :   // Set the projection on the raster destination to match the input layer
     236                 :          0 :   if ( GDALSetProjection( emptyDataset.get(), mSource->sourceCrs().toWkt().toLocal8Bit().data() ) != CE_None )
     237                 :          0 :     return false;
     238                 :            : 
     239                 :          0 :   GDALRasterBandH poBand = GDALGetRasterBand( emptyDataset.get(), 1 );
     240                 :          0 :   if ( !poBand )
     241                 :          0 :     return false;
     242                 :            : 
     243                 :          0 :   if ( GDALSetRasterNoDataValue( poBand, NO_DATA ) != CE_None )
     244                 :          0 :     return false;
     245                 :            : 
     246                 :          0 :   float *line = static_cast< float * >( CPLMalloc( sizeof( float ) * columns ) );
     247                 :          0 :   for ( int i = 0; i < columns; i++ )
     248                 :            :   {
     249                 :          0 :     line[i] = NO_DATA;
     250                 :          0 :   }
     251                 :            :   // Write the empty raster
     252                 :          0 :   for ( int i = 0; i < rows ; i++ )
     253                 :            :   {
     254                 :          0 :     if ( GDALRasterIO( poBand, GF_Write, 0, i, columns, 1, line, columns, 1, GDT_Float32, 0, 0 ) != CE_None )
     255                 :            :     {
     256                 :          0 :       return false;
     257                 :            :     }
     258                 :          0 :   }
     259                 :            : 
     260                 :          0 :   CPLFree( line );
     261                 :          0 :   return true;
     262                 :          0 : }
     263                 :            : 
     264                 :          0 : double QgsKernelDensityEstimation::calculateKernelValue( const double distance, const double bandwidth, const QgsKernelDensityEstimation::KernelShape shape, const QgsKernelDensityEstimation::OutputValues outputType ) const
     265                 :            : {
     266                 :          0 :   switch ( shape )
     267                 :            :   {
     268                 :            :     case KernelTriangular:
     269                 :          0 :       return triangularKernel( distance, bandwidth, outputType );
     270                 :            : 
     271                 :            :     case KernelUniform:
     272                 :          0 :       return uniformKernel( distance, bandwidth, outputType );
     273                 :            : 
     274                 :            :     case KernelQuartic:
     275                 :          0 :       return quarticKernel( distance, bandwidth, outputType );
     276                 :            : 
     277                 :            :     case KernelTriweight:
     278                 :          0 :       return triweightKernel( distance, bandwidth, outputType );
     279                 :            : 
     280                 :            :     case KernelEpanechnikov:
     281                 :          0 :       return epanechnikovKernel( distance, bandwidth, outputType );
     282                 :            :   }
     283                 :          0 :   return 0; //no warnings
     284                 :          0 : }
     285                 :            : 
     286                 :            : /* The kernel functions below are taken from "Kernel Smoothing" by Wand and Jones (1995), p. 175
     287                 :            :  *
     288                 :            :  * Each kernel is multiplied by a normalizing constant "k", which normalizes the kernel area
     289                 :            :  * to 1 for a given bandwidth size.
     290                 :            :  *
     291                 :            :  * k is calculated by polar double integration of the kernel function
     292                 :            :  * between a radius of 0 to the specified bandwidth and equating the area to 1. */
     293                 :            : 
     294                 :          0 : double QgsKernelDensityEstimation::uniformKernel( const double distance, const double bandwidth, const QgsKernelDensityEstimation::OutputValues outputType ) const
     295                 :            : {
     296                 :            :   Q_UNUSED( distance )
     297                 :          0 :   switch ( outputType )
     298                 :            :   {
     299                 :            :     case OutputScaled:
     300                 :            :     {
     301                 :            :       // Normalizing constant
     302                 :          0 :       double k = 2. / ( M_PI * bandwidth );
     303                 :            : 
     304                 :            :       // Derived from Wand and Jones (1995), p. 175
     305                 :          0 :       return k * ( 0.5 / bandwidth );
     306                 :            :     }
     307                 :            :     case OutputRaw:
     308                 :          0 :       return 1.0;
     309                 :            :   }
     310                 :          0 :   return 0.0; // NO warnings!!!!!
     311                 :          0 : }
     312                 :            : 
     313                 :          0 : double QgsKernelDensityEstimation::quarticKernel( const double distance, const double bandwidth, const QgsKernelDensityEstimation::OutputValues outputType ) const
     314                 :            : {
     315                 :          0 :   switch ( outputType )
     316                 :            :   {
     317                 :            :     case OutputScaled:
     318                 :            :     {
     319                 :            :       // Normalizing constant
     320                 :          0 :       double k = 116. / ( 5. * M_PI * std::pow( bandwidth, 2 ) );
     321                 :            : 
     322                 :            :       // Derived from Wand and Jones (1995), p. 175
     323                 :          0 :       return k * ( 15. / 16. ) * std::pow( 1. - std::pow( distance / bandwidth, 2 ), 2 );
     324                 :            :     }
     325                 :            :     case OutputRaw:
     326                 :          0 :       return std::pow( 1. - std::pow( distance / bandwidth, 2 ), 2 );
     327                 :            :   }
     328                 :          0 :   return 0.0; //no, seriously, I told you NO WARNINGS!
     329                 :          0 : }
     330                 :            : 
     331                 :          0 : double QgsKernelDensityEstimation::triweightKernel( const double distance, const double bandwidth, const QgsKernelDensityEstimation::OutputValues outputType ) const
     332                 :            : {
     333                 :          0 :   switch ( outputType )
     334                 :            :   {
     335                 :            :     case OutputScaled:
     336                 :            :     {
     337                 :            :       // Normalizing constant
     338                 :          0 :       double k = 128. / ( 35. * M_PI * std::pow( bandwidth, 2 ) );
     339                 :            : 
     340                 :            :       // Derived from Wand and Jones (1995), p. 175
     341                 :          0 :       return k * ( 35. / 32. ) * std::pow( 1. - std::pow( distance / bandwidth, 2 ), 3 );
     342                 :            :     }
     343                 :            :     case OutputRaw:
     344                 :          0 :       return std::pow( 1. - std::pow( distance / bandwidth, 2 ), 3 );
     345                 :            :   }
     346                 :          0 :   return 0.0; // this is getting ridiculous... don't you ever listen to a word I say?
     347                 :          0 : }
     348                 :            : 
     349                 :          0 : double QgsKernelDensityEstimation::epanechnikovKernel( const double distance, const double bandwidth, const QgsKernelDensityEstimation::OutputValues outputType ) const
     350                 :            : {
     351                 :          0 :   switch ( outputType )
     352                 :            :   {
     353                 :            :     case OutputScaled:
     354                 :            :     {
     355                 :            :       // Normalizing constant
     356                 :          0 :       double k = 8. / ( 3. * M_PI * std::pow( bandwidth, 2 ) );
     357                 :            : 
     358                 :            :       // Derived from Wand and Jones (1995), p. 175
     359                 :          0 :       return k * ( 3. / 4. ) * ( 1. - std::pow( distance / bandwidth, 2 ) );
     360                 :            :     }
     361                 :            :     case OutputRaw:
     362                 :          0 :       return ( 1. - std::pow( distance / bandwidth, 2 ) );
     363                 :            :   }
     364                 :            : 
     365                 :          0 :   return 0.0; // la la la i'm not listening
     366                 :          0 : }
     367                 :            : 
     368                 :          0 : double QgsKernelDensityEstimation::triangularKernel( const double distance, const double bandwidth, const QgsKernelDensityEstimation::OutputValues outputType ) const
     369                 :            : {
     370                 :          0 :   switch ( outputType )
     371                 :            :   {
     372                 :            :     case OutputScaled:
     373                 :            :     {
     374                 :            :       // Normalizing constant. In this case it's calculated a little different
     375                 :            :       // due to the inclusion of the non-standard "decay" parameter
     376                 :            : 
     377                 :          0 :       if ( mDecay >= 0 )
     378                 :            :       {
     379                 :          0 :         double k = 3. / ( ( 1. + 2. * mDecay ) * M_PI * std::pow( bandwidth, 2 ) );
     380                 :            : 
     381                 :            :         // Derived from Wand and Jones (1995), p. 175 (with addition of decay parameter)
     382                 :          0 :         return k * ( 1. - ( 1. - mDecay ) * ( distance / bandwidth ) );
     383                 :            :       }
     384                 :            :       else
     385                 :            :       {
     386                 :            :         // Non-standard or mathematically valid negative decay ("coolmap")
     387                 :          0 :         return ( 1. - ( 1. - mDecay ) * ( distance / bandwidth ) );
     388                 :            :       }
     389                 :            :     }
     390                 :            :     case OutputRaw:
     391                 :          0 :       return ( 1. - ( 1. - mDecay ) * ( distance / bandwidth ) );
     392                 :            :   }
     393                 :          0 :   return 0.0; // ....
     394                 :          0 : }
     395                 :            : 
     396                 :          0 : QgsRectangle QgsKernelDensityEstimation::calculateBounds() const
     397                 :            : {
     398                 :          0 :   if ( !mSource )
     399                 :          0 :     return QgsRectangle();
     400                 :            : 
     401                 :          0 :   QgsRectangle bbox = mSource->sourceExtent();
     402                 :            : 
     403                 :          0 :   double radius = 0;
     404                 :          0 :   if ( mRadiusField >= 0 )
     405                 :            :   {
     406                 :            :     // if radius is using a field, find the max value
     407                 :          0 :     radius = mSource->maximumValue( mRadiusField ).toDouble();
     408                 :          0 :   }
     409                 :            :   else
     410                 :            :   {
     411                 :          0 :     radius = mRadius;
     412                 :            :   }
     413                 :            :   // expand the bounds by the maximum search radius
     414                 :          0 :   bbox.setXMinimum( bbox.xMinimum() - radius );
     415                 :          0 :   bbox.setYMinimum( bbox.yMinimum() - radius );
     416                 :          0 :   bbox.setXMaximum( bbox.xMaximum() + radius );
     417                 :          0 :   bbox.setYMaximum( bbox.yMaximum() + radius );
     418                 :          0 :   return bbox;
     419                 :          0 : }
     420                 :            : 
     421                 :            : 
     422                 :            : 
     423                 :            : 

Generated by: LCOV version 1.14