QGIS API Documentation  2.10.1-Pisa
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgsheatmaprenderer.cpp
Go to the documentation of this file.
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 "qgssymbolv2.h"
19 #include "qgssymbollayerv2utils.h"
20 
21 #include "qgslogger.h"
22 #include "qgsfeature.h"
23 #include "qgsvectorlayer.h"
24 #include "qgssymbollayerv2.h"
25 #include "qgsogcutils.h"
26 #include "qgsvectorcolorrampv2.h"
27 #include "qgsrendercontext.h"
28 #include "qgspainteffect.h"
29 
30 #include <QDomDocument>
31 #include <QDomElement>
32 
34  : QgsFeatureRendererV2( "heatmapRenderer" )
35  , mCalculatedMaxValue( 0 )
36  , mRadius( 10 )
37  , mRadiusPixels( 0 )
38  , mRadiusSquared( 0 )
39  , mRadiusUnit( QgsSymbolV2::MM )
40  , mWeightAttrNum( -1 )
41  , mGradientRamp( 0 )
42  , mInvertRamp( false )
43  , mExplicitMax( 0.0 )
44  , mRenderQuality( 3 )
45  , mFeaturesRendered( 0 )
46 {
47  mGradientRamp = new QgsVectorGradientColorRampV2( QColor( 255, 255, 255 ), QColor( 0, 0, 0 ) );
48 
49 }
50 
52 {
53  delete mGradientRamp;
54 }
55 
56 void QgsHeatmapRenderer::initializeValues( QgsRenderContext& context )
57 {
58  mValues.resize( context.painter()->device()->width() * context.painter()->device()->height() / ( mRenderQuality * mRenderQuality ) );
59  mValues.fill( 0 );
60  mCalculatedMaxValue = 0;
61  mFeaturesRendered = 0;
62  mRadiusPixels = qRound( mRadius * QgsSymbolLayerV2Utils::pixelSizeScaleFactor( context, mRadiusUnit, mRadiusMapUnitScale ) / mRenderQuality );
63  mRadiusSquared = mRadiusPixels * mRadiusPixels;
64 }
65 
67 {
68  Q_UNUSED( fields );
69  if ( !context.painter() )
70  {
71  return;
72  }
73 
74  // find out classification attribute index from name
75  mWeightAttrNum = fields.fieldNameIndex( mWeightExpressionString );
76  if ( mWeightAttrNum == -1 )
77  {
78  mWeightExpression.reset( new QgsExpression( mWeightExpressionString ) );
79  mWeightExpression->prepare( fields );
80  }
81 
82  initializeValues( context );
83 }
84 
85 QgsMultiPoint QgsHeatmapRenderer::convertToMultipoint( const QgsGeometry* geom )
86 {
87  QgsMultiPoint multiPoint;
88  if ( !geom->isMultipart() )
89  {
90  multiPoint << geom->asPoint();
91  }
92  else
93  {
94  multiPoint = geom->asMultiPoint();
95  }
96 
97  return multiPoint;
98 }
99 
100 bool QgsHeatmapRenderer::renderFeature( QgsFeature& feature, QgsRenderContext& context, int layer, bool selected, bool drawVertexMarker )
101 {
102  Q_UNUSED( layer );
103  Q_UNUSED( selected );
104  Q_UNUSED( drawVertexMarker );
105 
106  if ( !context.painter() )
107  {
108  return false;
109  }
110 
111  if ( !feature.constGeometry() || feature.constGeometry()->type() != QGis::Point )
112  {
113  //can only render point type
114  return false;
115  }
116 
117  double weight = 1.0;
118  if ( !mWeightExpressionString.isEmpty() )
119  {
120  QVariant value;
121  if ( mWeightAttrNum == -1 )
122  {
123  Q_ASSERT( mWeightExpression.data() );
124  value = mWeightExpression->evaluate( &feature );
125  }
126  else
127  {
128  QgsAttributes attrs = feature.attributes();
129  value = attrs.value( mWeightAttrNum );
130  }
131  bool ok = false;
132  double evalWeight = value.toDouble( &ok );
133  if ( ok )
134  {
135  weight = evalWeight;
136  }
137  }
138 
139  int width = context.painter()->device()->width() / mRenderQuality;
140  int height = context.painter()->device()->height() / mRenderQuality;
141 
142  //transform geometry if required
143  QgsGeometry* transformedGeom = 0;
144  const QgsCoordinateTransform* xform = context.coordinateTransform();
145  if ( xform )
146  {
147  transformedGeom = new QgsGeometry( *feature.constGeometry() );
148  transformedGeom->transform( *xform );
149  }
150 
151  //convert point to multipoint
152  QgsMultiPoint multiPoint = convertToMultipoint( transformedGeom ? transformedGeom : feature.constGeometry() );
153 
154  delete transformedGeom;
155  transformedGeom = 0;
156 
157  //loop through all points in multipoint
158  for ( QgsMultiPoint::const_iterator pointIt = multiPoint.constBegin(); pointIt != multiPoint.constEnd(); ++pointIt )
159  {
160  QgsPoint pixel = context.mapToPixel().transform( *pointIt );
161  int pointX = pixel.x() / mRenderQuality;
162  int pointY = pixel.y() / mRenderQuality;
163  for ( int x = qMax( pointX - mRadiusPixels, 0 ); x < qMin( pointX + mRadiusPixels, width ); ++x )
164  {
165  for ( int y = qMax( pointY - mRadiusPixels, 0 ); y < qMin( pointY + mRadiusPixels, height ); ++y )
166  {
167  int index = y * width + x;
168  if ( index >= mValues.count( ) )
169  {
170  continue;
171  }
172  double distanceSquared = pow( pointX - x, 2.0 ) + pow( pointY - y, 2.0 );
173  if ( distanceSquared > mRadiusSquared )
174  {
175  continue;
176  }
177 
178  double score = weight * quarticKernel( sqrt( distanceSquared ), mRadiusPixels );
179  double value = mValues[ index ] + score;
180  if ( value > mCalculatedMaxValue )
181  {
182  mCalculatedMaxValue = value;
183  }
184  mValues[ index ] = value;
185  }
186  }
187  }
188 
189  mFeaturesRendered++;
190 #if 0
191  //TODO - enable progressive rendering
192  if ( mFeaturesRendered % 200 == 0 )
193  {
194  renderImage( context );
195  }
196 #endif
197  return true;
198 }
199 
200 
201 double QgsHeatmapRenderer::uniformKernel( const double distance, const int bandwidth ) const
202 {
203  Q_UNUSED( distance );
204  Q_UNUSED( bandwidth );
205  return 1.0;
206 }
207 
208 double QgsHeatmapRenderer::quarticKernel( const double distance, const int bandwidth ) const
209 {
210  return pow( 1. - pow( distance / ( double )bandwidth, 2 ), 2 );
211 }
212 
213 double QgsHeatmapRenderer::triweightKernel( const double distance, const int bandwidth ) const
214 {
215  return pow( 1. - pow( distance / ( double )bandwidth, 2 ), 3 );
216 }
217 
218 double QgsHeatmapRenderer::epanechnikovKernel( const double distance, const int bandwidth ) const
219 {
220  return ( 1. - pow( distance / ( double )bandwidth, 2 ) );
221 }
222 
223 double QgsHeatmapRenderer::triangularKernel( const double distance, const int bandwidth ) const
224 {
225  return ( 1. - ( distance / ( double )bandwidth ) );
226 }
227 
229 {
230  renderImage( context );
231  mWeightExpression.reset();
232 }
233 
234 void QgsHeatmapRenderer::renderImage( QgsRenderContext& context )
235 {
236  if ( !context.painter() || !mGradientRamp )
237  {
238  return;
239  }
240 
241  QImage image( context.painter()->device()->width() / mRenderQuality,
242  context.painter()->device()->height() / mRenderQuality,
243  QImage::Format_ARGB32 );
244  image.fill( Qt::transparent );
245 
246  double scaleMax = mExplicitMax > 0 ? mExplicitMax : mCalculatedMaxValue;
247 
248  int idx = 0;
249  double pixVal = 0;
250  QColor pixColor;
251  for ( int heightIndex = 0; heightIndex < image.height(); ++heightIndex )
252  {
253  QRgb* scanLine = ( QRgb* )image.scanLine( heightIndex );
254  for ( int widthIndex = 0; widthIndex < image.width(); ++widthIndex )
255  {
256  //scale result to fit in the range [0, 1]
257  pixVal = mValues.at( idx ) > 0 ? qMin(( mValues.at( idx ) / scaleMax ), 1.0 ) : 0;
258 
259  //convert value to color from ramp
260  pixColor = mGradientRamp->color( mInvertRamp ? 1 - pixVal : pixVal );
261 
262  scanLine[widthIndex] = pixColor.rgba();
263  idx++;
264  }
265  }
266 
267  if ( mRenderQuality > 1 )
268  {
269  QImage resized = image.scaled( context.painter()->device()->width(),
270  context.painter()->device()->height() );
271  context.painter()->drawImage( 0, 0, resized );
272  }
273  else
274  {
275  context.painter()->drawImage( 0, 0, image );
276  }
277 }
278 
280 {
281  return "[HEATMAP]";
282 }
283 
285 {
286  QgsHeatmapRenderer* newRenderer = new QgsHeatmapRenderer();
287  if ( mGradientRamp )
288  {
289  newRenderer->setColorRamp( mGradientRamp->clone() );
290  }
291  newRenderer->setInvertRamp( mInvertRamp );
292  newRenderer->setRadius( mRadius );
293  newRenderer->setRadiusUnit( mRadiusUnit );
294  newRenderer->setRadiusMapUnitScale( mRadiusMapUnitScale );
295  newRenderer->setMaximumValue( mExplicitMax );
296  newRenderer->setRenderQuality( mRenderQuality );
297  newRenderer->setWeightExpression( mWeightExpressionString );
298  copyPaintEffect( newRenderer );
299 
300  return newRenderer;
301 }
302 
304 {
305  //we need to expand out the request extent so that it includes points which are up to the heatmap radius outside of the
306  //actual visible extent
307  double extension = 0.0;
308  if ( mRadiusUnit == QgsSymbolV2::Pixel )
309  {
311  }
312  else if ( mRadiusUnit == QgsSymbolV2::MM )
313  {
314  double pixelSize = mRadius * QgsSymbolLayerV2Utils::pixelSizeScaleFactor( context, QgsSymbolV2::MM, QgsMapUnitScale() );
316  }
317  else
318  {
319  extension = mRadius;
320  }
321  extent.setXMinimum( extent.xMinimum() - extension );
322  extent.setXMaximum( extent.xMaximum() + extension );
323  extent.setYMinimum( extent.yMinimum() - extension );
324  extent.setYMaximum( extent.yMaximum() + extension );
325 }
326 
328 {
330  r->setRadius( element.attribute( "radius", "50.0" ).toFloat() );
331  r->setRadiusUnit(( QgsSymbolV2::OutputUnit )element.attribute( "radius_unit", "0" ).toInt() );
332  r->setRadiusMapUnitScale( QgsSymbolLayerV2Utils::decodeMapUnitScale( element.attribute( "radius_map_unit_scale", QString() ) ) );
333  r->setMaximumValue( element.attribute( "max_value", "0.0" ).toFloat() );
334  r->setRenderQuality( element.attribute( "quality", "0" ).toInt() );
335  r->setWeightExpression( element.attribute( "weight_expression" ) );
336 
337  QDomElement sourceColorRampElem = element.firstChildElement( "colorramp" );
338  if ( !sourceColorRampElem.isNull() && sourceColorRampElem.attribute( "name" ) == "[source]" )
339  {
340  r->setColorRamp( QgsSymbolLayerV2Utils::loadColorRamp( sourceColorRampElem ) );
341  }
342  r->setInvertRamp( element.attribute( "invert_ramp", "0" ).toInt() );
343  return r;
344 }
345 
347 {
348  QDomElement rendererElem = doc.createElement( RENDERER_TAG_NAME );
349  rendererElem.setAttribute( "type", "heatmapRenderer" );
350  rendererElem.setAttribute( "radius", QString::number( mRadius ) );
351  rendererElem.setAttribute( "radius_unit", QString::number( mRadiusUnit ) );
352  rendererElem.setAttribute( "radius_map_unit_scale", QgsSymbolLayerV2Utils::encodeMapUnitScale( mRadiusMapUnitScale ) );
353  rendererElem.setAttribute( "max_value", QString::number( mExplicitMax ) );
354  rendererElem.setAttribute( "quality", QString::number( mRenderQuality ) );
355  rendererElem.setAttribute( "weight_expression", mWeightExpressionString );
356  if ( mGradientRamp )
357  {
358  QDomElement colorRampElem = QgsSymbolLayerV2Utils::saveColorRamp( "[source]", mGradientRamp, doc );
359  rendererElem.appendChild( colorRampElem );
360  }
361  rendererElem.setAttribute( "invert_ramp", QString::number( mInvertRamp ) );
362 
363  if ( mPaintEffect )
364  mPaintEffect->saveProperties( doc, rendererElem );
365 
366  return rendererElem;
367 }
368 
370 {
371  Q_UNUSED( feature );
372  return 0;
373 }
374 
376 {
377  return QgsSymbolV2List();
378 }
379 
381 {
382  QSet<QString> attributes;
383 
384  // mAttrName can contain either attribute name or an expression.
385  // Sometimes it is not possible to distinguish between those two,
386  // e.g. "a - b" can be both a valid attribute name or expression.
387  // Since we do not have access to fields here, try both options.
388  attributes << mWeightExpressionString;
389 
390  QgsExpression testExpr( mWeightExpressionString );
391  if ( !testExpr.hasParserError() )
392  attributes.unite( testExpr.referencedColumns().toSet() );
393 
394  return attributes.toList();
395 }
396 
398 {
399  if ( renderer->type() == "heatmapRenderer" )
400  {
401  return dynamic_cast<QgsHeatmapRenderer*>( renderer->clone() );
402  }
403  else
404  {
405  return new QgsHeatmapRenderer();
406  }
407 }
408 
410 {
411  delete mGradientRamp;
412  mGradientRamp = ramp;
413 }
Class for parsing and evaluation of expressions (formerly called "search strings").
Definition: qgsexpression.h:86
void setInvertRamp(const bool invert)
Sets whether the ramp is inverted.
#define RENDERER_TAG_NAME
Definition: qgsrendererv2.h:48
static unsigned index
A rectangle specified with double values.
Definition: qgsrectangle.h:35
bool hasParserError() const
Returns true if an error occurred when parsing the input expression.
Definition: qgsexpression.h:93
QStringList referencedColumns() const
Get list of columns referenced by the expression.
virtual bool renderFeature(QgsFeature &feature, QgsRenderContext &context, int layer=-1, bool selected=false, bool drawVertexMarker=false) override
static QgsVectorColorRampV2 * loadColorRamp(QDomElement &element)
virtual QString dump() const override
for debugging
static QgsFeatureRendererV2 * create(QDomElement &element)
virtual QList< QString > usedAttributes() override
QList< QgsSymbolV2 * > QgsSymbolV2List
Definition: qgsrendererv2.h:39
QDomNode appendChild(const QDomNode &newChild)
void setXMaximum(double x)
Set the maximum x value.
Definition: qgsrectangle.h:167
virtual void modifyRequestExtent(QgsRectangle &extent, QgsRenderContext &context) override
Allows for a renderer to modify the extent of a feature request prior to rendering.
QString attribute(const QString &name, const QString &defValue) const
double yMaximum() const
Get the y maximum value (top side of rectangle)
Definition: qgsrectangle.h:192
int fieldNameIndex(const QString &fieldName) const
Look up field's index from name - case insensitive TODO: sort out case sensitive (indexFromName()) vs...
Definition: qgsfield.cpp:354
QVector< T > & fill(const T &value, int size)
const_iterator constEnd() const
QgsPoint transform(const QgsPoint &p) const
Transform the point from map (world) coordinates to device coordinates.
QGis::GeometryType type() const
Returns type of the geometry as a QGis::GeometryType.
Container of fields for a vector layer.
Definition: qgsfield.h:173
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:75
void setWeightExpression(const QString &expression)
Sets the expression used for weighting points when generating the heatmap.
QSet< T > toSet() const
static QString encodeMapUnitScale(const QgsMapUnitScale &mapUnitScale)
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:162
const QgsCoordinateTransform * coordinateTransform() const
QgsPaintEffect * mPaintEffect
double x() const
Definition: qgspoint.h:126
void setRadiusUnit(const QgsSymbolV2::OutputUnit unit)
Sets the units used for the heatmap's radius.
static double pixelSizeScaleFactor(const QgsRenderContext &c, QgsSymbolV2::OutputUnit u, const QgsMapUnitScale &scale=QgsMapUnitScale())
Returns scale factor painter units -> pixel dimensions.
void reset(T *other)
T value(int i) const
int width() const
QString type() const
Definition: qgsrendererv2.h:82
virtual QgsSymbolV2List symbols() override
for symbol levels
virtual QgsFeatureRendererV2 * clone() const =0
QString number(int n, int base)
static QDomElement saveColorRamp(QString name, QgsVectorColorRampV2 *ramp, QDomDocument &doc)
void resize(int size)
double yMinimum() const
Get the y minimum value (bottom side of rectangle)
Definition: qgsrectangle.h:197
void fill(uint pixelValue)
double xMaximum() const
Get the x maximum value (right side of rectangle)
Definition: qgsrectangle.h:182
A renderer which draws points as a live heatmap.
QgsAttributes attributes() const
Returns the feature's attributes.
Definition: qgsfeature.cpp:90
void setRadiusMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale used for the heatmap's radius.
void setAttribute(const QString &name, const QString &value)
virtual QgsVectorColorRampV2 * clone() const =0
int toInt(bool *ok, int base) const
void setYMinimum(double y)
Set the minimum y value.
Definition: qgsrectangle.h:172
bool isEmpty() const
QPaintDevice * device() const
virtual QDomElement save(QDomDocument &doc) override
store renderer info to XML element
virtual void startRender(QgsRenderContext &context, const QgsFields &fields) override
virtual QgsFeatureRendererV2 * clone() const override
A class to represent a point.
Definition: qgspoint.h:63
void setRenderQuality(const int quality)
Sets the render quality used for drawing the heatmap.
virtual QColor color(double value) const =0
T * data() const
virtual QgsSymbolV2 * symbolForFeature(QgsFeature &feature) override
to be overridden
void setRadius(const double radius)
Sets the radius for the heatmap.
bool isNull() const
const T & at(int i) const
virtual bool saveProperties(QDomDocument &doc, QDomElement &element) const
Saves the current state of the effect to a DOM element.
const_iterator constBegin() const
void setColorRamp(QgsVectorColorRampV2 *ramp)
Sets the color ramp to use for shading the heatmap.
Contains information about the context of a rendering operation.
void drawImage(const QRectF &target, const QImage &image, const QRectF &source, QFlags< Qt::ImageConversionFlag > flags)
QPainter * painter()
void copyPaintEffect(QgsFeatureRendererV2 *destRenderer) const
Copies paint effect of this renderer to another renderer.
QSet< T > & unite(const QSet< T > &other)
static QgsHeatmapRenderer * convertFromRenderer(const QgsFeatureRendererV2 *renderer)
void setYMaximum(double y)
Set the maximum y value.
Definition: qgsrectangle.h:177
float toFloat(bool *ok) const
QgsMultiPoint asMultiPoint() const
Return contents of the geometry as a multi point if wkbType is WKBMultiPoint, otherwise an empty list...
bool isMultipart() const
Returns true if WKB of the geometry is of WKBMulti* type.
QDomElement firstChildElement(const QString &tagName) const
int count(const T &value) const
virtual void stopRender(QgsRenderContext &context) override
const QgsGeometry * constGeometry() const
Gets a const pointer to the geometry object associated with this feature.
Definition: qgsfeature.cpp:68
Class for doing transforms between two map coordinate systems.
int transform(const QgsCoordinateTransform &ct)
Transform this geometry as described by CoordinateTransform ct.
const QgsMapToPixel & mapToPixel() const
QList< T > toList() const
double y() const
Definition: qgspoint.h:134
double toDouble(bool *ok) const
typedef const_iterator
static QgsMapUnitScale decodeMapUnitScale(const QString &str)
int height() const
QDomElement createElement(const QString &tagName)
QgsPoint asPoint() const
Return contents of the geometry as a point if wkbType is WKBPoint, otherwise returns [0...
A vector of attributes.
Definition: qgsfeature.h:109
double xMinimum() const
Get the x minimum value (left side of rectangle)
Definition: qgsrectangle.h:187
void setMaximumValue(const double value)
Sets the maximum value used for shading the heatmap.
QImage scaled(int width, int height, Qt::AspectRatioMode aspectRatioMode, Qt::TransformationMode transformMode) const
void setXMinimum(double x)
Set the minimum x value.
Definition: qgsrectangle.h:162
QRgb rgba() const