Skip to content
Permalink
Browse files

[processing][heatmap] custom parameter/widget for output resolution

Matches current behaviour of c++ heatmap plugin
  • Loading branch information
nyalldawson committed Dec 7, 2016
1 parent 20f1f76 commit bef5b5ed68acd03cb20a486e9916cb8a488b6f52
@@ -0,0 +1,143 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->

<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="32px"
height="32px"
id="svg2993"
version="1.1"
inkscape:version="0.48.2 r9819"
sodipodi:docname="New document 3">
<defs
id="defs2995">
<linearGradient
id="linearGradient3779">
<stop
style="stop-color:#ffff00;stop-opacity:1;"
offset="0"
id="stop3781" />
<stop
id="stop3787"
offset="0.5"
style="stop-color:#ffd600;stop-opacity:0.49803922;" />
<stop
style="stop-color:#ff2400;stop-opacity:0.24705882;"
offset="0.75"
id="stop3789" />
<stop
style="stop-color:#ff0000;stop-opacity:0;"
offset="1"
id="stop3783" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3779"
id="radialGradient3785"
cx="9.3768406"
cy="12.397939"
fx="9.3768406"
fy="12.397939"
r="6.6619234"
gradientTransform="matrix(1,0,0,0.96648745,0,0.4154865)"
gradientUnits="userSpaceOnUse" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3779-4"
id="radialGradient3785-3"
cx="9.3768406"
cy="12.397939"
fx="9.3768406"
fy="12.397939"
r="6.6619234"
gradientTransform="matrix(1,0,0,0.96648745,0,0.4154865)"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient3779-4">
<stop
style="stop-color:#ffff00;stop-opacity:1;"
offset="0"
id="stop3781-0" />
<stop
id="stop3787-1"
offset="0.5"
style="stop-color:#ffd600;stop-opacity:0.49803922;" />
<stop
style="stop-color:#ff2400;stop-opacity:0.24705882;"
offset="0.75"
id="stop3789-7" />
<stop
style="stop-color:#ff0000;stop-opacity:0;"
offset="1"
id="stop3783-7" />
</linearGradient>
<radialGradient
r="6.6619234"
fy="12.397939"
fx="9.3768406"
cy="12.397939"
cx="9.3768406"
gradientTransform="matrix(1.7101449,0,0,1.6059679,2.1821045,-0.32384171)"
gradientUnits="userSpaceOnUse"
id="radialGradient3808"
xlink:href="#linearGradient3779-4"
inkscape:collect="always" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="15.836083"
inkscape:cx="22.042908"
inkscape:cy="18.689818"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:window-width="1280"
inkscape:window-height="746"
inkscape:window-x="-1"
inkscape:window-y="25"
inkscape:window-maximized="1" />
<metadata
id="metadata2998">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer">
<path
sodipodi:type="arc"
style="fill:url(#radialGradient3785);fill-opacity:1;fill-rule:evenodd;stroke:none"
id="path3001"
sodipodi:cx="9.3768406"
sodipodi:cy="12.397939"
sodipodi:rx="6.1619234"
sodipodi:ry="5.9386654"
d="m 15.538764,12.397939 a 6.1619234,5.9386654 0 1 1 -12.3238468,0 6.1619234,5.9386654 0 1 1 12.3238468,0 z"
transform="translate(0.26790972,-2.589794)" />
<path
style="fill:url(#radialGradient3808);fill-opacity:1;fill-rule:evenodd;stroke:none"
d="M 18.21875 9.71875 C 12.398894 9.71875 7.6875 14.1438 7.6875 19.59375 C 7.6875 25.0437 12.398894 29.46875 18.21875 29.46875 C 24.038606 29.46875 28.75 25.0437 28.75 19.59375 C 28.75 14.1438 24.038606 9.71875 18.21875 9.71875 z "
id="path3001-8" />
</g>
</svg>
@@ -36,11 +36,11 @@
from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
from processing.core.parameters import ParameterVector
from processing.core.parameters import ParameterNumber
from processing.core.parameters import ParameterExtent
from processing.core.parameters import ParameterSelection
from processing.core.parameters import ParameterTableField
from processing.core.outputs import OutputRaster
from processing.tools import dataobjects, vector, raster
from processing.algs.qgis.ui.HeatmapWidgets import HeatmapPixelSizeWidgetWrapper

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

