HeatmapWidgets.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. """
  2. ***************************************************************************
  3. Heatmap.py
  4. ---------------------
  5. Date : December 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__ = 'December 2016'
  19. __copyright__ = '(C) 2016, Nyall Dawson'
  20. from processing.gui.wrappers import WidgetWrapper, DIALOG_STANDARD
  21. from processing.tools import dataobjects
  22. import os
  23. from typing import Optional
  24. from qgis.PyQt import uic
  25. from qgis.gui import QgsDoubleSpinBox
  26. from qgis.core import (QgsRectangle,
  27. QgsProcessingUtils)
  28. pluginPath = os.path.dirname(__file__)
  29. WIDGET, BASE = uic.loadUiType(
  30. os.path.join(pluginPath, 'RasterResolutionWidget.ui'))
  31. class HeatmapPixelSizeWidget(BASE, WIDGET):
  32. def __init__(self):
  33. super().__init__(None)
  34. self.setupUi(self)
  35. self.layer_bounds: QgsRectangle = QgsRectangle()
  36. self.source = None
  37. self.raster_bounds: QgsRectangle = QgsRectangle()
  38. self.radius: float = 100
  39. self.radius_field: Optional[str] = None
  40. self.mCellXSpinBox.setShowClearButton(False)
  41. self.mCellYSpinBox.setShowClearButton(False)
  42. self.mRowsSpinBox.setShowClearButton(False)
  43. self.mColumnsSpinBox.setShowClearButton(False)
  44. self.mCellYSpinBox.valueChanged.connect(self.mCellXSpinBox.setValue)
  45. self.mCellXSpinBox.valueChanged.connect(self.pixelSizeChanged)
  46. self.mRowsSpinBox.valueChanged.connect(self.rowsChanged)
  47. self.mColumnsSpinBox.valueChanged.connect(self.columnsChanged)
  48. def setRadius(self, radius: float):
  49. self.radius = radius
  50. self.recalculate_bounds()
  51. def setRadiusField(self, radius_field: Optional[str]):
  52. self.radius_field = radius_field
  53. self.recalculate_bounds()
  54. def setSource(self, source):
  55. if not source:
  56. return
  57. bounds = source.sourceExtent()
  58. if bounds.isNull():
  59. return
  60. self.source = source
  61. self.layer_bounds = bounds
  62. self.recalculate_bounds()
  63. def recalculate_bounds(self):
  64. self.raster_bounds = QgsRectangle(self.layer_bounds)
  65. if not self.source:
  66. return
  67. max_radius = self.radius
  68. if self.radius_field:
  69. idx = self.source.fields().lookupField(self.radius_field)
  70. try:
  71. max_radius = float(self.source.maximumValue(idx))
  72. except:
  73. pass
  74. self.raster_bounds.setXMinimum(self.raster_bounds.xMinimum() - max_radius)
  75. self.raster_bounds.setYMinimum(self.raster_bounds.yMinimum() - max_radius)
  76. self.raster_bounds.setXMaximum(self.raster_bounds.xMaximum() + max_radius)
  77. self.raster_bounds.setYMaximum(self.raster_bounds.yMaximum() + max_radius)
  78. self.pixelSizeChanged()
  79. def pixelSizeChanged(self):
  80. cell_size = self.mCellXSpinBox.value()
  81. if cell_size <= 0 or self.raster_bounds.isNull():
  82. return
  83. self.mCellYSpinBox.blockSignals(True)
  84. self.mCellYSpinBox.setValue(cell_size)
  85. self.mCellYSpinBox.blockSignals(False)
  86. rows = max(round(self.raster_bounds.height() / cell_size) + 1, 1)
  87. cols = max(round(self.raster_bounds.width() / cell_size) + 1, 1)
  88. self.mRowsSpinBox.blockSignals(True)
  89. self.mRowsSpinBox.setValue(rows)
  90. self.mRowsSpinBox.blockSignals(False)
  91. self.mColumnsSpinBox.blockSignals(True)
  92. self.mColumnsSpinBox.setValue(cols)
  93. self.mColumnsSpinBox.blockSignals(False)
  94. def rowsChanged(self):
  95. rows = self.mRowsSpinBox.value()
  96. if rows <= 0 or self.raster_bounds.isNull():
  97. return
  98. cell_size = self.raster_bounds.height() / rows
  99. if cell_size == 0:
  100. return
  101. cols = max(round(self.raster_bounds.width() / cell_size) + 1, 1)
  102. self.mColumnsSpinBox.blockSignals(True)
  103. self.mColumnsSpinBox.setValue(cols)
  104. self.mColumnsSpinBox.blockSignals(False)
  105. for w in [self.mCellXSpinBox, self.mCellYSpinBox]:
  106. w.blockSignals(True)
  107. w.setValue(cell_size)
  108. w.blockSignals(False)
  109. def columnsChanged(self):
  110. cols = self.mColumnsSpinBox.value()
  111. if cols < 2 or self.raster_bounds.isNull():
  112. return
  113. cell_size = self.raster_bounds.width() / (cols - 1)
  114. if cell_size == 0:
  115. return
  116. rows = max(round(self.raster_bounds.height() / cell_size), 1)
  117. self.mRowsSpinBox.blockSignals(True)
  118. self.mRowsSpinBox.setValue(rows)
  119. self.mRowsSpinBox.blockSignals(False)
  120. for w in [self.mCellXSpinBox, self.mCellYSpinBox]:
  121. w.blockSignals(True)
  122. w.setValue(cell_size)
  123. w.blockSignals(False)
  124. def setValue(self, value):
  125. try:
  126. numeric_value = float(value)
  127. except:
  128. return False
  129. self.mCellXSpinBox.setValue(numeric_value)
  130. self.mCellYSpinBox.setValue(numeric_value)
  131. return True
  132. def value(self):
  133. return self.mCellXSpinBox.value()
  134. class HeatmapPixelSizeWidgetWrapper(WidgetWrapper):
  135. def __init__(self, param, dialog, row=0, col=0, **kwargs):
  136. super().__init__(param, dialog, row, col, **kwargs)
  137. self.context = dataobjects.createContext()
  138. def _panel(self):
  139. return HeatmapPixelSizeWidget()
  140. def createWidget(self):
  141. if self.dialogType == DIALOG_STANDARD:
  142. return self._panel()
  143. else:
  144. w = QgsDoubleSpinBox()
  145. w.setShowClearButton(False)
  146. w.setMinimum(0)
  147. w.setMaximum(99999999999)
  148. w.setDecimals(6)
  149. w.setToolTip(self.tr('Resolution of each pixel in output raster, in layer units'))
  150. return w
  151. def postInitialize(self, wrappers):
  152. if self.dialogType != DIALOG_STANDARD:
  153. return
  154. for wrapper in wrappers:
  155. if wrapper.parameterDefinition().name() == self.param.parent_layer:
  156. self.setSource(wrapper.parameterValue())
  157. wrapper.widgetValueHasChanged.connect(self.parentLayerChanged)
  158. elif wrapper.parameterDefinition().name() == self.param.radius_param:
  159. self.setRadius(wrapper.parameterValue())
  160. wrapper.widgetValueHasChanged.connect(self.radiusChanged)
  161. elif wrapper.parameterDefinition().name() == self.param.radius_field_param:
  162. self.setSource(wrapper.parameterValue())
  163. wrapper.widgetValueHasChanged.connect(self.radiusFieldChanged)
  164. def parentLayerChanged(self, wrapper):
  165. self.setSource(wrapper.parameterValue())
  166. def setSource(self, source):
  167. source = QgsProcessingUtils.variantToSource(source, self.context)
  168. self.widget.setSource(source)
  169. def radiusChanged(self, wrapper):
  170. self.setRadius(wrapper.parameterValue())
  171. def setRadius(self, radius: float):
  172. self.widget.setRadius(radius)
  173. def radiusFieldChanged(self, wrapper):
  174. self.setRadiusField(wrapper.parameterValue())
  175. def setRadiusField(self, radius_field: Optional[str]):
  176. self.widget.setRadiusField(radius_field)
  177. def setValue(self, value):
  178. return self.widget.setValue(value)
  179. def value(self):
  180. return self.widget.value()