Skip to content

Commit

Permalink
Add support for weighting points by expression to heatmap renderer
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Nov 23, 2014
1 parent 50d2a74 commit 8039895
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 79 deletions.
12 changes: 12 additions & 0 deletions python/core/symbology-ng/qgsheatmaprenderer.sip
Expand Up @@ -121,5 +121,17 @@ class QgsHeatmapRenderer : QgsFeatureRendererV2
* @see renderQuality
*/
void setRenderQuality( const int quality );

/**Returns the expression used for weighting points when generating the heatmap.
* @returns point weight expression. If empty, all points are equally weighted.
* @see setWeightExpression
*/
QString weightExpression() const;

/**Sets the expression used for weighting points when generating the heatmap.
* @param expression point weight expression. If set to empty, all points are equally weighted.
* @see weightExpression
*/
void setWeightExpression( const QString& expression );

};
51 changes: 48 additions & 3 deletions src/core/symbology-ng/qgsheatmaprenderer.cpp
Expand Up @@ -65,6 +65,14 @@ void QgsHeatmapRenderer::startRender( QgsRenderContext& context, const QgsFields
return;
}

// find out classification attribute index from name
mWeightAttrNum = fields.fieldNameIndex( mWeightExpressionString );
if ( mWeightAttrNum == -1 )
{
mWeightExpression.reset( new QgsExpression( mWeightExpressionString ) );
mWeightExpression->prepare( fields );
}

initializeValues( context );
}

Expand Down Expand Up @@ -101,6 +109,28 @@ bool QgsHeatmapRenderer::renderFeature( QgsFeature& feature, QgsRenderContext& c
return false;
}

double weight = 1.0;
if ( !mWeightExpressionString.isEmpty() )
{
QVariant value;
if ( mWeightAttrNum == -1 )
{
Q_ASSERT( mWeightExpression.data() );
value = mWeightExpression->evaluate( &feature );
}
else
{
const QgsAttributes& attrs = feature.attributes();
value = attrs.value( mWeightAttrNum );
}
bool ok = false;
double evalWeight = value.toDouble( &ok );
if ( ok )
{
weight = evalWeight;
}
}

int width = context.painter()->device()->width() / mRenderQuality;
int height = context.painter()->device()->height() / mRenderQuality;

Expand Down Expand Up @@ -128,7 +158,7 @@ bool QgsHeatmapRenderer::renderFeature( QgsFeature& feature, QgsRenderContext& c
continue;
}