@@ -80,11 +80,25 @@ def defineCharacteristics(self):
self.addParameter(ParameterNumber(self.RADIUS,
self.tr('Radius (layer units)'),
0.0, 9999999999, 100.0))
self.addParameter(ParameterNumber(self.PIXEL_SIZE,
self.tr('Output pixel size (layer units)'),
0.0, 9999999999, 0.1))

self.addParameter(ParameterTableField(self.RADIUS_FIELD,
self.tr('Radius from field'), self.INPUT_LAYER, optional=True, datatype=ParameterTableField.DATA_TYPE_NUMBER))

class ParameterHeatmapPixelSize(ParameterNumber):

def __init__(self, name='', description='', parent_layer=None, radius_param=None, radius_field_param=None, minValue=None, maxValue=None,
default=None, optional=False, metadata={}):
ParameterNumber.__init__(self, name, description, minValue, maxValue, default, optional, metadata)
self.parent_layer = parent_layer
self.radius_param = radius_param
self.radius_field_param = radius_field_param

self.addParameter(ParameterHeatmapPixelSize(self.PIXEL_SIZE,
self.tr('Output raster size'), parent_layer=self.INPUT_LAYER, radius_param=self.RADIUS,
radius_field_param=self.RADIUS_FIELD,
minValue=0.0, maxValue=9999999999, default=0.1,
metadata={'widget_wrapper': HeatmapPixelSizeWidgetWrapper}))

