Skip to content

Commit cfbed91

Browse files
authoredJul 13, 2017
Merge pull request #4843 from alexbruy/processing-heatmap
[processing] port heatmap algorithm
2 parents fc7ac83 + 8285712 commit cfbed91

File tree

3 files changed

+125
-72
lines changed

3 files changed

+125
-72
lines changed
 

‎python/plugins/processing/algs/qgis/Heatmap.py

Lines changed: 106 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,24 @@
2626
__revision__ = '$Format:%H$'
2727

2828
import os
29+
from collections import OrderedDict
2930

3031
from qgis.PyQt.QtGui import QIcon
3132

3233
from qgis.core import (QgsFeatureRequest,
33-
QgsMessageLog,
34-
QgsProcessingUtils,
35-
QgsProcessingParameterDefinition)
34+
QgsProcessing,
35+
QgsProcessingException,
36+
QgsProcessingParameterVectorLayer,
37+
QgsProcessingParameterNumber,
38+
QgsProcessingParameterField,
39+
QgsProcessingParameterEnum,
40+
QgsProcessingParameterDefinition,
41+
QgsProcessingParameterRasterDestination)
42+
3643
from qgis.analysis import QgsKernelDensityEstimation
3744

3845
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
39-
from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
40-
from processing.core.parameters import ParameterVector
41-
from processing.core.parameters import ParameterNumber
42-
from processing.core.parameters import ParameterSelection
43-
from processing.core.parameters import ParameterTableField
44-
from processing.core.outputs import OutputRaster
45-
from processing.tools import dataobjects, raster
46-
from processing.algs.qgis.ui.HeatmapWidgets import HeatmapPixelSizeWidgetWrapper
46+
from processing.tools import raster
4747

4848
pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]
4949

@@ -55,18 +55,8 @@ class Heatmap(QgisAlgorithm):
5555
RADIUS_FIELD = 'RADIUS_FIELD'
5656
WEIGHT_FIELD = 'WEIGHT_FIELD'
5757
PIXEL_SIZE = 'PIXEL_SIZE'
58-
59-
KERNELS = ['Quartic',
60-
'Triangular',
61-
'Uniform',
62-
'Triweight',
63-
'Epanechnikov'
64-
]
6558
KERNEL = 'KERNEL'
6659
DECAY = 'DECAY'
67-
OUTPUT_VALUES = ['Raw',
68-
'Scaled'
69-
]
7060
OUTPUT_VALUE = 'OUTPUT_VALUE'
7161
OUTPUT_LAYER = 'OUTPUT_LAYER'
7262

@@ -79,74 +69,115 @@ def tags(self):
7969
def group(self):
8070
return self.tr('Interpolation')
8171

72+
def name(self):
73+
return 'heatmapkerneldensityestimation'
74+
75+
def displayName(self):
76+
return self.tr('Heatmap (Kernel Density Estimation)')
77+
8278
def __init__(self):
8379
super().__init__()
8480

8581
def initAlgorithm(self, config=None):
86-
self.addParameter(ParameterVector(self.INPUT_LAYER,
87-
self.tr('Point layer'), [dataobjects.TYPE_VECTOR_POINT]))
88-
self.addParameter(ParameterNumber(self.RADIUS,
89-
self.tr('Radius (layer units)'),
90-
0.0, 9999999999, 100.0))
91-
92-
radius_field_param = ParameterTableField(self.RADIUS_FIELD,
93-
self.tr('Radius from field'), self.INPUT_LAYER, optional=True, datatype=ParameterTableField.DATA_TYPE_NUMBER)
82+
self.KERNELS = OrderedDict([(self.tr('Quartic'), QgsKernelDensityEstimation.KernelQuartic),
83+
(self.tr('Triangular'), QgsKernelDensityEstimation.KernelTriangular),
84+
(self.tr('Uniform'), QgsKernelDensityEstimation.KernelUniform),
85+
(self.tr('Triweight'), QgsKernelDensityEstimation.KernelTriweight),
86+
(self.tr('Epanechnikov'), QgsKernelDensityEstimation.KernelEpanechnikov)])
87+
88+
self.OUTPUT_VALUES = OrderedDict([(self.tr('Raw'), QgsKernelDensityEstimation.OutputRaw),
89+
(self.tr('Scaled'), QgsKernelDensityEstimation.OutputScaled)])
90+
91+
self.addParameter(QgsProcessingParameterVectorLayer(self.INPUT_LAYER,
92+
self.tr('Point layer'),
93+
[QgsProcessing.TypeVectorPoint]))
94+
95+
self.addParameter(QgsProcessingParameterNumber(self.RADIUS,
96+
self.tr('Radius (layer units)'),
97+
QgsProcessingParameterNumber.Double,
98+
100.0, False, 0.0, 9999999999.99))
99+
100+
radius_field_param = QgsProcessingParameterField(self.RADIUS_FIELD,
101+
self.tr('Radius from field'),
102+
None,
103+
self.INPUT_LAYER,
104+
QgsProcessingParameterField.Numeric,
105+
optional=True
106+
)
94107
radius_field_param.setFlags(radius_field_param.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
95108
self.addParameter(radius_field_param)
96109

97-
class ParameterHeatmapPixelSize(ParameterNumber):
110+
class ParameterHeatmapPixelSize(QgsProcessingParameterNumber):
98111

99112
def __init__(self, name='', description='', parent_layer=None, radius_param=None, radius_field_param=None, minValue=None, maxValue=None,
100-
default=None, optional=False, metadata={}):
101-
ParameterNumber.__init__(self, name, description, minValue, maxValue, default, optional, metadata)
113+
default=None, optional=False):
114+
QgsProcessingParameterNumber.__init__(self, name, description, QgsProcessingParameterNumber.Double, default, optional, minValue, maxValue)
102115
self.parent_layer = parent_layer
103116
self.radius_param = radius_param
104117
self.radius_field_param = radius_field_param
105118