double score = quarticKernel( sqrt( distanceSquared ), mRadiusPixels );
double score = weight * quarticKernel( sqrt( distanceSquared ), mRadiusPixels );
double value = mValues[ index ] + score;
if ( value > mCalculatedMaxValue )
{
Expand Down Expand Up @@ -181,6 +211,7 @@ double QgsHeatmapRenderer::triangularKernel( const double distance, const int ba
void QgsHeatmapRenderer::stopRender( QgsRenderContext& context )
{
renderImage( context );
mWeightExpression.reset();
}

void QgsHeatmapRenderer::renderImage( QgsRenderContext& context )
Expand Down Expand Up @@ -246,6 +277,7 @@ QgsFeatureRendererV2* QgsHeatmapRenderer::clone() const
newRenderer->setRadiusMapUnitScale( mRadiusMapUnitScale );
newRenderer->setMaximumValue( mExplicitMax );
newRenderer->setRenderQuality( mRenderQuality );
newRenderer->setWeightExpression( mWeightExpressionString );

return newRenderer;
}
Expand Down Expand Up @@ -282,6 +314,7 @@ QgsFeatureRendererV2* QgsHeatmapRenderer::create( QDomElement& element )
r->setRadiusMapUnitScale( QgsSymbolLayerV2Utils::decodeMapUnitScale( element.attribute( "radius_map_unit_scale", QString() ) ) );
r->setMaximumValue( element.attribute( "max_value", "0.0" ).toFloat() );
r->setRenderQuality( element.attribute( "quality", "0" ).toInt() );
r->setWeightExpression( element.attribute( "weight_expression" ) );

QDomElement sourceColorRampElem = element.firstChildElement( "colorramp" );
if ( !sourceColorRampElem.isNull() && sourceColorRampElem.attribute( "name" ) == "[source]" )
Expand All @@ -301,6 +334,7 @@ QDomElement QgsHeatmapRenderer::save( QDomDocument& doc )
rendererElem.setAttribute( "radius_map_unit_scale", QgsSymbolLayerV2Utils::encodeMapUnitScale( mRadiusMapUnitScale ) );
rendererElem.setAttribute( "max_value", QString::number( mExplicitMax ) );
rendererElem.setAttribute( "quality", QString::number( mRenderQuality ) );
rendererElem.setAttribute( "weight_expression", mWeightExpressionString );
if ( mGradientRamp )
{
QDomElement colorRampElem = QgsSymbolLayerV2Utils::saveColorRamp( "[source]", mGradientRamp, doc );
Expand All @@ -324,8 +358,19 @@ QgsSymbolV2List QgsHeatmapRenderer::symbols()

QList<QString> QgsHeatmapRenderer::usedAttributes()
{
QList<QString> attributeList;
return attributeList;
QSet<QString> attributes;

// mAttrName can contain either attribute name or an expression.
// Sometimes it is not possible to distinguish between those two,
// e.g. "a - b" can be both a valid attribute name or expression.
// Since we do not have access to fields here, try both options.
attributes << mWeightExpressionString;

QgsExpression testExpr( mWeightExpressionString );
if ( !testExpr.hasParserError() )
attributes.unite( testExpr.referencedColumns().toSet() );

return attributes.toList();
}

QgsHeatmapRenderer* QgsHeatmapRenderer::convertFromRenderer( const QgsFeatureRendererV2 *renderer )
Expand Down
16 changes: 16 additions & 0 deletions src/core/symbology-ng/qgsheatmaprenderer.h
Expand Up @@ -151,6 +151,18 @@ class CORE_EXPORT QgsHeatmapRenderer : public QgsFeatureRendererV2
*/
void setRenderQuality( const int quality ) { mRenderQuality = quality; }

/**Returns the expression used for weighting points when generating the heatmap.
* @returns point weight expression. If empty, all points are equally weighted.
* @see setWeightExpression
*/
QString weightExpression() const { return mWeightExpressionString; }

/**Sets the expression used for weighting points when generating the heatmap.
* @param expression point weight expression. If set to empty, all points are equally weighted.
* @see weightExpression
*/
void setWeightExpression( const QString& expression ) { mWeightExpressionString = expression; }

private:
/** Private copy constructor. @see clone() */
QgsHeatmapRenderer( const QgsHeatmapRenderer& );
Expand All @@ -167,6 +179,10 @@ class CORE_EXPORT QgsHeatmapRenderer : public QgsFeatureRendererV2
QgsSymbolV2::OutputUnit mRadiusUnit;
QgsMapUnitScale mRadiusMapUnitScale;

QString mWeightExpressionString;
int mWeightAttrNum;
QScopedPointer<QgsExpression> mWeightExpression;

QgsVectorColorRampV2* mGradientRamp;
bool mInvertRamp;

Expand Down
10 changes: 10 additions & 0 deletions src/gui/symbology-ng/qgsheatmaprendererwidget.cpp
Expand Up @@ -23,6 +23,7 @@
#include "qgsvectorcolorrampv2.h"
#include "qgsvectorgradientcolorrampv2dialog.h"
#include "qgsstylev2.h"
#include "qgsproject.h"
#include <QGridLayout>
#include <QLabel>

Expand Down Expand Up @@ -100,6 +101,10 @@ QgsHeatmapRendererWidget::QgsHeatmapRendererWidget( QgsVectorLayer* layer, QgsSt
mInvertCheckBox->blockSignals( true );
mInvertCheckBox->setChecked( mRenderer->invertRamp() );
mInvertCheckBox->blockSignals( false );

mWeightExpressionWidget->setLayer( layer );
mWeightExpressionWidget->setField( mRenderer->weightExpression() );
connect( mWeightExpressionWidget, SIGNAL( fieldChanged( QString ) ), this, SLOT( weightExpressionChanged( QString ) ) );
}

QgsFeatureRendererV2* QgsHeatmapRendererWidget::renderer()
Expand Down Expand Up @@ -206,3 +211,8 @@ void QgsHeatmapRendererWidget::on_mInvertCheckBox_toggled( bool v )

mRenderer->setInvertRamp( v );
}

void QgsHeatmapRendererWidget::weightExpressionChanged( QString expression )
{
mRenderer->setWeightExpression( expression );
}
1 change: 1 addition & 0 deletions src/gui/symbology-ng/qgsheatmaprendererwidget.h
Expand Up @@ -55,6 +55,7 @@ class GUI_EXPORT QgsHeatmapRendererWidget : public QgsRendererV2Widget, private
void on_mMaxSpinBox_valueChanged( double d );
void on_mQualitySlider_valueChanged( int v );
void on_mInvertCheckBox_toggled( bool v );
void weightExpressionChanged( QString expression );

};

Expand Down

0 comments on commit 8039895

Please sign in to comment.