Add support for weighting points by expression to heatmap renderer

This commit is contained in:
Nyall Dawson 2014-11-23 21:47:58 +11:00
parent 50d2a7495b
commit 8039895db2
6 changed files with 199 additions and 79 deletions

View File

@ -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 );
};

View File

@ -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 );
}
@ -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;
@ -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 )
{
@ -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 )
@ -246,6 +277,7 @@ QgsFeatureRendererV2* QgsHeatmapRenderer::clone() const
newRenderer->setRadiusMapUnitScale( mRadiusMapUnitScale );
newRenderer->setMaximumValue( mExplicitMax );
newRenderer->setRenderQuality( mRenderQuality );
newRenderer->setWeightExpression( mWeightExpressionString );
return newRenderer;
}
@ -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]" )
@ -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 );
@ -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 )

View File

@ -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& );
@ -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;

View File

@ -23,6 +23,7 @@
#include "qgsvectorcolorrampv2.h"
#include "qgsvectorgradientcolorrampv2dialog.h"
#include "qgsstylev2.h"
#include "qgsproject.h"
#include <QGridLayout>
#include <QLabel>
@ -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()
@ -206,3 +211,8 @@ void QgsHeatmapRendererWidget::on_mInvertCheckBox_toggled( bool v )
mRenderer->setInvertRamp( v );
}
void QgsHeatmapRendererWidget::weightExpressionChanged( QString expression )
{
mRenderer->setWeightExpression( expression );
}

View File

@ -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 );
};

View File

@ -7,81 +7,27 @@
<x>0</x>
<y>0</y>
<width>532</width>
<height>197</height>
<height>386</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout" columnstretch="0,1">
<item row="0" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0,0">
<item>
<widget class="QgsColorRampComboBox" name="mRampComboBox"/>
</item>
<item>
<widget class="QPushButton" name="mButtonEditRamp">
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Edit</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="mInvertCheckBox">
<property name="text">
<string>Invert</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Radius</string>
<layout class="QGridLayout" name="gridLayout" columnstretch="0,1,0">
<item row="5" column="2">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item row="2" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QgsDoubleSpinBox" name="mRadiusSpinBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="decimals">
<number>6</number>
</property>
<property name="maximum">
<double>99999999.000000000000000</double>
</property>
<property name="singleStep">
<double>0.200000000000000</double>
</property>
</widget>
</item>
<item>
<widget class="QgsUnitSelectionWidget" name="mRadiusUnitWidget" native="true"/>
</item>
</layout>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Maximum value</string>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</widget>
</spacer>
</item>
<item row="3" column="1">
<item row="2" column="1" colspan="2">
<widget class="QgsDoubleSpinBox" name="mMaxSpinBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
@ -103,6 +49,13 @@
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Radius</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
@ -110,7 +63,7 @@
</property>
</widget>
</item>
<item row="4" column="1">
<item row="4" column="1" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_4">
@ -159,18 +112,94 @@
</item>
</layout>
</item>
<item row="5" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
<item row="0" column="0" colspan="3">
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0,0">
<item>
<widget class="QgsColorRampComboBox" name="mRampComboBox"/>
</item>
<item>
<widget class="QPushButton" name="mButtonEditRamp">
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Edit</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="mInvertCheckBox">
<property name="text">
<string>Invert</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Maximum value</string>
</property>
<property name="sizeHint" stdset="0">
</widget>
</item>
<item row="3" column="1" colspan="2">
<widget class="QgsFieldExpressionWidget" name="mWeightExpressionWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>20</width>
<height>40</height>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
</spacer>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="filters">
<set>QgsFieldProxyModel::Numeric</set>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Weight points by</string>
</property>
</widget>
</item>
<item row="1" column="1" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QgsDoubleSpinBox" name="mRadiusSpinBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="decimals">
<number>6</number>
</property>
<property name="maximum">
<double>99999999.000000000000000</double>
</property>
<property name="singleStep">
<double>0.200000000000000</double>
</property>
</widget>
</item>
<item>
<widget class="QgsUnitSelectionWidget" name="mRadiusUnitWidget" native="true"/>
</item>
</layout>
</item>
</layout>
</widget>
@ -180,6 +209,12 @@
<extends>QDoubleSpinBox</extends>
<header>qgsdoublespinbox.h</header>
</customwidget>
<customwidget>
<class>QgsFieldExpressionWidget</class>
<extends>QWidget</extends>
<header>qgsfieldexpressionwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QgsUnitSelectionWidget</class>
<extends>QWidget</extends>
@ -198,6 +233,7 @@
<tabstop>mInvertCheckBox</tabstop>
<tabstop>mRadiusSpinBox</tabstop>
<tabstop>mMaxSpinBox</tabstop>
<tabstop>mWeightExpressionWidget</tabstop>
<tabstop>mQualitySlider</tabstop>
</tabstops>
<resources/>