QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
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 "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 "qgscolorrampimpl.h"
28#include "qgsrendercontext.h"
29#include "qgspainteffect.h"
32
33#include <QDomDocument>
34#include <QDomElement>
35
37 : QgsFeatureRenderer( QStringLiteral( "heatmapRenderer" ) )
38{
39 mGradientRamp = new QgsGradientColorRamp( QColor( 255, 255, 255 ), QColor( 0, 0, 0 ) );
40}
41
43{
44 delete mGradientRamp;
45}
46
47void QgsHeatmapRenderer::initializeValues( QgsRenderContext &context )
48{
49 mValues.resize( context.painter()->device()->width() * context.painter()->device()->height() / ( mRenderQuality * mRenderQuality ) );
50 mValues.fill( 0 );
51 mCalculatedMaxValue = 0;
52 mFeaturesRendered = 0;
53 mRadiusPixels = std::round( context.convertToPainterUnits( mRadius, mRadiusUnit, mRadiusMapUnitScale ) / mRenderQuality );
54 mRadiusSquared = mRadiusPixels * mRadiusPixels;
55}
56
58{
59 QgsFeatureRenderer::startRender( context, fields );
60
61 if ( !context.painter() )
62 {
63 return;
64 }
65
66 // find out classification attribute index from name
67 mWeightAttrNum = fields.lookupField( mWeightExpressionString );
68 if ( mWeightAttrNum == -1 )
69 {
70 mWeightExpression.reset( new QgsExpression( mWeightExpressionString ) );
71 mWeightExpression->prepare( &context.expressionContext() );
72 }
73
74 initializeValues( context );
75}
76
77QgsMultiPointXY QgsHeatmapRenderer::convertToMultipoint( const QgsGeometry *geom )
78{
79 QgsMultiPointXY multiPoint;
80 if ( !geom->isMultipart() )
81 {
82 multiPoint << geom->asPoint();
83 }
84 else
85 {
86 multiPoint = geom->asMultiPoint();
87 }
88
89 return multiPoint;
90}
91
92bool QgsHeatmapRenderer::renderFeature( const QgsFeature &feature, QgsRenderContext &context, int layer, bool selected, bool drawVertexMarker )
93{
94 Q_UNUSED( layer )
95 Q_UNUSED( selected )
96 Q_UNUSED( drawVertexMarker )
97
98 if ( !context.painter() )
99 {
100 return false;
101 }
102
103 if ( !feature.hasGeometry() || feature.geometry().type() != QgsWkbTypes::PointGeometry )
104 {
105 //can only render point type
106 return false;
107 }
108
109 double weight = 1.0;
110 if ( !mWeightExpressionString.isEmpty() )
111 {
112 QVariant value;
113 if ( mWeightAttrNum == -1 )
114 {
115 Q_ASSERT( mWeightExpression.get() );
116 value = mWeightExpression->evaluate( &context.expressionContext() );
117 }
118 else
119 {
120 const QgsAttributes attrs = feature.attributes();
121 value = attrs.value( mWeightAttrNum );
122 }
123 bool ok = false;
124 const double evalWeight = value.toDouble( &ok );
125 if ( ok )
126 {
127 weight = evalWeight;
128 }
129 }
130
131 const int width = context.painter()->device()->width() / mRenderQuality;
132 const int height = context.painter()->device()->height() / mRenderQuality;
133
134 //transform geometry if required
135 QgsGeometry geom = feature.geometry();
136 const QgsCoordinateTransform xform = context.coordinateTransform();
137 if ( xform.isValid() )
138 {
139 geom.transform( xform );
140 }
141
142 //convert point to multipoint
143 const QgsMultiPointXY multiPoint = convertToMultipoint( &geom );
144
145 //loop through all points in multipoint
146 for ( QgsMultiPointXY::const_iterator pointIt = multiPoint.constBegin(); pointIt != multiPoint.constEnd(); ++pointIt )
147 {
148 const QgsPointXY pixel = context.mapToPixel().transform( *pointIt );
149 const int pointX = pixel.x() / mRenderQuality;
150 const int pointY = pixel.y() / mRenderQuality;
151 for ( int x = std::max( pointX - mRadiusPixels, 0 ); x < std::min( pointX + mRadiusPixels, width ); ++x )
152 {
153 if ( context.renderingStopped() )
154 break;
155
156 for ( int y = std::max( pointY - mRadiusPixels, 0 ); y < std::min( pointY + mRadiusPixels, height ); ++y )
157 {
158 const int index = y * width + x;
159 if ( index >= mValues.count() )
160 {
161 continue;
162 }
163 const double distanceSquared = std::pow( pointX - x, 2.0 ) + std::pow( pointY - y, 2.0 );
164 if ( distanceSquared > mRadiusSquared )
165 {
166 continue;
167 }
168
169 const double score = weight * quarticKernel( std::sqrt( distanceSquared ), mRadiusPixels );
170 const double value = mValues.at( index ) + score;
171 if ( value > mCalculatedMaxValue )
172 {
173 mCalculatedMaxValue = value;
174 }
175 mValues[ index ] = value;
176 }
177 }
178 }
179
180 mFeaturesRendered++;
181#if 0
182 //TODO - enable progressive rendering
183 if ( mFeaturesRendered % 200 == 0 )
184 {
185 renderImage( context );
186 }
187#endif
188 return true;
189}
190
191
192double QgsHeatmapRenderer::uniformKernel( const double distance, const int bandwidth ) const
193{
194 Q_UNUSED( distance )
195 Q_UNUSED( bandwidth )
196 return 1.0;
197}
198
199double QgsHeatmapRenderer::quarticKernel( const double distance, const int bandwidth ) const
200{
201 return std::pow( 1. - std::pow( distance / static_cast< double >( bandwidth ), 2 ), 2 );
202}
203
204double QgsHeatmapRenderer::triweightKernel( const double distance, const int bandwidth ) const
205{
206 return std::pow( 1. - std::pow( distance / static_cast< double >( bandwidth ), 2 ), 3 );
207}
208
209double QgsHeatmapRenderer::epanechnikovKernel( const double distance, const int bandwidth ) const
210{
211 return ( 1. - std::pow( distance / static_cast< double >( bandwidth ), 2 ) );
212}
213
214double QgsHeatmapRenderer::triangularKernel( const double distance, const int bandwidth ) const
215{
216 return ( 1. - ( distance / static_cast< double >( bandwidth ) ) );
217}
218
220{
222
223 renderImage( context );
224 mWeightExpression.reset();
225}
226
227void QgsHeatmapRenderer::renderImage( QgsRenderContext &context )
228{
229 if ( !context.painter() || !mGradientRamp || context.renderingStopped() )
230 {
231 return;
232 }
233
234 QImage image( context.painter()->device()->width() / mRenderQuality,
235 context.painter()->device()->height() / mRenderQuality,
236 QImage::Format_ARGB32 );
237 image.fill( Qt::transparent );
238
239 const double scaleMax = mExplicitMax > 0 ? mExplicitMax : mCalculatedMaxValue;
240
241 int idx = 0;
242 double pixVal = 0;
243 QColor pixColor;
244 for ( int heightIndex = 0; heightIndex < image.height(); ++heightIndex )
245 {
246 if ( context.renderingStopped() )
247 break;
248
249 QRgb *scanLine = reinterpret_cast< QRgb * >( image.scanLine( heightIndex ) );
250 for ( int widthIndex = 0; widthIndex < image.width(); ++widthIndex )
251 {
252 //scale result to fit in the range [0, 1]
253 pixVal = mValues.at( idx ) > 0 ? std::min( ( mValues.at( idx ) / scaleMax ), 1.0 ) : 0;
254
255 //convert value to color from ramp
256 pixColor = mGradientRamp->color( pixVal );
257
258 scanLine[widthIndex] = pixColor.rgba();
259 idx++;
260 }
261 }
262
263 if ( mRenderQuality > 1 )
264 {
265 const QImage resized = image.scaled( context.painter()->device()->width(),
266 context.painter()->device()->height() );
267 context.painter()->drawImage( 0, 0, resized );
268 }
269 else
270 {
271 context.painter()->drawImage( 0, 0, image );
272 }
273}
274
276{
277 return QStringLiteral( "[HEATMAP]" );
278}
279
281{
282 QgsHeatmapRenderer *newRenderer = new QgsHeatmapRenderer();
283 if ( mGradientRamp )
284 {
285 newRenderer->setColorRamp( mGradientRamp->clone() );
286 }
287 newRenderer->setRadius( mRadius );
288 newRenderer->setRadiusUnit( mRadiusUnit );
289 newRenderer->setRadiusMapUnitScale( mRadiusMapUnitScale );
290 newRenderer->setMaximumValue( mExplicitMax );
291 newRenderer->setRenderQuality( mRenderQuality );
292 newRenderer->setWeightExpression( mWeightExpressionString );
293 copyRendererData( newRenderer );
294
295 return newRenderer;
296}
297
299{
300 //we need to expand out the request extent so that it includes points which are up to the heatmap radius outside of the
301 //actual visible extent
302 const double extension = context.convertToMapUnits( mRadius, mRadiusUnit, mRadiusMapUnitScale );
303 extent.setXMinimum( extent.xMinimum() - extension );
304 extent.setXMaximum( extent.xMaximum() + extension );
305 extent.setYMinimum( extent.yMinimum() - extension );
306 extent.setYMaximum( extent.yMaximum() + extension );
307}
308
310{
311 Q_UNUSED( context )
313 r->setRadius( element.attribute( QStringLiteral( "radius" ), QStringLiteral( "50.0" ) ).toFloat() );
314 r->setRadiusUnit( static_cast< QgsUnitTypes::RenderUnit >( element.attribute( QStringLiteral( "radius_unit" ), QStringLiteral( "0" ) ).toInt() ) );
315 r->setRadiusMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( element.attribute( QStringLiteral( "radius_map_unit_scale" ), QString() ) ) );
316 r->setMaximumValue( element.attribute( QStringLiteral( "max_value" ), QStringLiteral( "0.0" ) ).toFloat() );
317 r->setRenderQuality( element.attribute( QStringLiteral( "quality" ), QStringLiteral( "0" ) ).toInt() );
318 r->setWeightExpression( element.attribute( QStringLiteral( "weight_expression" ) ) );
319
320 QDomElement sourceColorRampElem = element.firstChildElement( QStringLiteral( "colorramp" ) );
321 if ( !sourceColorRampElem.isNull() && sourceColorRampElem.attribute( QStringLiteral( "name" ) ) == QLatin1String( "[source]" ) )
322 {
323 r->setColorRamp( QgsSymbolLayerUtils::loadColorRamp( sourceColorRampElem ) );
324 }
325 return r;
326}
327
328QDomElement QgsHeatmapRenderer::save( QDomDocument &doc, const QgsReadWriteContext &context )
329{
330 Q_UNUSED( context )
331 QDomElement rendererElem = doc.createElement( RENDERER_TAG_NAME );
332 rendererElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "heatmapRenderer" ) );
333 rendererElem.setAttribute( QStringLiteral( "radius" ), QString::number( mRadius ) );
334 rendererElem.setAttribute( QStringLiteral( "radius_unit" ), QString::number( mRadiusUnit ) );
335 rendererElem.setAttribute( QStringLiteral( "radius_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mRadiusMapUnitScale ) );
336 rendererElem.setAttribute( QStringLiteral( "max_value" ), QString::number( mExplicitMax ) );
337 rendererElem.setAttribute( QStringLiteral( "quality" ), QString::number( mRenderQuality ) );
338 rendererElem.setAttribute( QStringLiteral( "weight_expression" ), mWeightExpressionString );
339
340 if ( mGradientRamp )
341 {
342 const QDomElement colorRampElem = QgsSymbolLayerUtils::saveColorRamp( QStringLiteral( "[source]" ), mGradientRamp, doc );
343 rendererElem.appendChild( colorRampElem );
344 }
345
346 saveRendererData( doc, rendererElem, context );
347
348 return rendererElem;
349}
350
352{
353 Q_UNUSED( feature )
354 return nullptr;
355}
356
358{
359 return QgsSymbolList();
360}
361
363{
364 QSet<QString> attributes;
365
366 // mAttrName can contain either attribute name or an expression.
367 // Sometimes it is not possible to distinguish between those two,
368 // e.g. "a - b" can be both a valid attribute name or expression.
369 // Since we do not have access to fields here, try both options.
370 attributes << mWeightExpressionString;
371
372 const QgsExpression testExpr( mWeightExpressionString );
373 if ( !testExpr.hasParserError() )
374 attributes.unite( testExpr.referencedColumns() );
375
376 return attributes;
377}
378
380{
381 if ( renderer->type() == QLatin1String( "heatmapRenderer" ) )
382 {
383 return dynamic_cast<QgsHeatmapRenderer *>( renderer->clone() );
384 }
385 else
386 {
387 std::unique_ptr< QgsHeatmapRenderer > res = std::make_unique< QgsHeatmapRenderer >();
388 renderer->copyRendererData( res.get() );
389 return res.release();
390 }
391}
392
394{
395 if ( mGradientRamp )
396 {
397 QgsStyleColorRampEntity entity( mGradientRamp );
398 if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity ) ) )
399 return false;
400 }
401 return true;
402}
403
405{
406 delete mGradientRamp;
407 mGradientRamp = ramp;
408}
A vector of attributes.
Definition: qgsattributes.h:59
Abstract base class for color ramps.
Definition: qgscolorramp.h:30
virtual QColor color(double value) const =0
Returns the color corresponding to a specified value.
virtual QgsColorRamp * clone() const =0
Creates a clone of the color ramp.
Class for doing transforms between two map coordinate systems.
bool isValid() const
Returns true if the coordinate transform is valid, ie both the source and destination CRS have been s...
Class for parsing and evaluation of expressions (formerly called "search strings").
bool hasParserError() const
Returns true if an error occurred when parsing the input expression.
QSet< QString > referencedColumns() const
Gets list of columns referenced by the expression.
virtual void stopRender(QgsRenderContext &context)
Must be called when a render cycle has finished, to allow the renderer to clean up.
QString type() const
Definition: qgsrenderer.h:142
void copyRendererData(QgsFeatureRenderer *destRenderer) const
Clones generic renderer data to another renderer.
Definition: qgsrenderer.cpp:52
void saveRendererData(QDomDocument &doc, QDomElement &element, const QgsReadWriteContext &context)
Saves generic renderer data into the specified element.
virtual void startRender(QgsRenderContext &context, const QgsFields &fields)
Must be called when a new render cycle is started.
Definition: qgsrenderer.cpp:96
virtual QgsFeatureRenderer * clone() const =0
Create a deep copy of this renderer.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
QgsAttributes attributes
Definition: qgsfeature.h:65
QgsGeometry geometry
Definition: qgsfeature.h:67
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Definition: qgsfeature.cpp:233
Container of fields for a vector layer.
Definition: qgsfields.h:45
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
Definition: qgsfields.cpp:349
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:164
Qgis::GeometryOperationResult transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool transformZ=false) SIP_THROW(QgsCsException)
Transforms this geometry as described by the coordinate transform ct.
QgsMultiPointXY asMultiPoint() const
Returns the contents of the geometry as a multi-point.
bool isMultipart() const SIP_HOLDGIL
Returns true if WKB of the geometry is of WKBMulti* type.
QgsPointXY asPoint() const
Returns the contents of the geometry as a 2-dimensional point.
QgsWkbTypes::GeometryType type
Definition: qgsgeometry.h:167
Gradient color ramp, which smoothly interpolates between two colors and also supports optional extra ...
A renderer which draws points as a live heatmap.
void setColorRamp(QgsColorRamp *ramp)
Sets the color ramp to use for shading the heatmap.
QgsSymbol * symbolForFeature(const QgsFeature &feature, QgsRenderContext &context) const override
void modifyRequestExtent(QgsRectangle &extent, QgsRenderContext &context) override
Allows for a renderer to modify the extent of a feature request prior to rendering.
void startRender(QgsRenderContext &context, const QgsFields &fields) override
Must be called when a new render cycle is started.
QSet< QString > usedAttributes(const QgsRenderContext &context) const override
Returns a list of attributes required by this renderer.
void setRadius(const double radius)
Sets the radius for the heatmap.
static QgsFeatureRenderer * create(QDomElement &element, const QgsReadWriteContext &context)
Creates a new heatmap renderer instance from XML.
void setRenderQuality(const int quality)
Sets the render quality used for drawing the heatmap.
bool renderFeature(const QgsFeature &feature, QgsRenderContext &context, int layer=-1, bool selected=false, bool drawVertexMarker=false) override SIP_THROW(QgsCsException)
Render a feature using this renderer in the given context.
static QgsHeatmapRenderer * convertFromRenderer(const QgsFeatureRenderer *renderer)
void setWeightExpression(const QString &expression)
Sets the expression used for weighting points when generating the heatmap.
void setMaximumValue(const double value)
Sets the maximum value used for shading the heatmap.
void stopRender(QgsRenderContext &context) override
Must be called when a render cycle has finished, to allow the renderer to clean up.
QString dump() const override
Returns debug information about this renderer.
QDomElement save(QDomDocument &doc, const QgsReadWriteContext &context) override
Stores renderer properties to an XML element.
void setRadiusUnit(const QgsUnitTypes::RenderUnit unit)
Sets the units used for the heatmap's radius.
QgsHeatmapRenderer * clone() const override
Create a deep copy of this renderer.
bool accept(QgsStyleEntityVisitorInterface *visitor) const override
Accepts the specified symbology visitor, causing it to visit all symbols associated with the renderer...
void setRadiusMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale used for the heatmap's radius.
QgsSymbolList symbols(QgsRenderContext &context) const override
QgsPointXY transform(const QgsPointXY &p) const
Transforms a point p from map (world) coordinates to device coordinates.
Definition: qgsmaptopixel.h:90
A class to represent a 2D point.
Definition: qgspointxy.h:59
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
The class is used as a container of context for various read/write operations on other objects.
A rectangle specified with double values.
Definition: qgsrectangle.h:42
double yMaximum() const SIP_HOLDGIL
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:193
double xMaximum() const SIP_HOLDGIL
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:183
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:188
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:198
void setYMinimum(double y) SIP_HOLDGIL
Set the minimum y value.
Definition: qgsrectangle.h:161
void setXMaximum(double x) SIP_HOLDGIL
Set the maximum x value.
Definition: qgsrectangle.h:156
void setXMinimum(double x) SIP_HOLDGIL
Set the minimum x value.
Definition: qgsrectangle.h:151
void setYMaximum(double y) SIP_HOLDGIL
Set the maximum y value.
Definition: qgsrectangle.h:166
Contains information about the context of a rendering operation.
QPainter * painter()
Returns the destination QPainter for the render operation.
double convertToPainterUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
QgsExpressionContext & expressionContext()
Gets the expression context.
const QgsMapToPixel & mapToPixel() const
Returns the context's map to pixel transform, which transforms between map coordinates and device coo...
double convertToMapUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale()) const
Converts a size from the specified units to map units.
bool renderingStopped() const
Returns true if the rendering operation has been stopped and any ongoing rendering should be canceled...
QgsCoordinateTransform coordinateTransform() const
Returns the current coordinate transform for the context.
A color ramp entity for QgsStyle databases.
Definition: qgsstyle.h:1374
An interface for classes which can visit style entity (e.g.
virtual bool visit(const QgsStyleEntityVisitorInterface::StyleLeaf &entity)
Called when the visitor will visit a style entity.
static QString encodeMapUnitScale(const QgsMapUnitScale &mapUnitScale)
static QgsColorRamp * loadColorRamp(QDomElement &element)
Creates a color ramp from the settings encoded in an XML element.
static QgsMapUnitScale decodeMapUnitScale(const QString &str)
static QDomElement saveColorRamp(const QString &name, QgsColorRamp *ramp, QDomDocument &doc)
Encodes a color ramp's settings to an XML element.
Abstract base class for all rendered symbols.
Definition: qgssymbol.h:93
RenderUnit
Rendering size units.
Definition: qgsunittypes.h:168
QVector< QgsPointXY > QgsMultiPointXY
A collection of QgsPoints that share a common collection of attributes.
Definition: qgsgeometry.h:82
#define RENDERER_TAG_NAME
Definition: qgsrenderer.h:50
QList< QgsSymbol * > QgsSymbolList
Definition: qgsrenderer.h:44
Contains information relating to the style entity currently being visited.