123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420 |
- """
- ***************************************************************************
- InterpolationDataWidget.py
- ---------------------
- Date : December 2016
- Copyright : (C) 2016 by Alexander Bruy
- Email : alexander dot bruy 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__ = 'Alexander Bruy'
- __date__ = 'December 2016'
- __copyright__ = '(C) 2016, Alexander Bruy'
- import os
- from typing import (
- Optional,
- Union
- )
- from qgis.PyQt import uic
- from qgis.PyQt.QtCore import pyqtSignal
- from qgis.PyQt.QtWidgets import (QTreeWidgetItem,
- QComboBox)
- from qgis.core import (
- Qgis,
- QgsApplication,
- QgsWkbTypes,
- QgsRectangle,
- QgsReferencedRectangle,
- QgsCoordinateReferenceSystem,
- QgsProcessingUtils,
- QgsProcessingParameterNumber,
- QgsProcessingParameterDefinition,
- QgsFieldProxyModel,
- QgsVectorLayer
- )
- from qgis.gui import QgsDoubleSpinBox
- from qgis.analysis import QgsInterpolator
- from processing.gui.wrappers import WidgetWrapper, DIALOG_STANDARD
- from processing.tools import dataobjects
- pluginPath = os.path.dirname(__file__)
- class ParameterInterpolationData(QgsProcessingParameterDefinition):
- def __init__(self, name='', description=''):
- super().__init__(name, description)
- self.setMetadata({
- 'widget_wrapper': 'processing.algs.qgis.ui.InterpolationWidgets.InterpolationDataWidgetWrapper'
- })
- def type(self):
- return 'idw_interpolation_data'
- def clone(self):
- return ParameterInterpolationData(self.name(), self.description())
- @staticmethod
- def parseValue(value):
- if value is None:
- return None
- if value == '':
- return None
- if isinstance(value, str):
- return value if value != '' else None
- else:
- return ParameterInterpolationData.dataToString(value)
- @staticmethod
- def dataToString(data):
- s = ''
- for c in data:
- s += '{}::~::{}::~::{:d}::~::{:d};'.format(c[0],
- c[1],
- c[2],
- c[3])
- return s[:-1]
- WIDGET, BASE = uic.loadUiType(os.path.join(pluginPath, 'interpolationdatawidgetbase.ui'))
- class InterpolationDataWidget(BASE, WIDGET):
- hasChanged = pyqtSignal()
- def __init__(self):
- super().__init__(None)
- self.setupUi(self)
- self.btnAdd.setIcon(QgsApplication.getThemeIcon('/symbologyAdd.svg'))
- self.btnRemove.setIcon(QgsApplication.getThemeIcon('/symbologyRemove.svg'))
- self.btnAdd.clicked.connect(self.addLayer)
- self.btnRemove.clicked.connect(self.removeLayer)
- self.cmbLayers.layerChanged.connect(self.layerChanged)
- self.cmbLayers.setFilters(Qgis.LayerFilter.VectorLayer)
- self.cmbFields.setFilters(QgsFieldProxyModel.Numeric)
- self.cmbFields.setLayer(self.cmbLayers.currentLayer())
- def addLayer(self):
- layer = self.cmbLayers.currentLayer()
- attribute = ''
- if self.chkUseZCoordinate.isChecked():
- attribute = 'Z_COORD'
- else:
- attribute = self.cmbFields.currentField()
- self._addLayerData(layer.name(), attribute)
- self.hasChanged.emit()
- def removeLayer(self):
- item = self.layersTree.currentItem()
- if not item:
- return
- self.layersTree.invisibleRootItem().removeChild(item)
- self.hasChanged.emit()
- def layerChanged(self, layer: Optional[QgsVectorLayer]):
- self.chkUseZCoordinate.setEnabled(False)
- self.chkUseZCoordinate.setChecked(False)
- if layer is None or not layer.isValid():
- return
- provider = layer.dataProvider()
- if not provider:
- return
- if QgsWkbTypes.hasZ(provider.wkbType()):
- self.chkUseZCoordinate.setEnabled(True)
- self.cmbFields.setLayer(layer)
- def _addLayerData(self, layerName: str, attribute: str):
- item = QTreeWidgetItem()
- item.setText(0, layerName)
- item.setText(1, attribute)
- self.layersTree.addTopLevelItem(item)
- comboBox = QComboBox()
- comboBox.addItem(self.tr('Points'))
- comboBox.addItem(self.tr('Structure lines'))
- comboBox.addItem(self.tr('Break lines'))
- comboBox.setCurrentIndex(0)
- self.layersTree.setItemWidget(item, 2, comboBox)
- def setValue(self, value: str):
- self.layersTree.clear()
- rows = value.split('::|::')
- for i, r in enumerate(rows):
- v = r.split('::~::')
- layer = QgsProcessingUtils.mapLayerFromString(v[0], dataobjects.createContext())
- field_index = int(v[2])
- if field_index == -1:
- field_name = 'Z_COORD'
- else:
- field_name = layer.fields().at(field_index).name()
- self._addLayerData(v[0], field_name)
- comboBox = self.layersTree.itemWidget(self.layersTree.topLevelItem(i), 2)
- comboBox.setCurrentIndex(int(v[3]))
- self.hasChanged.emit()
- def value(self) -> str:
- layers = ''
- context = dataobjects.createContext()
- for i in range(self.layersTree.topLevelItemCount()):
- item = self.layersTree.topLevelItem(i)
- if item:
- layerName = item.text(0)
- layer = QgsProcessingUtils.mapLayerFromString(layerName, context)
- if not layer:
- continue
- provider = layer.dataProvider()
- if not provider:
- continue
- interpolationAttribute = item.text(1)
- interpolationSource = QgsInterpolator.ValueAttribute
- if interpolationAttribute == 'Z_COORD':
- interpolationSource = QgsInterpolator.ValueZ
- fieldIndex = -1
- else:
- fieldIndex = layer.fields().indexFromName(interpolationAttribute)
- comboBox = self.layersTree.itemWidget(self.layersTree.topLevelItem(i), 2)
- inputTypeName = comboBox.currentText()
- if inputTypeName == self.tr('Points'):
- inputType = QgsInterpolator.SourcePoints
- elif inputTypeName == self.tr('Structure lines'):
- inputType = QgsInterpolator.SourceStructureLines
- else:
- inputType = QgsInterpolator.SourceBreakLines
- layers += '{}::~::{:d}::~::{:d}::~::{:d}::|::'.format(layer.source(),
- interpolationSource,
- fieldIndex,
- inputType)
- return layers[:-len('::|::')]
- class InterpolationDataWidgetWrapper(WidgetWrapper):
- def createWidget(self):
- widget = InterpolationDataWidget()
- widget.hasChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
- return widget
- def setValue(self, value):
- self.widget.setValue(value)
- def value(self):
- return self.widget.value()
- class ParameterPixelSize(QgsProcessingParameterNumber):
- def __init__(self, name='', description='', layersData=None, extent=None, minValue=None, default=None, optional=False):
- QgsProcessingParameterNumber.__init__(self, name, description, QgsProcessingParameterNumber.Double, default, optional, minValue)
- self.setMetadata({
- 'widget_wrapper': 'processing.algs.qgis.ui.InterpolationWidgets.PixelSizeWidgetWrapper'
- })
- self.layersData = layersData
- self.extent = extent
- self.layers = []
- def clone(self):
- copy = ParameterPixelSize(self.name(), self.description(), self.layersData, self.extent, self.minimum(), self.defaultValue(), self.flags() & QgsProcessingParameterDefinition.FlagOptional)
- return copy
- WIDGET, BASE = uic.loadUiType(os.path.join(pluginPath, 'RasterResolutionWidget.ui'))
- class PixelSizeWidget(BASE, WIDGET):
- def __init__(self):
- super().__init__(None)
- self.setupUi(self)
- self.context = dataobjects.createContext()
- self.extent: Optional[Union[QgsReferencedRectangle, QgsRectangle]] = QgsRectangle()
- self.layers = []
- self.mCellXSpinBox.setShowClearButton(False)
- self.mCellYSpinBox.setShowClearButton(False)
- self.mRowsSpinBox.setShowClearButton(False)
- self.mColumnsSpinBox.setShowClearButton(False)
- self.mCellYSpinBox.valueChanged.connect(self.mCellXSpinBox.setValue)
- self.mCellXSpinBox.valueChanged.connect(self.pixelSizeChanged)
- self.mRowsSpinBox.valueChanged.connect(self.rowsChanged)
- self.mColumnsSpinBox.valueChanged.connect(self.columnsChanged)
- def setLayers(self, layersData: str):
- self.extent = QgsRectangle()
- self.layers = []
- for row in layersData.split(';'):
- v = row.split('::~::')
- # need to keep a reference until interpolation is complete
- layer = QgsProcessingUtils.variantToSource(v[0], self.context)
- if layer:
- self.layers.append(layer)
- self.extent.combineExtentWith(layer.sourceExtent())
- self.pixelSizeChanged()
- def setExtent(self, extent: Optional[str]):
- if extent is not None:
- tokens = extent.split(' ')[0].split(',')
- ext = QgsRectangle(
- float(tokens[0]),
- float(tokens[2]),
- float(tokens[1]),
- float(tokens[3]))
- if len(tokens) > 1:
- self.extent = QgsReferencedRectangle(
- ext, QgsCoordinateReferenceSystem(tokens[1][1:-1]))
- else:
- self.extent = ext
- self.pixelSizeChanged()
- def pixelSizeChanged(self):
- cell_size = self.mCellXSpinBox.value()
- if cell_size <= 0 or self.extent.isNull():
- return
- self.mCellYSpinBox.blockSignals(True)
- self.mCellYSpinBox.setValue(cell_size)
- self.mCellYSpinBox.blockSignals(False)
- rows = max(round(self.extent.height() / cell_size) + 1, 1)
- cols = max(round(self.extent.width() / cell_size) + 1, 1)
- self.mRowsSpinBox.blockSignals(True)
- self.mRowsSpinBox.setValue(rows)
- self.mRowsSpinBox.blockSignals(False)
- self.mColumnsSpinBox.blockSignals(True)
- self.mColumnsSpinBox.setValue(cols)
- self.mColumnsSpinBox.blockSignals(False)
- def rowsChanged(self):
- rows = self.mRowsSpinBox.value()
- if rows <= 0 or self.extent.isNull():
- return
- cell_size = self.extent.height() / rows
- if cell_size == 0:
- return
- cols = max(round(self.extent.width() / cell_size) + 1, 1)
- self.mColumnsSpinBox.blockSignals(True)
- self.mColumnsSpinBox.setValue(cols)
- self.mColumnsSpinBox.blockSignals(False)
- for w in [self.mCellXSpinBox, self.mCellYSpinBox]:
- w.blockSignals(True)
- w.setValue(cell_size)
- w.blockSignals(False)
- def columnsChanged(self):
- cols = self.mColumnsSpinBox.value()
- if cols < 2 or self.extent.isNull():
- return
- cell_size = self.extent.width() / (cols - 1)
- if cell_size == 0:
- return
- rows = max(round(self.extent.height() / cell_size), 1)
- self.mRowsSpinBox.blockSignals(True)
- self.mRowsSpinBox.setValue(rows)
- self.mRowsSpinBox.blockSignals(False)
- for w in [self.mCellXSpinBox, self.mCellYSpinBox]:
- w.blockSignals(True)
- w.setValue(cell_size)
- w.blockSignals(False)
- def setValue(self, value):
- try:
- numeric_value = float(value)
- except:
- return False
- self.mCellXSpinBox.setValue(numeric_value)
- self.mCellYSpinBox.setValue(numeric_value)
- return True
- def value(self):
- return self.mCellXSpinBox.value()
- class PixelSizeWidgetWrapper(WidgetWrapper):
- def __init__(self, param, dialog, row=0, col=0, **kwargs):
- super().__init__(param, dialog, row, col, **kwargs)
- self.context = dataobjects.createContext()
- def _panel(self):
- return PixelSizeWidget()
- def createWidget(self):
- if self.dialogType == DIALOG_STANDARD:
- return self._panel()
- else:
- w = QgsDoubleSpinBox()
- w.setShowClearButton(False)
- w.setMinimum(0)
- w.setMaximum(99999999999)
- w.setDecimals(6)
- w.setToolTip(self.tr('Resolution of each pixel in output raster, in layer units'))
- return w
- def postInitialize(self, wrappers):
- if self.dialogType != DIALOG_STANDARD:
- return
- for wrapper in wrappers:
- if wrapper.parameterDefinition().name() == self.param.layersData:
- self.setLayers(wrapper.parameterValue())
- wrapper.widgetValueHasChanged.connect(self.layersChanged)
- elif wrapper.parameterDefinition().name() == self.param.extent:
- self.setExtent(wrapper.parameterValue())
- wrapper.widgetValueHasChanged.connect(self.extentChanged)
- def layersChanged(self, wrapper):
- self.setLayers(wrapper.parameterValue())
- def setLayers(self, layersData: str):
- self.widget.setLayers(layersData)
- def extentChanged(self, wrapper):
- self.setExtent(wrapper.parameterValue())
- def setExtent(self, extent: Optional[str]):
- self.widget.setExtent(extent)
- def setValue(self, value):
- return self.widget.setValue(value)
- def value(self):
- return self.widget.value()
|