Skip to content

Commit e1a588e

Browse files
authored
Merge pull request #3782 from nyalldawson/heatmap
Port heatmap plugin code to analysis, add processing alg
2 parents c558d51 + 034cc65 commit e1a588e

26 files changed

+1239
-2111
lines changed

debian/qgis.install

-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ usr/lib/qgis/plugins/librasterterrainplugin.so
88
usr/lib/qgis/plugins/libspatialqueryplugin.so
99
usr/lib/qgis/plugins/libofflineeditingplugin.so
1010
usr/lib/qgis/plugins/libroadgraphplugin.so
11-
usr/lib/qgis/plugins/libheatmapplugin.so
1211
usr/lib/qgis/plugins/libtopolplugin.so
1312
usr/lib/qgis/plugins/libgeometrycheckerplugin.so
1413
usr/lib/qgis/qgis_help
File renamed without changes.

ms-windows/osgeo4w/package.cmd

-1
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,6 @@ tar -C %OSGEO4W_ROOT% -cjf %ARCH%/release/qgis/%PACKAGENAME%/%PACKAGENAME%-%VERS
385385
"apps/%PACKAGENAME%/plugins/evis.dll" ^
386386
"apps/%PACKAGENAME%/plugins/georefplugin.dll" ^
387387
"apps/%PACKAGENAME%/plugins/gpsimporterplugin.dll" ^
388-
"apps/%PACKAGENAME%/plugins/heatmapplugin.dll" ^
389388
"apps/%PACKAGENAME%/plugins/interpolationplugin.dll" ^
390389
"apps/%PACKAGENAME%/plugins/offlineeditingplugin.dll" ^
391390
"apps/%PACKAGENAME%/plugins/oracleplugin.dll" ^

ms-windows/plugins.nsh

-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ WriteRegStr HKEY_CURRENT_USER "Software\QGIS\QGIS3\Plugins" "georefplugin" "true
1414
WriteRegStr HKEY_CURRENT_USER "Software\QGIS\QGIS3\Plugins" "globeplugin" "false"
1515
WriteRegStr HKEY_CURRENT_USER "Software\QGIS\QGIS3\Plugins" "gpsimporterplugin" "true"
1616
WriteRegStr HKEY_CURRENT_USER "Software\QGIS\QGIS3\Plugins" "grassplugin" "true"
17-
WriteRegStr HKEY_CURRENT_USER "Software\QGIS\QGIS3\Plugins" "heatmapplugin" "true"
1817
WriteRegStr HKEY_CURRENT_USER "Software\QGIS\QGIS3\Plugins" "interpolationplugin" "true"
1918
WriteRegStr HKEY_CURRENT_USER "Software\QGIS\QGIS3\Plugins" "offlineeditingplugin" "true"
2019
WriteRegStr HKEY_CURRENT_USER "Software\QGIS\QGIS3\Plugins" "oracleplugin" "true"

python/analysis/analysis.sip

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
%Include raster/qgsderivativefilter.sip
4545
%Include raster/qgsaspectfilter.sip
4646
%Include raster/qgshillshadefilter.sip
47+
%Include raster/qgskde.sip
4748
%Include raster/qgsninecellfilter.sip
4849
%Include raster/qgsrastercalcnode.sip
4950
%Include raster/qgsrastercalculator.sip