106-
self.addParameter(ParameterHeatmapPixelSize(self.PIXEL_SIZE,
107-
self.tr('Output raster size'), parent_layer=self.INPUT_LAYER, radius_param=self.RADIUS,
108-
radius_field_param=self.RADIUS_FIELD,
109-
minValue=0.0, maxValue=9999999999, default=0.1,
110-
metadata={'widget_wrapper': HeatmapPixelSizeWidgetWrapper}))
111-
112-
weight_field_param = ParameterTableField(self.WEIGHT_FIELD,
113-
self.tr('Weight from field'), self.INPUT_LAYER, optional=True, datatype=ParameterTableField.DATA_TYPE_NUMBER)
119+
pixel_size_param = ParameterHeatmapPixelSize(self.PIXEL_SIZE,
120+
self.tr('Output raster size'),
121+
parent_layer=self.INPUT_LAYER,
122+
radius_param=self.RADIUS,
123+
radius_field_param=self.RADIUS_FIELD,
124+
minValue=0.0,
125+
maxValue=9999999999,
126+
default=0.1)
127+
pixel_size_param.setMetadata({
128+
'widget_wrapper': {
129+
'class': 'processing.algs.qgis.ui.HeatmapWidgets.HeatmapPixelSizeWidgetWrapper'}})
130+
self.addParameter(pixel_size_param)
131+
132+
weight_field_param = QgsProcessingParameterField(self.WEIGHT_FIELD,
133+
self.tr('Weight from field'),
134+
None,
135+
self.INPUT_LAYER,
136+
QgsProcessingParameterField.Numeric,
137+
optional=True
138+
)
114139
weight_field_param.setFlags(weight_field_param.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
115140
self.addParameter(weight_field_param)
116-
kernel_shape_param = ParameterSelection(self.KERNEL,
117-
self.tr('Kernel shape'), self.KERNELS)
141+
142+
keys = list(self.KERNELS.keys())
143+
kernel_shape_param = QgsProcessingParameterEnum(self.KERNEL,
144+
self.tr('Kernel shape'),
145+
keys,
146+
allowMultiple=False,
147+
defaultValue=0)
118148
kernel_shape_param.setFlags(kernel_shape_param.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
119149
self.addParameter(kernel_shape_param)
120-
decay_ratio = ParameterNumber(self.DECAY,
121-
self.tr('Decay ratio (Triangular kernels only)'),
122-
-100.0, 100.0, 0.0)
150+
151+
decay_ratio = QgsProcessingParameterNumber(self.DECAY,
152+
self.tr('Decay ratio (Triangular kernels only)'),
153+
QgsProcessingParameterNumber.Double,
154+
0.0, True, -100.0, 100.0)
123155
decay_ratio.setFlags(decay_ratio.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
124156
self.addParameter(decay_ratio)
125-
output_scaling = ParameterSelection(self.OUTPUT_VALUE,
126-
self.tr('Output value scaling'), self.OUTPUT_VALUES)
157+
158+
keys = list(self.OUTPUT_VALUES.keys())
159+
output_scaling = QgsProcessingParameterEnum(self.OUTPUT_VALUE,
160+
self.tr('Output value scaling'),
161+
keys,
162+
allowMultiple=False,
163+
defaultValue=0)
127164
output_scaling.setFlags(output_scaling.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
128165
self.addParameter(output_scaling)
129-
self.addOutput(OutputRaster(self.OUTPUT_LAYER,
130-
self.tr('Heatmap')))
131166

132-
def name(self):
133-
return 'heatmapkerneldensityestimation'
134-
135-
def displayName(self):
136-
return self.tr('Heatmap (Kernel Density Estimation)')
167+
self.addParameter(QgsProcessingParameterRasterDestination(self.OUTPUT_LAYER, self.tr('Heatmap')))
137168

138169
def processAlgorithm(self, parameters, context, feedback):
139-
layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT_LAYER), context)
140-
141-
radius = self.getParameterValue(self.RADIUS)
142-
kernel_shape = self.getParameterValue(self.KERNEL)
143-
pixel_size = self.getParameterValue(self.PIXEL_SIZE)
144-
decay = self.getParameterValue(self.DECAY)
145-
output_values = self.getParameterValue(self.OUTPUT_VALUE)
146-
output = self.getOutputValue(self.OUTPUT_LAYER)
147-
output_format = raster.formatShortNameFromFileName(output)
148-
weight_field = self.getParameterValue(self.WEIGHT_FIELD)
149-
radius_field = self.getParameterValue(self.RADIUS_FIELD)
170+
layer = self.parameterAsVectorLayer(parameters, self.INPUT_LAYER, context)
171+
172+
radius = self.parameterAsDouble(parameters, self.RADIUS, context)
173+
kernel_shape = self.parameterAsEnum(parameters, self.KERNEL, context)
174+
pixel_size = self.parameterAsDouble(parameters, self.PIXEL_SIZE, context)
175+
decay = self.parameterAsDouble(parameters, self.DECAY, context)
176+
output_values = self.parameterAsEnum(parameters, self.OUTPUT_VALUE, context)
177+
outputFile = self.parameterAsOutputLayer(parameters, self.OUTPUT_LAYER, context)
178+
output_format = raster.formatShortNameFromFileName(outputFile)
179+
weight_field = self.parameterAsString(parameters, self.WEIGHT_FIELD, context)
180+
radius_field = self.parameterAsString(parameters, self.RADIUS_FIELD, context)
150181

151182
attrs = []
152183

@@ -167,22 +198,27 @@ def processAlgorithm(self, parameters, context, feedback):
167198
kde_params.decayRatio = decay
168199
kde_params.outputValues = output_values
169200

170-
kde = QgsKernelDensityEstimation(kde_params, output, output_format)
201+
kde = QgsKernelDensityEstimation(kde_params, outputFile, output_format)
171202

172203
if kde.prepare() != QgsKernelDensityEstimation.Success:
173-
raise GeoAlgorithmExecutionException(
204+
raise QgsProcessingException(
174205
self.tr('Could not create destination layer'))
175206

176207
request = QgsFeatureRequest()
177208
request.setSubsetOfAttributes(attrs)
178-
features = QgsProcessingUtils.getFeatures(layer, context, request)
209+
features = layer.getFeatures(request)
179210
total = 100.0 / layer.featureCount() if layer.featureCount() else 0
180211
for current, f in enumerate(features):
212+
if feedback.isCanceled():
213+
break
214+
181215
if kde.addFeature(f) != QgsKernelDensityEstimation.Success:
182-
QgsMessageLog.logMessage(self.tr('Error adding feature with ID {} to heatmap').format(f.id()), self.tr('Processing'), QgsMessageLog.CRITICAL)
216+
feedback.reportError(self.tr('Error adding feature with ID {} to heatmap').format(f.id()), self.tr('Processing'), QgsMessageLog.CRITICAL)
183217

184218
feedback.setProgress(int(current * total))
185219

186220
if kde.finalise() != QgsKernelDensityEstimation.Success:
187-
raise GeoAlgorithmExecutionException(
221+
raise QgsProcessingException(
188222
self.tr('Could not save destination layer'))
223+
224+
return {self.OUTPUT_LAYER: outputFile}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
from .ExtentFromLayer import ExtentFromLayer
6060
from .FixGeometry import FixGeometry
6161
from .GridPolygon import GridPolygon
62+
from .Heatmap import Heatmap
6263
from .ImportIntoPostGIS import ImportIntoPostGIS
6364
from .ImportIntoSpatialite import ImportIntoSpatialite
6465
from .Intersection import Intersection
@@ -159,7 +160,6 @@
159160
# from .GeometryByExpression import GeometryByExpression
160161
# from .PoleOfInaccessibility import PoleOfInaccessibility
161162
# from .RasterCalculator import RasterCalculator
162-
# from .Heatmap import Heatmap
163163
# from .Orthogonalize import Orthogonalize
164164
# from .ShortestPathPointToPoint import ShortestPathPointToPoint
165165
# from .ShortestPathPointToLayer import ShortestPathPointToLayer
@@ -277,7 +277,8 @@ def getAlgs(self):
277277
Union(),
278278
VectorSplit(),
279279
VoronoiPolygons(),
280-
ZonalStatistics()
280+
ZonalStatistics(),
281+
Heatmap()
281282
]
282283

283284
if hasPlotly:

‎python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2591,3 +2591,19 @@ tests:
25912591
type: vector
25922592
name: points_weighted.gml
25932593
compare: false
2594+
2595+
- algorithm: qgis:heatmapkerneldensityestimation
2596+
name: Heatmap (Kernel density estimation)
2597+
params:
2598+
DECAY: 0.0
2599+
INPUT_LAYER:
2600+
name: points.gml
2601+
type: vector
2602+
KERNEL: '0'
2603+
OUTPUT_VALUE: '0'
2604+
PIXEL_SIZE: 0.1
2605+
RADIUS: 0.5
2606+
results:
2607+
OUTPUT_LAYER:
2608+
hash: a594d4fa994b81641bfadde54447dc548fa6e23f630c6a4c383487c5
2609+
type: rasterhash

0 commit comments

Comments
 (0)
Please sign in to comment.