mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-15 00:04:00 -04:00
[processing] port heatmap algorithm
This commit is contained in:
parent
fc7ac8323f
commit
229d2f1add
@ -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}
|
||||
|
@ -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:
|
||||
|
Loading…
x
Reference in New Issue
Block a user