InterpolationWidgets.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. """
  2. ***************************************************************************
  3. InterpolationDataWidget.py
  4. ---------------------
  5. Date : December 2016
  6. Copyright : (C) 2016 by Alexander Bruy
  7. Email : alexander dot bruy 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__ = 'Alexander Bruy'
  18. __date__ = 'December 2016'
  19. __copyright__ = '(C) 2016, Alexander Bruy'
  20. import os
  21. from typing import (
  22. Optional,
  23. Union
  24. )
  25. from qgis.PyQt import uic
  26. from qgis.PyQt.QtCore import pyqtSignal
  27. from qgis.PyQt.QtWidgets import (QTreeWidgetItem,
  28. QComboBox)
  29. from qgis.core import (
  30. Qgis,
  31. QgsApplication,
  32. QgsWkbTypes,
  33. QgsRectangle,
  34. QgsReferencedRectangle,
  35. QgsCoordinateReferenceSystem,
  36. QgsProcessingUtils,
  37. QgsProcessingParameterNumber,
  38. QgsProcessingParameterDefinition,
  39. QgsFieldProxyModel,
  40. QgsVectorLayer
  41. )
  42. from qgis.gui import QgsDoubleSpinBox
  43. from qgis.analysis import QgsInterpolator
  44. from processing.gui.wrappers import WidgetWrapper, DIALOG_STANDARD
  45. from processing.tools import dataobjects
  46. pluginPath = os.path.dirname(__file__)
  47. class ParameterInterpolationData(QgsProcessingParameterDefinition):
  48. def __init__(self, name='', description=''):
  49. super().__init__(name, description)
  50. self.setMetadata({
  51. 'widget_wrapper': 'processing.algs.qgis.ui.InterpolationWidgets.InterpolationDataWidgetWrapper'
  52. })
  53. def type(self):
  54. return 'idw_interpolation_data'
  55. def clone(self):
  56. return ParameterInterpolationData(self.name(), self.description())
  57. @staticmethod
  58. def parseValue(value):
  59. if value is None:
  60. return None
  61. if value == '':
  62. return None
  63. if isinstance(value, str):
  64. return value if value != '' else None
  65. else:
  66. return ParameterInterpolationData.dataToString(value)
  67. @staticmethod
  68. def dataToString(data):
  69. s = ''
  70. for c in data:
  71. s += '{}::~::{}::~::{:d}::~::{:d};'.format(c[0],
  72. c[1],
  73. c[2],
  74. c[3])
  75. return s[:-1]
  76. WIDGET, BASE = uic.loadUiType(os.path.join(pluginPath, 'interpolationdatawidgetbase.ui'))
  77. class InterpolationDataWidget(BASE, WIDGET):
  78. hasChanged = pyqtSignal()
  79. def __init__(self):
  80. super().__init__(None)
  81. self.setupUi(self)
  82. self.btnAdd.setIcon(QgsApplication.getThemeIcon('/symbologyAdd.svg'))
  83. self.btnRemove.setIcon(QgsApplication.getThemeIcon('/symbologyRemove.svg'))
  84. self.btnAdd.clicked.connect(self.addLayer)
  85. self.btnRemove.clicked.connect(self.removeLayer)
  86. self.cmbLayers.layerChanged.connect(self.layerChanged)
  87. self.cmbLayers.setFilters(Qgis.LayerFilter.VectorLayer)
  88. self.cmbFields.setFilters(QgsFieldProxyModel.Numeric)
  89. self.cmbFields.setLayer(self.cmbLayers.currentLayer())
  90. def addLayer(self):
  91. layer = self.cmbLayers.currentLayer()
  92. attribute = ''
  93. if self.chkUseZCoordinate.isChecked():
  94. attribute = 'Z_COORD'
  95. else:
  96. attribute = self.cmbFields.currentField()
  97. self._addLayerData(layer.name(), attribute)
  98. self.hasChanged.emit()
  99. def removeLayer(self):
  100. item = self.layersTree.currentItem()
  101. if not item:
  102. return
  103. self.layersTree.invisibleRootItem().removeChild(item)
  104. self.hasChanged.emit()
  105. def layerChanged(self, layer: Optional[QgsVectorLayer]):
  106. self.chkUseZCoordinate.setEnabled(False)
  107. self.chkUseZCoordinate.setChecked(False)
  108. if layer is None or not layer.isValid():
  109. return
  110. provider = layer.dataProvider()
  111. if not provider:
  112. return
  113. if QgsWkbTypes.hasZ(provider.wkbType()):
  114. self.chkUseZCoordinate.setEnabled(True)
  115. self.cmbFields.setLayer(layer)
  116. def _addLayerData(self, layerName: str, attribute: str):
  117. item = QTreeWidgetItem()
  118. item.setText(0, layerName)
  119. item.setText(1, attribute)
  120. self.layersTree.addTopLevelItem(item)
  121. comboBox = QComboBox()
  122. comboBox.addItem(self.tr('Points'))
  123. comboBox.addItem(self.tr('Structure lines'))
  124. comboBox.addItem(self.tr('Break lines'))
  125. comboBox.setCurrentIndex(0)
  126. self.layersTree.setItemWidget(item, 2, comboBox)
  127. def setValue(self, value: str):
  128. self.layersTree.clear()
  129. rows = value.split('::|::')
  130. for i, r in enumerate(rows):
  131. v = r.split('::~::')
  132. layer = QgsProcessingUtils.mapLayerFromString(v[0], dataobjects.createContext())
  133. field_index = int(v[2])
  134. if field_index == -1:
  135. field_name = 'Z_COORD'
  136. else:
  137. field_name = layer.fields().at(field_index).name()
  138. self._addLayerData(v[0], field_name)
  139. comboBox = self.layersTree.itemWidget(self.layersTree.topLevelItem(i), 2)
  140. comboBox.setCurrentIndex(int(v[3]))
  141. self.hasChanged.emit()
  142. def value(self) -> str:
  143. layers = ''
  144. context = dataobjects.createContext()
  145. for i in range(self.layersTree.topLevelItemCount()):
  146. item = self.layersTree.topLevelItem(i)
  147. if item:
  148. layerName = item.text(0)
  149. layer = QgsProcessingUtils.mapLayerFromString(layerName, context)
  150. if not layer:
  151. continue
  152. provider = layer.dataProvider()
  153. if not provider:
  154. continue
  155. interpolationAttribute = item.text(1)
  156. interpolationSource = QgsInterpolator.ValueAttribute
  157. if interpolationAttribute == 'Z_COORD':
  158. interpolationSource = QgsInterpolator.ValueZ
  159. fieldIndex = -1
  160. else:
  161. fieldIndex = layer.fields().indexFromName(interpolationAttribute)
  162. comboBox = self.layersTree.itemWidget(self.layersTree.topLevelItem(i), 2)
  163. inputTypeName = comboBox.currentText()
  164. if inputTypeName == self.tr('Points'):
  165. inputType = QgsInterpolator.SourcePoints
  166. elif inputTypeName == self.tr('Structure lines'):
  167. inputType = QgsInterpolator.SourceStructureLines
  168. else:
  169. inputType = QgsInterpolator.SourceBreakLines
  170. layers += '{}::~::{:d}::~::{:d}::~::{:d}::|::'.format(layer.source(),
  171. interpolationSource,
  172. fieldIndex,
  173. inputType)
  174. return layers[:-len('::|::')]
  175. class InterpolationDataWidgetWrapper(WidgetWrapper):
  176. def createWidget(self):
  177. widget = InterpolationDataWidget()
  178. widget.hasChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
  179. return widget
  180. def setValue(self, value):
  181. self.widget.setValue(value)
  182. def value(self):
  183. return self.widget.value()
  184. class ParameterPixelSize(QgsProcessingParameterNumber):
  185. def __init__(self, name='', description='', layersData=None, extent=None, minValue=None, default=None, optional=False):
  186. QgsProcessingParameterNumber.__init__(self, name, description, QgsProcessingParameterNumber.Double, default, optional, minValue)
  187. self.setMetadata({
  188. 'widget_wrapper': 'processing.algs.qgis.ui.InterpolationWidgets.PixelSizeWidgetWrapper'
  189. })
  190. self.layersData = layersData
  191. self.extent = extent
  192. self.layers = []
  193. def clone(self):
  194. copy = ParameterPixelSize(self.name(), self.description(), self.layersData, self.extent, self.minimum(), self.defaultValue(), self.flags() & QgsProcessingParameterDefinition.FlagOptional)
  195. return copy
  196. WIDGET, BASE = uic.loadUiType(os.path.join(pluginPath, 'RasterResolutionWidget.ui'))
  197. class PixelSizeWidget(BASE, WIDGET):
  198. def __init__(self):
  199. super().__init__(None)
  200. self.setupUi(self)
  201. self.context = dataobjects.createContext()
  202. self.extent: Optional[Union[QgsReferencedRectangle, QgsRectangle]] = QgsRectangle()
  203. self.layers = []
  204. self.mCellXSpinBox.setShowClearButton(False)
  205. self.mCellYSpinBox.setShowClearButton(False)
  206. self.mRowsSpinBox.setShowClearButton(False)
  207. self.mColumnsSpinBox.setShowClearButton(False)
  208. self.mCellYSpinBox.valueChanged.connect(self.mCellXSpinBox.setValue)
  209. self.mCellXSpinBox.valueChanged.connect(self.pixelSizeChanged)
  210. self.mRowsSpinBox.valueChanged.connect(self.rowsChanged)
  211. self.mColumnsSpinBox.valueChanged.connect(self.columnsChanged)
  212. def setLayers(self, layersData: str):
  213. self.extent = QgsRectangle()
  214. self.layers = []
  215. for row in layersData.split(';'):
  216. v = row.split('::~::')
  217. # need to keep a reference until interpolation is complete
  218. layer = QgsProcessingUtils.variantToSource(v[0], self.context)
  219. if layer:
  220. self.layers.append(layer)
  221. self.extent.combineExtentWith(layer.sourceExtent())
  222. self.pixelSizeChanged()
  223. def setExtent(self, extent: Optional[str]):
  224. if extent is not None:
  225. tokens = extent.split(' ')[0].split(',')
  226. ext = QgsRectangle(
  227. float(tokens[0]),
  228. float(tokens[2]),
  229. float(tokens[1]),
  230. float(tokens[3]))
  231. if len(tokens) > 1:
  232. self.extent = QgsReferencedRectangle(
  233. ext, QgsCoordinateReferenceSystem(tokens[1][1:-1]))
  234. else:
  235. self.extent = ext
  236. self.pixelSizeChanged()
  237. def pixelSizeChanged(self):
  238. cell_size = self.mCellXSpinBox.value()
  239. if cell_size <= 0 or self.extent.isNull():
  240. return
  241. self.mCellYSpinBox.blockSignals(True)
  242. self.mCellYSpinBox.setValue(cell_size)
  243. self.mCellYSpinBox.blockSignals(False)
  244. rows = max(round(self.extent.height() / cell_size) + 1, 1)
  245. cols = max(round(self.extent.width() / cell_size) + 1, 1)
  246. self.mRowsSpinBox.blockSignals(True)
  247. self.mRowsSpinBox.setValue(rows)
  248. self.mRowsSpinBox.blockSignals(False)
  249. self.mColumnsSpinBox.blockSignals(True)
  250. self.mColumnsSpinBox.setValue(cols)
  251. self.mColumnsSpinBox.blockSignals(False)
  252. def rowsChanged(self):
  253. rows = self.mRowsSpinBox.value()
  254. if rows <= 0 or self.extent.isNull():
  255. return
  256. cell_size = self.extent.height() / rows
  257. if cell_size == 0:
  258. return
  259. cols = max(round(self.extent.width() / cell_size) + 1, 1)
  260. self.mColumnsSpinBox.blockSignals(True)
  261. self.mColumnsSpinBox.setValue(cols)
  262. self.mColumnsSpinBox.blockSignals(False)
  263. for w in [self.mCellXSpinBox, self.mCellYSpinBox]:
  264. w.blockSignals(True)
  265. w.setValue(cell_size)
  266. w.blockSignals(False)
  267. def columnsChanged(self):
  268. cols = self.mColumnsSpinBox.value()
  269. if cols < 2 or self.extent.isNull():
  270. return
  271. cell_size = self.extent.width() / (cols - 1)
  272. if cell_size == 0:
  273. return
  274. rows = max(round(self.extent.height() / cell_size), 1)
  275. self.mRowsSpinBox.blockSignals(True)
  276. self.mRowsSpinBox.setValue(rows)
  277. self.mRowsSpinBox.blockSignals(False)
  278. for w in [self.mCellXSpinBox, self.mCellYSpinBox]:
  279. w.blockSignals(True)
  280. w.setValue(cell_size)
  281. w.blockSignals(False)
  282. def setValue(self, value):
  283. try:
  284. numeric_value = float(value)
  285. except:
  286. return False
  287. self.mCellXSpinBox.setValue(numeric_value)
  288. self.mCellYSpinBox.setValue(numeric_value)
  289. return True
  290. def value(self):
  291. return self.mCellXSpinBox.value()
  292. class PixelSizeWidgetWrapper(WidgetWrapper):
  293. def __init__(self, param, dialog, row=0, col=0, **kwargs):
  294. super().__init__(param, dialog, row, col, **kwargs)
  295. self.context = dataobjects.createContext()
  296. def _panel(self):
  297. return PixelSizeWidget()
  298. def createWidget(self):
  299. if self.dialogType == DIALOG_STANDARD:
  300. return self._panel()
  301. else:
  302. w = QgsDoubleSpinBox()
  303. w.setShowClearButton(False)
  304. w.setMinimum(0)
  305. w.setMaximum(99999999999)
  306. w.setDecimals(6)
  307. w.setToolTip(self.tr('Resolution of each pixel in output raster, in layer units'))
  308. return w
  309. def postInitialize(self, wrappers):
  310. if self.dialogType != DIALOG_STANDARD:
  311. return
  312. for wrapper in wrappers:
  313. if wrapper.parameterDefinition().name() == self.param.layersData:
  314. self.setLayers(wrapper.parameterValue())
  315. wrapper.widgetValueHasChanged.connect(self.layersChanged)
  316. elif wrapper.parameterDefinition().name() == self.param.extent:
  317. self.setExtent(wrapper.parameterValue())
  318. wrapper.widgetValueHasChanged.connect(self.extentChanged)
  319. def layersChanged(self, wrapper):
  320. self.setLayers(wrapper.parameterValue())
  321. def setLayers(self, layersData: str):
  322. self.widget.setLayers(layersData)
  323. def extentChanged(self, wrapper):
  324. self.setExtent(wrapper.parameterValue())
  325. def setExtent(self, extent: Optional[str]):
  326. self.widget.setExtent(extent)
  327. def setValue(self, value):
  328. return self.widget.setValue(value)
  329. def value(self):
  330. return self.widget.value()