python/analysis/raster/qgskde.sip

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/**
2+
* \class QgsKernelDensityEstimation
3+
* \ingroup analysis
4+
* Performs Kernel Density Estimation ("heatmap") calculations on a vector layer.
5+
* @note added in QGIS 3.0
6+
*/
7+
class QgsKernelDensityEstimation
8+
{
9+
%TypeHeaderCode
10+
#include <qgskde.h>
11+
%End
12+
13+
public:
14+
15+
//! Kernel shape type
16+
enum KernelShape
17+
{
18+
KernelQuartic, //!< Quartic kernel
19+
KernelTriangular, //!< Triangular kernel
20+
KernelUniform, //!< Uniform (flat) kernel
21+
KernelTriweight, //!< Triweight kernel
22+
KernelEpanechnikov, //!< Epanechnikov kernel
23+
};
24+
25+
//! Output values type
26+
enum OutputValues
27+
{
28+
OutputRaw, //!< Output the raw KDE values
29+
OutputScaled, //!< Output mathematically correct scaled values
30+
};
31+
32+
//! Result of operation
33+
enum Result
34+
{
35+
Success, //!< Operation completed successfully
36+
DriverError, //!< Could not open the driver for the specified format
37+
InvalidParameters, //!< Input parameters were not valid
38+
FileCreationError, //!< Error creating output file
39+
RasterIoError, //!< Error writing to raster
40+
};
41+
42+
//! KDE parameters
43+
struct Parameters
44+
{
45+
//! Vector point layer
46+
QgsVectorLayer* vectorLayer;
47+
48+
//! Fixed radius, in map units
49+
double radius;
50+
51+
//! Field for radius, or empty if using a fixed radius
52+
QString radiusField;
53+
54+
//! Field name for weighting field, or empty if not using weights
55+
QString weightField;
56+
57+
//! Size of pixel in output file
58+
double pixelSize;
59+
60+
//! Kernel shape
61+
QgsKernelDensityEstimation::KernelShape shape;
62+
63+
//! Decay ratio (Triangular kernels only)
64+
double decayRatio;
65+
66+
//! Type of output value
67+
QgsKernelDensityEstimation::OutputValues outputValues;
68+
};
69+
70+
/**
71+
* Constructor for QgsKernelDensityEstimation. Requires a Parameters object specifying the options to use
72+
* to generate the surface. The output path and file format are also required.
73+
*/
74+
QgsKernelDensityEstimation( const Parameters& parameters, const QString& outputFile, const QString& outputFormat );
75+
76+
/**
77+
* Runs the KDE calculation across the whole layer at once. Either call this method, or manually
78+
* call run(), addFeature() and finalise() separately.
79+
*/
80+
Result run();
81+
82+
/**
83+
* Prepares the output file for writing and setups up the surface calculation. This must be called
84+
* before adding features via addFeature().
85+
* @see addFeature()
86+
* @see finalise()
87+
*/
88+
Result prepare();
89+
90+
/**
91+
* Adds a single feature to the KDE surface. prepare() must be called before adding features.
92+
* @see prepare()
93+
* @see finalise()
94+
*/
95+
Result addFeature( const QgsFeature& feature );
96+
97+
/**
98+
* Finalises the output file. Must be called after adding all features via addFeature().
99+
* @see prepare()
100+
* @see addFeature()
101+
*/
102+
Result finalise();
103+
104+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
***************************************************************************
5+
Heatmap.py
6+
---------------------
7+
Date : November 2016
8+
Copyright : (C) 2016 by Nyall Dawson
9+
Email : nyall dot dawson at gmail dot com
10+
***************************************************************************
11+
* *
12+
* This program is free software; you can redistribute it and/or modify *
13+
* it under the terms of the GNU General Public License as published by *
14+
* the Free Software Foundation; either version 2 of the License, or *
15+
* (at your option) any later version. *
16+
* *
17+
***************************************************************************
18+
"""
19+
20+
__author__ = 'Nyall Dawson'
21+
__date__ = 'November 2016'
22+
__copyright__ = '(C) 2016, Nyall Dawson'
23+
24+
# This will get replaced with a git SHA1 when you do a git archive
25+
26+
__revision__ = '$Format:%H$'
27+
28+
import os
29+
30+
from qgis.PyQt.QtGui import QIcon
31+
32+
from qgis.core import QgsFeatureRequest
33+
from qgis.analysis import QgsKernelDensityEstimation
34+
35+
from processing.core.GeoAlgorithm import GeoAlgorithm
36+
from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
37+
from processing.core.parameters import ParameterVector
38+
from processing.core.parameters import ParameterNumber
39+
from processing.core.parameters import ParameterSelection
40+
from processing.core.parameters import ParameterTableField
41+
from processing.core.outputs import OutputRaster
42+
from processing.tools import dataobjects, vector, raster
43+
from processing.algs.qgis.ui.HeatmapWidgets import HeatmapPixelSizeWidgetWrapper
44+
45+
pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]
46+
47+
48+
class Heatmap(GeoAlgorithm):
49+
50+
INPUT_LAYER = 'INPUT_LAYER'
51+
RADIUS = 'RADIUS'
52+
RADIUS_FIELD = 'RADIUS_FIELD'
53+
WEIGHT_FIELD = 'WEIGHT_FIELD'
54+
PIXEL_SIZE = 'PIXEL_SIZE'
55+
56+
KERNELS = ['Quartic',
57+
'Triangular',
58+
'Uniform',
59+
'Triweight',
60+
'Epanechnikov'
61+
]
62+
KERNEL = 'KERNEL'
63+
DECAY = 'DECAY'
64+
OUTPUT_VALUES = ['Raw',
65+
'Scaled'
66+
]
67+
OUTPUT_VALUE = 'OUTPUT_VALUE'
68+
OUTPUT_LAYER = 'OUTPUT_LAYER'
69+
70+
def getIcon(self):
71+
return QIcon(os.path.join(pluginPath, 'images', 'heatmap.png'))
72+
73+
def defineCharacteristics(self):
74+
self.name, self.i18n_name = self.trAlgorithm('Heatmap (Kernel Density Estimation)')
75+
self.group, self.i18n_group = self.trAlgorithm('Interpolation')
76+
self.tags = self.tr('heatmap,kde,hotspot')
77+
78+
self.addParameter(ParameterVector(self.INPUT_LAYER,
79+
self.tr('Point layer'), [dataobjects.TYPE_VECTOR_POINT]))
80+
self.addParameter(ParameterNumber(self.RADIUS,
81+
self.tr('Radius (layer units)'),
82+
0.0, 9999999999, 100.0))
83+
84+
self.addParameter(ParameterTableField(self.RADIUS_FIELD,
85+
self.tr('Radius from field'), self.INPUT_LAYER, optional=True, datatype=ParameterTableField.DATA_TYPE_NUMBER))
86+
87+
class ParameterHeatmapPixelSize(ParameterNumber):
88+
89+
def __init__(self, name='', description='', parent_layer=None, radius_param=None, radius_field_param=None, minValue=None, maxValue=None,
90+
default=None, optional=False, metadata={}):
91+
ParameterNumber.__init__(self, name, description, minValue, maxValue, default, optional, metadata)
92+
self.parent_layer = parent_layer
93+
self.radius_param = radius_param
94+
self.radius_field_param = radius_field_param
95+
96+
self.addParameter(ParameterHeatmapPixelSize(self.PIXEL_SIZE,
97+
self.tr('Output raster size'), parent_layer=self.INPUT_LAYER, radius_param=self.RADIUS,
98+
radius_field_param=self.RADIUS_FIELD,
99+
minValue=0.0, maxValue=9999999999, default=0.1,
100+
metadata={'widget_wrapper': HeatmapPixelSizeWidgetWrapper}))
101+
102+
self.addParameter(ParameterTableField(self.WEIGHT_FIELD,
103+
self.tr('Weight from field'), self.INPUT_LAYER, optional=True, datatype=ParameterTableField.DATA_TYPE_NUMBER))
104+
self.addParameter(ParameterSelection(self.KERNEL,
105+
self.tr('Kernel shape'), self.KERNELS))
106+
self.addParameter(ParameterNumber(self.DECAY,
107+
self.tr('Decay ratio (Triangular kernels only)'),
108+
-100.0, 100.0, 0.0))
109+
self.addParameter(ParameterSelection(self.OUTPUT_VALUE,
110+
self.tr('Output value scaling'), self.OUTPUT_VALUES))
111+
self.addOutput(OutputRaster(self.OUTPUT_LAYER,
112+
self.tr('Heatmap')))
113+
114+
def processAlgorithm(self, progress):
115+
layer = dataobjects.getObjectFromUri(
116+
self.getParameterValue(self.INPUT_LAYER))
117+
118+
radius = self.getParameterValue(self.RADIUS)
119+
kernel_shape = self.getParameterValue(self.KERNEL)
120+
pixel_size = self.getParameterValue(self.PIXEL_SIZE)
121+
decay = self.getParameterValue(self.DECAY)
122+
output_values = self.getParameterValue(self.OUTPUT_VALUE)
123+
output = self.getOutputValue(self.OUTPUT_LAYER)
124+
output_format = raster.formatShortNameFromFileName(output)
125+
weight_field = self.getParameterValue(self.WEIGHT_FIELD)
126+
radius_field = self.getParameterValue(self.RADIUS_FIELD)
127+
128+
attrs = []
129+
130+
kde_params = QgsKernelDensityEstimation.Parameters()
131+
kde_params.vectorLayer = layer
132+
kde_params.radius = radius
133+
kde_params.pixelSize = pixel_size
134+
# radius field
135+
if radius_field:
136+
kde_params.radiusField = radius_field
137+
attrs.append(layer.fields().lookupField(radius_field))
138+
# weight field
139+
if weight_field:
140+
kde_params.weightField = weight_field
141+
attrs.append(layer.fields().lookupField(weight_field))
142+
143+
kde_params.shape = kernel_shape
144+
kde_params.decayRatio = decay
145+
kde_params.outputValues = output_values
146+
147+
kde = QgsKernelDensityEstimation(kde_params, output, output_format)
148+
149+
if kde.prepare() != QgsKernelDensityEstimation.Success:
150+
raise GeoAlgorithmExecutionException(
151+
self.tr('Could not create destination layer'))
152+
153+
request = QgsFeatureRequest()
154+
request.setSubsetOfAttributes(attrs)
155+
features = vector.features(layer, request)
156+
total = 100.0 / len(features)
157+
for current, f in enumerate(features):
158+
if kde.addFeature(f) != QgsKernelDensityEstimation.Success:
159+
raise GeoAlgorithmExecutionException(
160+
self.tr('Error adding feature to heatmap'))
161+
162+
progress.setPercentage(int(current * total))
163+
164+
if kde.finalise() != QgsKernelDensityEstimation.Success:
165+
raise GeoAlgorithmExecutionException(
166+
self.tr('Could not save destination layer'))

python/plugins/processing/algs/qgis/IdwInterpolationZValue.py

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
)
3737

3838
from processing.core.GeoAlgorithm import GeoAlgorithm
39+
from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
3940
from processing.core.parameters import ParameterVector
4041
from processing.core.parameters import ParameterSelection
4142
from processing.core.parameters import ParameterNumber

python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@
182182
from .CreateAttributeIndex import CreateAttributeIndex
183183
from .DropGeometry import DropGeometry
184184
from .BasicStatistics import BasicStatisticsForField
185+
from .Heatmap import Heatmap
185186

186187
pluginPath = os.path.normpath(os.path.join(
187188
os.path.split(os.path.dirname(__file__))[0], os.pardir))
@@ -246,7 +247,7 @@ def __init__(self):
246247
RemoveNullGeometry(), ExtractByExpression(), ExtendLines(),
247248
ExtractSpecificNodes(), GeometryByExpression(), SnapGeometriesToLayer(),
248249
PoleOfInaccessibility(), CreateAttributeIndex(), DropGeometry(),
249-
BasicStatisticsForField(), RasterCalculator()
250+
BasicStatisticsForField(), RasterCalculator(), Heatmap()
250251
]
251252

252253
if hasMatplotlib:

0 commit comments

Comments
 (0)