Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
Merge pull request #3782 from nyalldawson/heatmap
Port heatmap plugin code to analysis, add processing alg
- Loading branch information
Showing
26 changed files
with
1,239 additions
and
2,111 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
/** | ||
* \class QgsKernelDensityEstimation | ||
* \ingroup analysis | ||
* Performs Kernel Density Estimation ("heatmap") calculations on a vector layer. | ||
* @note added in QGIS 3.0 | ||
*/ | ||
class QgsKernelDensityEstimation | ||
{ | ||
%TypeHeaderCode | ||
#include <qgskde.h> | ||
%End | ||
|
||
public: | ||
|
||
//! Kernel shape type | ||
enum KernelShape | ||
{ | ||
KernelQuartic, //!< Quartic kernel | ||
KernelTriangular, //!< Triangular kernel | ||
KernelUniform, //!< Uniform (flat) kernel | ||
KernelTriweight, //!< Triweight kernel | ||
KernelEpanechnikov, //!< Epanechnikov kernel | ||
}; | ||
|
||
//! Output values type | ||
enum OutputValues | ||
{ | ||
OutputRaw, //!< Output the raw KDE values | ||
OutputScaled, //!< Output mathematically correct scaled values | ||
}; | ||
|
||
//! Result of operation | ||
enum Result | ||
{ | ||
Success, //!< Operation completed successfully | ||
DriverError, //!< Could not open the driver for the specified format | ||
InvalidParameters, //!< Input parameters were not valid | ||
FileCreationError, //!< Error creating output file | ||
RasterIoError, //!< Error writing to raster | ||
}; | ||
|
||
//! KDE parameters | ||
struct Parameters | ||
{ | ||
//! Vector point layer | ||
QgsVectorLayer* vectorLayer; | ||
|
||
//! Fixed radius, in map units | ||
double radius; | ||
|
||
//! Field for radius, or empty if using a fixed radius | ||
QString radiusField; | ||
|
||
//! Field name for weighting field, or empty if not using weights | ||
QString weightField; | ||
|
||
//! Size of pixel in output file | ||
double pixelSize; | ||
|
||
//! Kernel shape | ||
QgsKernelDensityEstimation::KernelShape shape; | ||
|
||
//! Decay ratio (Triangular kernels only) | ||
double decayRatio; | ||
|
||
//! Type of output value | ||
QgsKernelDensityEstimation::OutputValues outputValues; | ||
}; | ||
|
||
/** | ||
* Constructor for QgsKernelDensityEstimation. Requires a Parameters object specifying the options to use | ||
* to generate the surface. The output path and file format are also required. | ||
*/ | ||
QgsKernelDensityEstimation( const Parameters& parameters, const QString& outputFile, const QString& outputFormat ); | ||
|
||
/** | ||
* Runs the KDE calculation across the whole layer at once. Either call this method, or manually | ||
* call run(), addFeature() and finalise() separately. | ||
*/ | ||
Result run(); | ||
|
||
/** | ||
* Prepares the output file for writing and setups up the surface calculation. This must be called | ||
* before adding features via addFeature(). | ||
* @see addFeature() | ||
* @see finalise() | ||
*/ | ||
Result prepare(); | ||
|
||
/** | ||
* Adds a single feature to the KDE surface. prepare() must be called before adding features. | ||
* @see prepare() | ||
* @see finalise() | ||
*/ | ||
Result addFeature( const QgsFeature& feature ); | ||
|
||
/** | ||
* Finalises the output file. Must be called after adding all features via addFeature(). | ||
* @see prepare() | ||
* @see addFeature() | ||
*/ | ||
Result finalise(); | ||
|
||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
""" | ||
*************************************************************************** | ||
Heatmap.py | ||
--------------------- | ||
Date : November 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__ = 'November 2016' | ||
__copyright__ = '(C) 2016, Nyall Dawson' | ||
|
||
# This will get replaced with a git SHA1 when you do a git archive | ||
|
||
__revision__ = '$Format:%H$' | ||
|
||
import os | ||
|
||
from qgis.PyQt.QtGui import QIcon | ||
|
||
from qgis.core import QgsFeatureRequest | ||
from qgis.analysis import QgsKernelDensityEstimation | ||
|
||
from processing.core.GeoAlgorithm import GeoAlgorithm | ||
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, vector, raster | ||
from processing.algs.qgis.ui.HeatmapWidgets import HeatmapPixelSizeWidgetWrapper | ||
|
||
pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] | ||
|
||
|
||
class Heatmap(GeoAlgorithm): | ||
|
||
INPUT_LAYER = 'INPUT_LAYER' | ||
RADIUS = 'RADIUS' | ||
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' | ||
|
||
def getIcon(self): | ||
return QIcon(os.path.join(pluginPath, 'images', 'heatmap.png')) | ||
|
||
def defineCharacteristics(self): | ||
self.name, self.i18n_name = self.trAlgorithm('Heatmap (Kernel Density Estimation)') | ||
self.group, self.i18n_group = self.trAlgorithm('Interpolation') | ||
self.tags = self.tr('heatmap,kde,hotspot') | ||
|
||
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)) | ||
|
||
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, | ||
self.tr('Kernel shape'), self.KERNELS)) | ||
self.addParameter(ParameterNumber(self.DECAY, | ||
self.tr('Decay ratio (Triangular kernels only)'), | ||
-100.0, 100.0, 0.0)) | ||
self.addParameter(ParameterSelection(self.OUTPUT_VALUE, | ||
self.tr('Output value scaling'), self.OUTPUT_VALUES)) | ||
self.addOutput(OutputRaster(self.OUTPUT_LAYER, | ||
self.tr('Heatmap'))) | ||
|
||
def processAlgorithm(self, progress): | ||
layer = dataobjects.getObjectFromUri( | ||
self.getParameterValue(self.INPUT_LAYER)) | ||
|
||
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) | ||
output_format = raster.formatShortNameFromFileName(output) | ||
weight_field = self.getParameterValue(self.WEIGHT_FIELD) | ||
radius_field = self.getParameterValue(self.RADIUS_FIELD) | ||
|
||
attrs = [] | ||
|
||
kde_params = QgsKernelDensityEstimation.Parameters() | ||
kde_params.vectorLayer = layer | ||
kde_params.radius = radius | ||
kde_params.pixelSize = pixel_size | ||
# radius field | ||
if radius_field: | ||
kde_params.radiusField = radius_field | ||
attrs.append(layer.fields().lookupField(radius_field)) | ||
# weight field | ||
if weight_field: | ||
kde_params.weightField = weight_field | ||
attrs.append(layer.fields().lookupField(weight_field)) | ||
|
||
kde_params.shape = kernel_shape | ||
kde_params.decayRatio = decay | ||
kde_params.outputValues = output_values | ||
|
||
kde = QgsKernelDensityEstimation(kde_params, output, output_format) | ||
|
||
if kde.prepare() != QgsKernelDensityEstimation.Success: | ||
raise GeoAlgorithmExecutionException( | ||
self.tr('Could not create destination layer')) | ||
|
||
request = QgsFeatureRequest() | ||
request.setSubsetOfAttributes(attrs) | ||
features = vector.features(layer, request) | ||
total = 100.0 / len(features) | ||
for current, f in enumerate(features): | ||
if kde.addFeature(f) != QgsKernelDensityEstimation.Success: | ||
raise GeoAlgorithmExecutionException( | ||
self.tr('Error adding feature to heatmap')) | ||
|
||
progress.setPercentage(int(current * total)) | ||
|
||
if kde.finalise() != QgsKernelDensityEstimation.Success: | ||
raise GeoAlgorithmExecutionException( | ||
self.tr('Could not save destination layer')) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.