[processing] port heatmap algorithm

This commit is contained in:
Alexander Bruy 2017-07-12 10:22:45 +03:00
parent fc7ac8323f
commit 229d2f1add
2 changed files with 119 additions and 84 deletions

View File

@ -26,24 +26,24 @@ __copyright__ = '(C) 2016, Nyall Dawson'
__revision__ = '$Format:%H$'
import os
from collections import OrderedDict
from qgis.PyQt.QtGui import QIcon
from qgis.core import (QgsFeatureRequest,
QgsMessageLog,
QgsProcessing,
QgsProcessingException,
QgsProcessingUtils,
QgsProcessingParameterVectorLayer,
QgsProcessingParameterNumber,
QgsProcessingParameterField,
QgsProcessingParameterEnum,
QgsProcessingParameterDefinition)
from qgis.analysis import QgsKernelDensityEstimation
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
from processing.core.parameters import ParameterVector
from processing.core.parameters import ParameterNumber
from processing.core.parameters import ParameterSelection
from processing.core.parameters import ParameterTableField
from processing.core.outputs import OutputRaster
from processing.tools import dataobjects, raster
from processing.algs.qgis.ui.HeatmapWidgets import HeatmapPixelSizeWidgetWrapper
from processing.tools import raster
pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]
@ -55,18 +55,8 @@ class Heatmap(QgisAlgorithm):
RADIUS_FIELD = 'RADIUS_FIELD'
WEIGHT_FIELD = 'WEIGHT_FIELD'
PIXEL_SIZE = 'PIXEL_SIZE'
KERNELS = ['Quartic',
'Triangular',
'Uniform',
'Triweight',
'Epanechnikov'
]
KERNEL = 'KERNEL'
DECAY = 'DECAY'
OUTPUT_VALUES = ['Raw',
'Scaled'
]
OUTPUT_VALUE = 'OUTPUT_VALUE'
OUTPUT_LAYER = 'OUTPUT_LAYER'
@ -79,74 +69,113 @@ class Heatmap(QgisAlgorithm):
def group(self):
return self.tr('Interpolation')
def __init__(self):
super().__init__()
def initAlgorithm(self, config=None):
self.addParameter(ParameterVector(self.INPUT_LAYER,
self.tr('Point layer'), [dataobjects.TYPE_VECTOR_POINT]))
self.addParameter(ParameterNumber(self.RADIUS,
self.tr('Radius (layer units)'),
0.0, 9999999999, 100.0))
radius_field_param = ParameterTableField(self.RADIUS_FIELD,
self.tr('Radius from field'), self.INPUT_LAYER, optional=True, datatype=ParameterTableField.DATA_TYPE_NUMBER)
radius_field_param.setFlags(radius_field_param.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
self.addParameter(radius_field_param)
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}))
weight_field_param = ParameterTableField(self.WEIGHT_FIELD,
self.tr('Weight from field'), self.INPUT_LAYER, optional=True, datatype=ParameterTableField.DATA_TYPE_NUMBER)
weight_field_param.setFlags(weight_field_param.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
self.addParameter(weight_field_param)
kernel_shape_param = ParameterSelection(self.KERNEL,
self.tr('Kernel shape'), self.KERNELS)
kernel_shape_param.setFlags(kernel_shape_param.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
self.addParameter(kernel_shape_param)
decay_ratio = ParameterNumber(self.DECAY,
self.tr('Decay ratio (Triangular kernels only)'),
-100.0, 100.0, 0.0)
decay_ratio.setFlags(decay_ratio.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
self.addParameter(decay_ratio)
output_scaling = ParameterSelection(self.OUTPUT_VALUE,
self.tr('Output value scaling'), self.OUTPUT_VALUES)
output_scaling.setFlags(output_scaling.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
self.addParameter(output_scaling)
self.addOutput(OutputRaster(self.OUTPUT_LAYER,
self.tr('Heatmap')))
def name(self):
return 'heatmapkerneldensityestimation'
def displayName(self):
return self.tr('Heatmap (Kernel Density Estimation)')
def processAlgorithm(self, parameters, context, feedback):
layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT_LAYER), context)
def __init__(self):
super().__init__()
radius = self.getParameterValue(self.RADIUS)
kernel_shape = self.getParameterValue(self.KERNEL)
pixel_size = self.getParameterValue(self.PIXEL_SIZE)
decay = self.getParameterValue(self.DECAY)
output_values = self.getParameterValue(self.OUTPUT_VALUE)
output = self.getOutputValue(self.OUTPUT_LAYER)
def initAlgorithm(self, config=None):
self.KERNELS = OrderedDict([(self.tr('Quartic'), QgsKernelDensityEstimation.KernelQuartic),
(self.tr('Triangular'), QgsKernelDensityEstimation.KernelTriangular),
(self.tr('Uniform'), QgsKernelDensityEstimation.KernelUniform),
(self.tr('Triweight'), QgsKernelDensityEstimation.KernelTriweight),
(self.tr('Epanechnikov'), QgsKernelDensityEstimation.KernelEpanechnikov)])
self.OUTPUT_VALUES = OrderedDict([(self.tr('Raw'), QgsKernelDensityEstimation.OutputRaw),
(self.tr('Scaled'), QgsKernelDensityEstimation.OutputScaled)])
self.addParameter(QgsProcessingParameterVectorLayer(self.INPUT_LAYER,
self.tr('Point layer'),
[QgsProcessing.TypeVectorPoint]))
self.addParameter(QgsProcessingParameterNumber(self.RADIUS,
self.tr('Radius (layer units)'),
QgsProcessingParameterNumber.Double,
100.0, False, 0.0, 9999999999.99))
radius_field_param = QgsProcessingParameterField(self.RADIUS_FIELD,
self.tr('Radius from field'),
None,
self.INPUT_LAYER,
QgsProcessingParameterField.Numeric,
optional=True
)
radius_field_param.setFlags(radius_field_param.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
self.addParameter(radius_field_param)
class ParameterHeatmapPixelSize(QgsProcessingParameterNumber):
def __init__(self, name='', description='', parent_layer=None, radius_param=None, radius_field_param=None, minValue=None, maxValue=None,
default=None, optional=False):
QgsProcessingParameterNumber.__init__(self, name, description, QgsProcessingParameterNumber.Double, default, optional, minValue, maxValue)
self.parent_layer = parent_layer
self.radius_param = radius_param
self.radius_field_param = radius_field_param
pixel_size_param = 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)
pixel_size_param.setMetadata({
'widget_wrapper': {
'class': 'processing.algs.qgis.ui.HeatmapWidgets.HeatmapPixelSizeWidgetWrapper'}})
self.addParameter(pixel_size_param)
weight_field_param = QgsProcessingParameterField(self.WEIGHT_FIELD,
self.tr('Weight from field'),
None,
self.INPUT_LAYER,
QgsProcessingParameterField.Numeric,
optional=True
)
weight_field_param.setFlags(weight_field_param.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
self.addParameter(weight_field_param)
keys = list(self.KERNELS.keys())
kernel_shape_param = QgsProcessingParameterEnum(self.KERNEL,
self.tr('Kernel shape'),
keys,
allowMultiple=False,
defaultValue=0)
kernel_shape_param.setFlags(kernel_shape_param.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
self.addParameter(kernel_shape_param)
decay_ratio = QgsProcessingParameterNumber(self.DECAY,
self.tr('Decay ratio (Triangular kernels only)'),
QgsProcessingParameterNumber.Double,
0.0, True, -100.0, 100.0)
decay_ratio.setFlags(decay_ratio.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
self.addParameter(decay_ratio)
keys = list(self.OUTPUT_VALUES.keys())
output_scaling = QgsProcessingParameterEnum(self.OUTPUT_VALUE,
self.tr('Output value scaling'),
keys,
allowMultiple=False,
defaultValue=0)
output_scaling.setFlags(output_scaling.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
self.addParameter(output_scaling)
def processAlgorithm(self, parameters, context, feedback):
layer = self.parameterAsVectorLayer(parameters, self.INPUT_LAYER, context)
radius = self.parameterAsDouble(parameters, self.RADIUS, context)
kernel_shape = self.parameterAsEnum(parameters, self.KERNEL, context)
pixel_size = self.parameterAsDouble(parameters, self.PIXEL_SIZE, context)
decay = self.parameterAsDouble(parameters, self.DECAY, context)
output_values = self.parameterAsEnum(parameters, self.OUTPUT_VALUE, context)
output = self.parameterAsOutputLayer(parameters, self.OUTPUT_LAYER, context)
output_format = raster.formatShortNameFromFileName(output)
weight_field = self.getParameterValue(self.WEIGHT_FIELD)
radius_field = self.getParameterValue(self.RADIUS_FIELD)
weight_field = self.parameterAsString(parameters, self.WEIGHT_FIELD, context)
radius_field = self.parameterAsString(parameters, self.RADIUS_FIELD, context)
attrs = []
@ -170,7 +199,7 @@ class Heatmap(QgisAlgorithm):
kde = QgsKernelDensityEstimation(kde_params, output, output_format)
if kde.prepare() != QgsKernelDensityEstimation.Success:
raise GeoAlgorithmExecutionException(
raise QgsProcessingException(
self.tr('Could not create destination layer'))
request = QgsFeatureRequest()
@ -178,11 +207,16 @@ class Heatmap(QgisAlgorithm):
features = QgsProcessingUtils.getFeatures(layer, context, request)
total = 100.0 / layer.featureCount() if layer.featureCount() else 0
for current, f in enumerate(features):
if feedback.isCanceled():
break
if kde.addFeature(f) != QgsKernelDensityEstimation.Success:
QgsMessageLog.logMessage(self.tr('Error adding feature with ID {} to heatmap').format(f.id()), self.tr('Processing'), QgsMessageLog.CRITICAL)
feedback.reportError(self.tr('Error adding feature with ID {} to heatmap').format(f.id()), self.tr('Processing'), QgsMessageLog.CRITICAL)
feedback.setProgress(int(current * total))
if kde.finalise() != QgsKernelDensityEstimation.Success:
raise GeoAlgorithmExecutionException(
raise QgsProcessingException(
self.tr('Could not save destination layer'))
return {self.OUTPUT_LAYER: output}

View File

@ -59,6 +59,7 @@ from .DropGeometry import DropGeometry
from .ExtentFromLayer import ExtentFromLayer
from .FixGeometry import FixGeometry
from .GridPolygon import GridPolygon
from .Heatmap import Heatmap
from .ImportIntoPostGIS import ImportIntoPostGIS
from .ImportIntoSpatialite import ImportIntoSpatialite
from .Intersection import Intersection
@ -159,7 +160,6 @@ from .ZonalStatistics import ZonalStatistics
# from .GeometryByExpression import GeometryByExpression
# from .PoleOfInaccessibility import PoleOfInaccessibility
# from .RasterCalculator import RasterCalculator
# from .Heatmap import Heatmap
# from .Orthogonalize import Orthogonalize
# from .ShortestPathPointToPoint import ShortestPathPointToPoint
# from .ShortestPathPointToLayer import ShortestPathPointToLayer
@ -277,7 +277,8 @@ class QGISAlgorithmProvider(QgsProcessingProvider):
Union(),
VectorSplit(),
VoronoiPolygons(),
ZonalStatistics()
ZonalStatistics(),
Heatmap()
]
if hasPlotly: