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

           Branch data     Line data    Source code
       1                 :            : /***************************************************************************
       2                 :            :     qgsheatmaprenderer.cpp
       3                 :            :     ----------------------
       4                 :            :     begin                : November 2014
       5                 :            :     copyright            : (C) 2014 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 "qgsheatmaprenderer.h"
      17                 :            : 
      18                 :            : #include "qgssymbol.h"
      19                 :            : #include "qgssymbollayerutils.h"
      20                 :            : 
      21                 :            : #include "qgslogger.h"
      22                 :            : #include "qgsfeature.h"
      23                 :            : #include "qgsvectorlayer.h"
      24                 :            : #include "qgssymbollayer.h"
      25                 :            : #include "qgsogcutils.h"
      26                 :            : #include "qgscolorramp.h"
      27                 :            : #include "qgsrendercontext.h"
      28                 :            : #include "qgspainteffect.h"
      29                 :            : #include "qgspainteffectregistry.h"
      30                 :            : #include "qgsstyleentityvisitor.h"
      31                 :            : 
      32                 :            : #include <QDomDocument>
      33                 :            : #include <QDomElement>
      34                 :            : 
      35                 :          0 : QgsHeatmapRenderer::QgsHeatmapRenderer()
      36                 :          0 :   : QgsFeatureRenderer( QStringLiteral( "heatmapRenderer" ) )
      37                 :          0 : {
      38                 :          0 :   mGradientRamp = new QgsGradientColorRamp( QColor( 255, 255, 255 ), QColor( 0, 0, 0 ) );
      39                 :          0 : }
      40                 :            : 
      41                 :          0 : QgsHeatmapRenderer::~QgsHeatmapRenderer()
      42                 :          0 : {
      43                 :          0 :   delete mGradientRamp;
      44                 :          0 : }
      45                 :            : 
      46                 :          0 : void QgsHeatmapRenderer::initializeValues( QgsRenderContext &context )
      47                 :            : {
      48                 :          0 :   mValues.resize( context.painter()->device()->width() * context.painter()->device()->height() / ( mRenderQuality * mRenderQuality ) );
      49                 :          0 :   mValues.fill( 0 );
      50                 :          0 :   mCalculatedMaxValue = 0;
      51                 :          0 :   mFeaturesRendered = 0;
      52                 :          0 :   mRadiusPixels = std::round( context.convertToPainterUnits( mRadius, mRadiusUnit, mRadiusMapUnitScale ) / mRenderQuality );
      53                 :          0 :   mRadiusSquared = mRadiusPixels * mRadiusPixels;
      54                 :          0 : }
      55                 :            : 
      56                 :          0 : void QgsHeatmapRenderer::startRender( QgsRenderContext &context, const QgsFields &fields )
      57                 :            : {
      58                 :          0 :   QgsFeatureRenderer::startRender( context, fields );
      59                 :            : 
      60                 :          0 :   if ( !context.painter() )
      61                 :            :   {
      62                 :          0 :     return;
      63                 :            :   }
      64                 :            : 
      65                 :            :   // find out classification attribute index from name
      66                 :          0 :   mWeightAttrNum = fields.lookupField( mWeightExpressionString );
      67                 :          0 :   if ( mWeightAttrNum == -1 )
      68                 :            :   {
      69                 :          0 :     mWeightExpression.reset( new QgsExpression( mWeightExpressionString ) );
      70                 :          0 :     mWeightExpression->prepare( &context.expressionContext() );
      71                 :          0 :   }
      72                 :            : 
      73                 :          0 :   initializeValues( context );
      74                 :          0 : }
      75                 :            : 
      76                 :          0 : QgsMultiPointXY QgsHeatmapRenderer::convertToMultipoint( const QgsGeometry *geom )
      77                 :            : {
      78                 :          0 :   QgsMultiPointXY multiPoint;
      79                 :          0 :   if ( !geom->isMultipart() )
      80                 :            :   {
      81                 :          0 :     multiPoint << geom->asPoint();
      82                 :          0 :   }
      83                 :            :   else
      84                 :            :   {
      85                 :          0 :     multiPoint = geom->asMultiPoint();
      86                 :            :   }
      87                 :            : 
      88                 :          0 :   return multiPoint;
      89                 :          0 : }
      90                 :            : 
      91                 :          0 : bool QgsHeatmapRenderer::renderFeature( const QgsFeature &feature, QgsRenderContext &context, int layer, bool selected, bool drawVertexMarker )
      92                 :            : {
      93                 :            :   Q_UNUSED( layer )
      94                 :            :   Q_UNUSED( selected )
      95                 :            :   Q_UNUSED( drawVertexMarker )
      96                 :            : 
      97                 :          0 :   if ( !context.painter() )
      98                 :            :   {
      99                 :          0 :     return false;
     100                 :            :   }
     101                 :            : 
     102                 :          0 :   if ( !feature.hasGeometry() || feature.geometry().type() != QgsWkbTypes::PointGeometry )
     103                 :            :   {
     104                 :            :     //can only render point type
     105                 :          0 :     return false;
     106                 :            :   }
     107                 :            : 
     108                 :          0 :   double weight = 1.0;
     109                 :          0 :   if ( !mWeightExpressionString.isEmpty() )
     110                 :            :   {
     111                 :          0 :     QVariant value;
     112                 :          0 :     if ( mWeightAttrNum == -1 )
     113                 :            :     {
     114                 :            :       Q_ASSERT( mWeightExpression.get() );
     115                 :          0 :       value = mWeightExpression->evaluate( &context.expressionContext() );
     116                 :          0 :     }
     117                 :            :     else
     118                 :            :     {
     119                 :          0 :       QgsAttributes attrs = feature.attributes();
     120                 :          0 :       value = attrs.value( mWeightAttrNum );
     121                 :          0 :     }
     122                 :          0 :     bool ok = false;
     123                 :          0 :     double evalWeight = value.toDouble( &ok );
     124                 :          0 :     if ( ok )
     125                 :            :     {
     126                 :          0 :       weight = evalWeight;
     127                 :          0 :     }
     128                 :          0 :   }
     129                 :            : 
     130                 :          0 :   int width = context.painter()->device()->width() / mRenderQuality;
     131                 :          0 :   int height = context.painter()->device()->height() / mRenderQuality;
     132                 :            : 
     133                 :            :   //transform geometry if required
     134                 :          0 :   QgsGeometry geom = feature.geometry();
     135                 :          0 :   QgsCoordinateTransform xform = context.coordinateTransform();
     136                 :          0 :   if ( xform.isValid() )
     137                 :            :   {
     138                 :          0 :     geom.transform( xform );
     139                 :          0 :   }
     140                 :            : 
     141                 :            :   //convert point to multipoint
     142                 :          0 :   QgsMultiPointXY multiPoint = convertToMultipoint( &geom );
     143                 :            : 
     144                 :            :   //loop through all points in multipoint
     145                 :          0 :   for ( QgsMultiPointXY::const_iterator pointIt = multiPoint.constBegin(); pointIt != multiPoint.constEnd(); ++pointIt )
     146                 :            :   {
     147                 :          0 :     QgsPointXY pixel = context.mapToPixel().transform( *pointIt );
     148                 :          0 :     int pointX = pixel.x() / mRenderQuality;
     149                 :          0 :     int pointY = pixel.y() / mRenderQuality;
     150                 :          0 :     for ( int x = std::max( pointX - mRadiusPixels, 0 ); x < std::min( pointX + mRadiusPixels, width ); ++x )
     151                 :            :     {
     152                 :          0 :       if ( context.renderingStopped() )
     153                 :          0 :         break;
     154                 :            : 
     155                 :          0 :       for ( int y = std::max( pointY - mRadiusPixels, 0 ); y < std::min( pointY + mRadiusPixels, height ); ++y )
     156                 :            :       {
     157                 :          0 :         int index = y * width + x;
     158                 :          0 :         if ( index >= mValues.count() )
     159                 :            :         {
     160                 :          0 :           continue;
     161                 :            :         }
     162                 :          0 :         double distanceSquared = std::pow( pointX - x, 2.0 ) + std::pow( pointY - y, 2.0 );
     163                 :          0 :         if ( distanceSquared > mRadiusSquared )
     164                 :            :         {
     165                 :          0 :           continue;
     166                 :            :         }
     167                 :            : 
     168                 :          0 :         double score = weight * quarticKernel( std::sqrt( distanceSquared ), mRadiusPixels );
     169                 :          0 :         double value = mValues.at( index ) + score;
     170                 :          0 :         if ( value > mCalculatedMaxValue )
     171                 :            :         {
     172                 :          0 :           mCalculatedMaxValue = value;
     173                 :          0 :         }
     174                 :          0 :         mValues[ index ] = value;
     175                 :          0 :       }
     176                 :          0 :     }
     177                 :          0 :   }
     178                 :            : 
     179                 :          0 :   mFeaturesRendered++;
     180                 :            : #if 0
     181                 :            :   //TODO - enable progressive rendering
     182                 :            :   if ( mFeaturesRendered % 200  == 0 )
     183                 :            :   {
     184                 :            :     renderImage( context );
     185                 :            :   }
     186                 :          0 : #endif
     187                 :          0 :   return true;
     188                 :          0 : }
     189                 :          0 : 
     190                 :          0 : 
     191                 :          0 : double QgsHeatmapRenderer::uniformKernel( const double distance, const int bandwidth ) const
     192                 :            : {
     193                 :            :   Q_UNUSED( distance )
     194                 :            :   Q_UNUSED( bandwidth )
     195                 :          0 :   return 1.0;
     196                 :            : }
     197                 :            : 
     198                 :          0 : double QgsHeatmapRenderer::quarticKernel( const double distance, const int bandwidth ) const
     199                 :            : {
     200                 :          0 :   return std::pow( 1. - std::pow( distance / static_cast< double >( bandwidth ), 2 ), 2 );
     201                 :          0 : }
     202                 :            : 
     203                 :          0 : double QgsHeatmapRenderer::triweightKernel( const double distance, const int bandwidth ) const
     204                 :            : {
     205                 :          0 :   return std::pow( 1. - std::pow( distance / static_cast< double >( bandwidth ), 2 ), 3 );
     206                 :            : }
     207                 :            : 
     208                 :          0 : double QgsHeatmapRenderer::epanechnikovKernel( const double distance, const int bandwidth ) const
     209                 :            : {
     210                 :          0 :   return ( 1. - std::pow( distance / static_cast< double >( bandwidth ), 2 ) );
     211                 :            : }
     212                 :            : 
     213                 :          0 : double QgsHeatmapRenderer::triangularKernel( const double distance, const int bandwidth ) const
     214                 :            : {
     215                 :          0 :   return ( 1. - ( distance / static_cast< double >( bandwidth ) ) );
     216                 :            : }
     217                 :            : 
     218                 :          0 : void QgsHeatmapRenderer::stopRender( QgsRenderContext &context )
     219                 :            : {
     220                 :          0 :   QgsFeatureRenderer::stopRender( context );
     221                 :            : 
     222                 :          0 :   renderImage( context );
     223                 :          0 :   mWeightExpression.reset();
     224                 :          0 : }
     225                 :            : 
     226                 :          0 : void QgsHeatmapRenderer::renderImage( QgsRenderContext &context )
     227                 :            : {
     228                 :          0 :   if ( !context.painter() || !mGradientRamp || context.renderingStopped() )
     229                 :            :   {
     230                 :          0 :     return;
     231                 :            :   }
     232                 :            : 
     233                 :          0 :   QImage image( context.painter()->device()->width() / mRenderQuality,
     234                 :          0 :                 context.painter()->device()->height() / mRenderQuality,
     235                 :            :                 QImage::Format_ARGB32 );
     236                 :          0 :   image.fill( Qt::transparent );
     237                 :            : 
     238                 :          0 :   double scaleMax = mExplicitMax > 0 ? mExplicitMax : mCalculatedMaxValue;
     239                 :            : 
     240                 :          0 :   int idx = 0;
     241                 :          0 :   double pixVal = 0;
     242                 :          0 :   QColor pixColor;
     243                 :          0 :   for ( int heightIndex = 0; heightIndex < image.height(); ++heightIndex )
     244                 :            :   {
     245                 :          0 :     if ( context.renderingStopped() )
     246                 :          0 :       break;
     247                 :            : 
     248                 :          0 :     QRgb *scanLine = reinterpret_cast< QRgb * >( image.scanLine( heightIndex ) );
     249                 :          0 :     for ( int widthIndex = 0; widthIndex < image.width(); ++widthIndex )
     250                 :            :     {
     251                 :            :       //scale result to fit in the range [0, 1]
     252                 :          0 :       pixVal = mValues.at( idx ) > 0 ? std::min( ( mValues.at( idx ) / scaleMax ), 1.0 ) : 0;
     253                 :            : 
     254                 :            :       //convert value to color from ramp
     255                 :          0 :       pixColor = mGradientRamp->color( pixVal );
     256                 :            : 
     257                 :          0 :       scanLine[widthIndex] = pixColor.rgba();
     258                 :          0 :       idx++;
     259                 :          0 :     }
     260                 :          0 :   }
     261                 :            : 
     262                 :          0 :   if ( mRenderQuality > 1 )
     263                 :            :   {
     264                 :          0 :     QImage resized = image.scaled( context.painter()->device()->width(),
     265                 :          0 :                                    context.painter()->device()->height() );
     266                 :          0 :     context.painter()->drawImage( 0, 0, resized );
     267                 :          0 :   }
     268                 :            :   else
     269                 :            :   {
     270                 :          0 :     context.painter()->drawImage( 0, 0, image );
     271                 :            :   }
     272                 :          0 : }
     273                 :            : 
     274                 :          0 : QString QgsHeatmapRenderer::dump() const
     275                 :            : {
     276                 :          0 :   return QStringLiteral( "[HEATMAP]" );
     277                 :            : }
     278                 :            : 
     279                 :          0 : QgsHeatmapRenderer *QgsHeatmapRenderer::clone() const
     280                 :            : {
     281                 :          0 :   QgsHeatmapRenderer *newRenderer = new QgsHeatmapRenderer();
     282                 :          0 :   if ( mGradientRamp )
     283                 :            :   {
     284                 :          0 :     newRenderer->setColorRamp( mGradientRamp->clone() );
     285                 :          0 :   }
     286                 :          0 :   newRenderer->setRadius( mRadius );
     287                 :          0 :   newRenderer->setRadiusUnit( mRadiusUnit );
     288                 :          0 :   newRenderer->setRadiusMapUnitScale( mRadiusMapUnitScale );
     289                 :          0 :   newRenderer->setMaximumValue( mExplicitMax );
     290                 :          0 :   newRenderer->setRenderQuality( mRenderQuality );
     291                 :          0 :   newRenderer->setWeightExpression( mWeightExpressionString );
     292                 :          0 :   copyRendererData( newRenderer );
     293                 :            : 
     294                 :          0 :   return newRenderer;
     295                 :          0 : }
     296                 :            : 
     297                 :          0 : void QgsHeatmapRenderer::modifyRequestExtent( QgsRectangle &extent, QgsRenderContext &context )
     298                 :            : {
     299                 :            :   //we need to expand out the request extent so that it includes points which are up to the heatmap radius outside of the
     300                 :            :   //actual visible extent
     301                 :          0 :   double extension = context.convertToMapUnits( mRadius, mRadiusUnit, mRadiusMapUnitScale );
     302                 :          0 :   extent.setXMinimum( extent.xMinimum() - extension );
     303                 :          0 :   extent.setXMaximum( extent.xMaximum() + extension );
     304                 :          0 :   extent.setYMinimum( extent.yMinimum() - extension );
     305                 :          0 :   extent.setYMaximum( extent.yMaximum() + extension );
     306                 :          0 : }
     307                 :            : 
     308                 :          0 : QgsFeatureRenderer *QgsHeatmapRenderer::create( QDomElement &element, const QgsReadWriteContext &context )
     309                 :            : {
     310                 :          0 :   Q_UNUSED( context )
     311                 :          0 :   QgsHeatmapRenderer *r = new QgsHeatmapRenderer();
     312                 :          0 :   r->setRadius( element.attribute( QStringLiteral( "radius" ), QStringLiteral( "50.0" ) ).toFloat() );
     313                 :          0 :   r->setRadiusUnit( static_cast< QgsUnitTypes::RenderUnit >( element.attribute( QStringLiteral( "radius_unit" ), QStringLiteral( "0" ) ).toInt() ) );
     314                 :          0 :   r->setRadiusMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( element.attribute( QStringLiteral( "radius_map_unit_scale" ), QString() ) ) );
     315                 :          0 :   r->setMaximumValue( element.attribute( QStringLiteral( "max_value" ), QStringLiteral( "0.0" ) ).toFloat() );
     316                 :          0 :   r->setRenderQuality( element.attribute( QStringLiteral( "quality" ), QStringLiteral( "0" ) ).toInt() );
     317                 :          0 :   r->setWeightExpression( element.attribute( QStringLiteral( "weight_expression" ) ) );
     318                 :            : 
     319                 :          0 :   QDomElement sourceColorRampElem = element.firstChildElement( QStringLiteral( "colorramp" ) );
     320                 :          0 :   if ( !sourceColorRampElem.isNull() && sourceColorRampElem.attribute( QStringLiteral( "name" ) ) == QLatin1String( "[source]" ) )
     321                 :            :   {
     322                 :          0 :     r->setColorRamp( QgsSymbolLayerUtils::loadColorRamp( sourceColorRampElem ) );
     323                 :          0 :   }
     324                 :          0 :   return r;
     325                 :          0 : }
     326                 :            : 
     327                 :          0 : QDomElement QgsHeatmapRenderer::save( QDomDocument &doc, const QgsReadWriteContext &context )
     328                 :            : {
     329                 :          0 :   Q_UNUSED( context )
     330                 :          0 :   QDomElement rendererElem = doc.createElement( RENDERER_TAG_NAME );
     331                 :          0 :   rendererElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "heatmapRenderer" ) );
     332                 :          0 :   rendererElem.setAttribute( QStringLiteral( "radius" ), QString::number( mRadius ) );
     333                 :          0 :   rendererElem.setAttribute( QStringLiteral( "radius_unit" ), QString::number( mRadiusUnit ) );
     334                 :          0 :   rendererElem.setAttribute( QStringLiteral( "radius_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mRadiusMapUnitScale ) );
     335                 :          0 :   rendererElem.setAttribute( QStringLiteral( "max_value" ), QString::number( mExplicitMax ) );
     336                 :          0 :   rendererElem.setAttribute( QStringLiteral( "quality" ), QString::number( mRenderQuality ) );
     337                 :          0 :   rendererElem.setAttribute( QStringLiteral( "weight_expression" ), mWeightExpressionString );
     338                 :          0 :   if ( mGradientRamp )
     339                 :            :   {
     340                 :          0 :     QDomElement colorRampElem = QgsSymbolLayerUtils::saveColorRamp( QStringLiteral( "[source]" ), mGradientRamp, doc );
     341                 :          0 :     rendererElem.appendChild( colorRampElem );
     342                 :          0 :   }
     343                 :          0 :   rendererElem.setAttribute( QStringLiteral( "forceraster" ), ( mForceRaster ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ) );
     344                 :            : 
     345                 :          0 :   if ( mPaintEffect && !QgsPaintEffectRegistry::isDefaultStack( mPaintEffect ) )
     346                 :          0 :     mPaintEffect->saveProperties( doc, rendererElem );
     347                 :            : 
     348                 :          0 :   if ( !mOrderBy.isEmpty() )
     349                 :            :   {
     350                 :          0 :     QDomElement orderBy = doc.createElement( QStringLiteral( "orderby" ) );
     351                 :          0 :     mOrderBy.save( orderBy );
     352                 :          0 :     rendererElem.appendChild( orderBy );
     353                 :          0 :   }
     354                 :          0 :   rendererElem.setAttribute( QStringLiteral( "enableorderby" ), ( mOrderByEnabled ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ) );
     355                 :            : 
     356                 :          0 :   return rendererElem;
     357                 :          0 : }
     358                 :            : 
     359                 :          0 : QgsSymbol *QgsHeatmapRenderer::symbolForFeature( const QgsFeature &feature, QgsRenderContext & ) const
     360                 :            : {
     361                 :          0 :   Q_UNUSED( feature )
     362                 :          0 :   return nullptr;
     363                 :            : }
     364                 :            : 
     365                 :          0 : QgsSymbolList QgsHeatmapRenderer::symbols( QgsRenderContext & ) const
     366                 :            : {
     367                 :          0 :   return QgsSymbolList();
     368                 :            : }
     369                 :            : 
     370                 :          0 : QSet<QString> QgsHeatmapRenderer::usedAttributes( const QgsRenderContext & ) const
     371                 :            : {
     372                 :          0 :   QSet<QString> attributes;
     373                 :            : 
     374                 :            :   // mAttrName can contain either attribute name or an expression.
     375                 :            :   // Sometimes it is not possible to distinguish between those two,
     376                 :            :   // e.g. "a - b" can be both a valid attribute name or expression.
     377                 :            :   // Since we do not have access to fields here, try both options.
     378                 :          0 :   attributes << mWeightExpressionString;
     379                 :            : 
     380                 :          0 :   QgsExpression testExpr( mWeightExpressionString );
     381                 :          0 :   if ( !testExpr.hasParserError() )
     382                 :          0 :     attributes.unite( testExpr.referencedColumns() );
     383                 :            : 
     384                 :          0 :   return attributes;
     385                 :          0 : }
     386                 :            : 
     387                 :          0 : QgsHeatmapRenderer *QgsHeatmapRenderer::convertFromRenderer( const QgsFeatureRenderer *renderer )
     388                 :            : {
     389                 :          0 :   if ( renderer->type() == QLatin1String( "heatmapRenderer" ) )
     390                 :            :   {
     391                 :          0 :     return dynamic_cast<QgsHeatmapRenderer *>( renderer->clone() );
     392                 :            :   }
     393                 :            :   else
     394                 :            :   {
     395                 :          0 :     return new QgsHeatmapRenderer();
     396                 :            :   }
     397                 :          0 : }
     398                 :            : 
     399                 :          0 : bool QgsHeatmapRenderer::accept( QgsStyleEntityVisitorInterface *visitor ) const
     400                 :            : {
     401                 :          0 :   if ( mGradientRamp )
     402                 :            :   {
     403                 :          0 :     QgsStyleColorRampEntity entity( mGradientRamp );
     404                 :          0 :     if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity ) ) )
     405                 :          0 :       return false;
     406                 :          0 :   }
     407                 :          0 :   return true;
     408                 :          0 : }
     409                 :            : 
     410                 :          0 : void QgsHeatmapRenderer::setColorRamp( QgsColorRamp *ramp )
     411                 :            : {
     412                 :          0 :   delete mGradientRamp;
     413                 :          0 :   mGradientRamp = ramp;
     414                 :          0 : }

Generated by: LCOV version 1.14