Heatmap.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. """
  2. ***************************************************************************
  3. Heatmap.py
  4. ---------------------
  5. Date : November 2016
  6. Copyright : (C) 2016 by Nyall Dawson
  7. Email : nyall dot dawson at gmail dot com
  8. ***************************************************************************
  9. * *
  10. * This program is free software; you can redistribute it and/or modify *
  11. * it under the terms of the GNU General Public License as published by *
  12. * the Free Software Foundation; either version 2 of the License, or *
  13. * (at your option) any later version. *
  14. * *
  15. ***************************************************************************
  16. """
  17. __author__ = 'Nyall Dawson'
  18. __date__ = 'November 2016'
  19. __copyright__ = '(C) 2016, Nyall Dawson'
  20. import os
  21. from collections import OrderedDict
  22. from qgis.PyQt.QtGui import QIcon
  23. from qgis.core import (QgsApplication,
  24. QgsFeatureRequest,
  25. QgsRasterFileWriter,
  26. QgsProcessing,
  27. QgsProcessingException,
  28. QgsProcessingParameterFeatureSource,
  29. QgsProcessingParameterNumber,
  30. QgsProcessingParameterDistance,
  31. QgsProcessingParameterField,
  32. QgsProcessingParameterEnum,
  33. QgsProcessingParameterDefinition,
  34. QgsProcessingParameterRasterDestination)
  35. from qgis.analysis import QgsKernelDensityEstimation
  36. from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
  37. pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]
  38. class Heatmap(QgisAlgorithm):
  39. INPUT = 'INPUT'
  40. RADIUS = 'RADIUS'
  41. RADIUS_FIELD = 'RADIUS_FIELD'
  42. WEIGHT_FIELD = 'WEIGHT_FIELD'
  43. PIXEL_SIZE = 'PIXEL_SIZE'
  44. KERNEL = 'KERNEL'
  45. DECAY = 'DECAY'
  46. OUTPUT_VALUE = 'OUTPUT_VALUE'
  47. OUTPUT = 'OUTPUT'
  48. def icon(self):
  49. return QgsApplication.getThemeIcon("/heatmap.svg")
  50. def tags(self):
  51. return self.tr('heatmap,kde,hotspot').split(',')
  52. def group(self):
  53. return self.tr('Interpolation')
  54. def groupId(self):
  55. return 'interpolation'
  56. def name(self):
  57. return 'heatmapkerneldensityestimation'
  58. def displayName(self):
  59. return self.tr('Heatmap (Kernel Density Estimation)')
  60. def __init__(self):
  61. super().__init__()
  62. def initAlgorithm(self, config=None):
  63. self.KERNELS = OrderedDict([(self.tr('Quartic'), QgsKernelDensityEstimation.KernelQuartic),
  64. (self.tr('Triangular'), QgsKernelDensityEstimation.KernelTriangular),
  65. (self.tr('Uniform'), QgsKernelDensityEstimation.KernelUniform),
  66. (self.tr('Triweight'), QgsKernelDensityEstimation.KernelTriweight),
  67. (self.tr('Epanechnikov'), QgsKernelDensityEstimation.KernelEpanechnikov)])
  68. self.OUTPUT_VALUES = OrderedDict([(self.tr('Raw'), QgsKernelDensityEstimation.OutputRaw),
  69. (self.tr('Scaled'), QgsKernelDensityEstimation.OutputScaled)])
  70. self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT,
  71. self.tr('Point layer'),
  72. [QgsProcessing.TypeVectorPoint]))
  73. self.addParameter(QgsProcessingParameterDistance(self.RADIUS,
  74. self.tr('Radius'),
  75. 100.0, self.INPUT, False, 0.0))
  76. radius_field_param = QgsProcessingParameterField(self.RADIUS_FIELD,
  77. self.tr('Radius from field'),
  78. None,
  79. self.INPUT,
  80. QgsProcessingParameterField.Numeric,
  81. optional=True
  82. )
  83. radius_field_param.setFlags(radius_field_param.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
  84. self.addParameter(radius_field_param)
  85. class ParameterHeatmapPixelSize(QgsProcessingParameterNumber):
  86. def __init__(self, name='', description='', parent_layer=None, radius_param=None, radius_field_param=None, minValue=None,
  87. default=None, optional=False):
  88. QgsProcessingParameterNumber.__init__(self, name, description, QgsProcessingParameterNumber.Double, default, optional, minValue)
  89. self.parent_layer = parent_layer
  90. self.radius_param = radius_param
  91. self.radius_field_param = radius_field_param
  92. def clone(self):
  93. return ParameterHeatmapPixelSize(self.name(), self.description(), self.parent_layer, self.radius_param, self.radius_field_param, self.minimum(), self.maximum(), self.defaultValue((), self.flags() & QgsProcessingParameterDefinition.FlagOptional))
  94. pixel_size_param = ParameterHeatmapPixelSize(self.PIXEL_SIZE,
  95. self.tr('Output raster size'),
  96. parent_layer=self.INPUT,
  97. radius_param=self.RADIUS,
  98. radius_field_param=self.RADIUS_FIELD,
  99. minValue=0.0,
  100. default=0.1)
  101. pixel_size_param.setMetadata({
  102. 'widget_wrapper': {
  103. 'class': 'processing.algs.qgis.ui.HeatmapWidgets.HeatmapPixelSizeWidgetWrapper'}})
  104. self.addParameter(pixel_size_param)
  105. weight_field_param = QgsProcessingParameterField(self.WEIGHT_FIELD,
  106. self.tr('Weight from field'),
  107. None,
  108. self.INPUT,
  109. QgsProcessingParameterField.Numeric,
  110. optional=True
  111. )
  112. weight_field_param.setFlags(weight_field_param.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
  113. self.addParameter(weight_field_param)
  114. keys = list(self.KERNELS.keys())
  115. kernel_shape_param = QgsProcessingParameterEnum(self.KERNEL,
  116. self.tr('Kernel shape'),
  117. keys,
  118. allowMultiple=False,
  119. defaultValue=0)
  120. kernel_shape_param.setFlags(kernel_shape_param.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
  121. self.addParameter(kernel_shape_param)
  122. decay_ratio = QgsProcessingParameterNumber(self.DECAY,
  123. self.tr('Decay ratio (Triangular kernels only)'),
  124. QgsProcessingParameterNumber.Double,
  125. 0.0, True, -100.0, 100.0)
  126. decay_ratio.setFlags(decay_ratio.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
  127. self.addParameter(decay_ratio)
  128. keys = list(self.OUTPUT_VALUES.keys())
  129. output_scaling = QgsProcessingParameterEnum(self.OUTPUT_VALUE,
  130. self.tr('Output value scaling'),
  131. keys,
  132. allowMultiple=False,
  133. defaultValue=0)
  134. output_scaling.setFlags(output_scaling.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
  135. self.addParameter(output_scaling)
  136. self.addParameter(QgsProcessingParameterRasterDestination(self.OUTPUT, self.tr('Heatmap')))
  137. def processAlgorithm(self, parameters, context, feedback):
  138. source = self.parameterAsSource(parameters, self.INPUT, context)
  139. if source is None:
  140. raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT))
  141. radius = self.parameterAsDouble(parameters, self.RADIUS, context)
  142. kernel_shape = self.parameterAsEnum(parameters, self.KERNEL, context)
  143. pixel_size = self.parameterAsDouble(parameters, self.PIXEL_SIZE, context)
  144. decay = self.parameterAsDouble(parameters, self.DECAY, context)
  145. output_values = self.parameterAsEnum(parameters, self.OUTPUT_VALUE, context)
  146. outputFile = self.parameterAsOutputLayer(parameters, self.OUTPUT, context)
  147. output_format = QgsRasterFileWriter.driverForExtension(os.path.splitext(outputFile)[1])
  148. weight_field = self.parameterAsString(parameters, self.WEIGHT_FIELD, context)
  149. radius_field = self.parameterAsString(parameters, self.RADIUS_FIELD, context)
  150. attrs = []
  151. kde_params = QgsKernelDensityEstimation.Parameters()
  152. kde_params.source = source
  153. kde_params.radius = radius
  154. kde_params.pixelSize = pixel_size
  155. # radius field
  156. if radius_field:
  157. kde_params.radiusField = radius_field
  158. attrs.append(source.fields().lookupField(radius_field))
  159. # weight field
  160. if weight_field:
  161. kde_params.weightField = weight_field
  162. attrs.append(source.fields().lookupField(weight_field))
  163. kde_params.shape = kernel_shape
  164. kde_params.decayRatio = decay
  165. kde_params.outputValues = output_values
  166. kde = QgsKernelDensityEstimation(kde_params, outputFile, output_format)
  167. if kde.prepare() != QgsKernelDensityEstimation.Success:
  168. raise QgsProcessingException(
  169. self.tr('Could not create destination layer'))
  170. request = QgsFeatureRequest()
  171. request.setSubsetOfAttributes(attrs)
  172. features = source.getFeatures(request)
  173. total = 100.0 / source.featureCount() if source.featureCount() else 0
  174. for current, f in enumerate(features):
  175. if feedback.isCanceled():
  176. break
  177. if kde.addFeature(f) != QgsKernelDensityEstimation.Success:
  178. feedback.reportError(self.tr('Error adding feature with ID {} to heatmap').format(f.id()))
  179. feedback.setProgress(int(current * total))
  180. if kde.finalise() != QgsKernelDensityEstimation.Success:
  181. raise QgsProcessingException(
  182. self.tr('Could not save destination layer'))
  183. return {self.OUTPUT: outputFile}