self.addParameter(ParameterTableField(self.WEIGHT_FIELD,
self.tr('Weight from field'), self.INPUT_LAYER, optional=True, datatype=ParameterTableField.DATA_TYPE_NUMBER))
self.addParameter(ParameterSelection(self.KERNEL,
@@ -0,0 +1,212 @@
# -*- coding: utf-8 -*-

"""
***************************************************************************
Heatmap.py
---------------------
Date : December 2016
Copyright : (C) 2016 by Nyall Dawson
Email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************
"""

__author__ = 'Nyall Dawson'
__date__ = 'December 2016'
__copyright__ = '(C) 2016, Nyall Dawson'

from processing.gui.wrappers import WidgetWrapper, DIALOG_STANDARD
from processing.tools import dataobjects
from processing.core.parameters import _resolveLayers
import os
from qgis.PyQt import uic
from qgis.gui import QgsDoubleSpinBox
from qgis.core import QgsRectangle

pluginPath = os.path.dirname(__file__)
WIDGET, BASE = uic.loadUiType(
os.path.join(pluginPath, 'RasterResolutionWidget.ui'))


class HeatmapPixelSizeWidget(BASE, WIDGET):

def __init__(self):
super(HeatmapPixelSizeWidget, self).__init__(None)
self.setupUi(self)

self.layer_bounds = QgsRectangle()
self.layer = None
self.raster_bounds = QgsRectangle()
self.radius = 100
self.radius_field = None

self.mCellXSpinBox.setShowClearButton(False)
self.mCellYSpinBox.setShowClearButton(False)
self.mRowsSpinBox.setShowClearButton(False)
self.mColumnsSpinBox.setShowClearButton(False)

self.mCellYSpinBox.valueChanged.connect(self.mCellXSpinBox.setValue)
self.mCellXSpinBox.valueChanged.connect(self.pixelSizeChanged)
self.mRowsSpinBox.valueChanged.connect(self.rowsChanged)
self.mColumnsSpinBox.valueChanged.connect(self.columnsChanged)

def setRadius(self, radius):
self.radius = radius
self.recalculate_bounds()

def setRadiusField(self, radius_field):
self.radius_field = radius_field
self.recalculate_bounds()

def setLayer(self, layer):
if not layer:
return
bounds = layer.extent()
if bounds.isNull():
return

self.layer = layer
self.layer_bounds = bounds
self.recalculate_bounds()

def recalculate_bounds(self):
self.raster_bounds = QgsRectangle(self.layer_bounds)

if not self.layer:
return

max_radius = self.radius
if self.radius_field:
idx = self.layer.fields().lookupField(self.radius_field)
try:
max_radius = float(self.layer.maximumValue(idx))
except:
pass

self.raster_bounds.setXMinimum(self.raster_bounds.xMinimum() - max_radius)
self.raster_bounds.setYMinimum(self.raster_bounds.yMinimum() - max_radius)
self.raster_bounds.setXMaximum(self.raster_bounds.xMaximum() + max_radius)
self.raster_bounds.setYMaximum(self.raster_bounds.yMaximum() + max_radius)

self.pixelSizeChanged()

def pixelSizeChanged(self):
cell_size = self.mCellXSpinBox.value()
if cell_size <= 0:
return
self.mCellYSpinBox.blockSignals(True)
self.mCellYSpinBox.setValue(cell_size)
self.mCellYSpinBox.blockSignals(False)
rows = max(round(self.raster_bounds.height() / cell_size) + 1, 1)
cols = max(round(self.raster_bounds.width() / cell_size) + 1, 1)
self.mRowsSpinBox.blockSignals(True)
self.mRowsSpinBox.setValue(rows)
self.mRowsSpinBox.blockSignals(False)
self.mColumnsSpinBox.blockSignals(True)
self.mColumnsSpinBox.setValue(cols)
self.mColumnsSpinBox.blockSignals(False)

def rowsChanged(self):
rows = self.mRowsSpinBox.value()
if rows <= 0:
return
cell_size = self.raster_bounds.height() / rows
cols = max(round(self.raster_bounds.width() / cell_size) + 1, 1)
self.mColumnsSpinBox.blockSignals(True)
self.mColumnsSpinBox.setValue(cols)
self.mColumnsSpinBox.blockSignals(False)
for w in [self.mCellXSpinBox, self.mCellYSpinBox]:
w.blockSignals(True)
w.setValue(cell_size)
w.blockSignals(False)

def columnsChanged(self):
cols = self.mColumnsSpinBox.value()
if cols < 2:
return
cell_size = self.raster_bounds.width() / (cols - 1)
rows = max(round(self.raster_bounds.height() / cell_size), 1)
self.mRowsSpinBox.blockSignals(True)
self.mRowsSpinBox.setValue(rows)
self.mRowsSpinBox.blockSignals(False)
for w in [self.mCellXSpinBox, self.mCellYSpinBox]:
w.blockSignals(True)
w.setValue(cell_size)
w.blockSignals(False)

def setValue(self, value):
try:
numeric_value = float(value)
except:
return False

self.mCellXSpinBox.setValue(numeric_value)
self.mCellYSpinBox.setValue(numeric_value)
return True

def value(self):
return self.mCellXSpinBox.value()


class HeatmapPixelSizeWidgetWrapper(WidgetWrapper):

def _panel(self):
return HeatmapPixelSizeWidget()

def createWidget(self):
if self.dialogType == DIALOG_STANDARD:
return self._panel()
else:
w = QgsDoubleSpinBox()
w.setShowClearButton(False)
w.setMinimum(0)
w.setMaximum(99999999999)
w.setDecimals(6)
w.setTooltip(self.tr('Resolution of each pixel in output raster, in layer units'))

def postInitialize(self, wrappers):
if self.dialogType != DIALOG_STANDARD:
return

for wrapper in wrappers:
if wrapper.param.name == self.param.parent_layer:
self.setLayer(wrapper.value())
wrapper.widgetValueHasChanged.connect(self.parentLayerChanged)
elif wrapper.param.name == self.param.radius_param:
self.setRadius(wrapper.value())
wrapper.widgetValueHasChanged.connect(self.radiusChanged)
elif wrapper.param.name == self.param.radius_field_param:
self.setLayer(wrapper.value())
wrapper.widgetValueHasChanged.connect(self.radiusFieldChanged)

def parentLayerChanged(self, wrapper):
self.setLayer(wrapper.value())

def setLayer(self, layer):
if isinstance(layer, str):
layer = dataobjects.getObjectFromUri(_resolveLayers(layer))
self.widget.setLayer(layer)

def radiusChanged(self, wrapper):
self.setRadius(wrapper.value())

def setRadius(self, radius):
self.widget.setRadius(radius)

def radiusFieldChanged(self, wrapper):
self.setRadiusField(wrapper.value())

def setRadiusField(self, radius_field):
self.widget.setRadiusField(radius_field)

def setValue(self, value):
return self.widget.setValue(value)

def value(self):
return self.widget.value()

0 comments on commit bef5b5e

Please sign in to comment.
You can’t perform that action at this time.