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