Skip to content

Commit

Permalink
add cell size option to IDW and TIN interpolation algorithms
Browse files Browse the repository at this point in the history
  • Loading branch information
alexbruy committed Dec 30, 2018
1 parent 357cf27 commit 5813b96
Show file tree
Hide file tree
Showing 3 changed files with 214 additions and 26 deletions.
25 changes: 14 additions & 11 deletions python/plugins/processing/algs/qgis/IdwInterpolation.py
Expand Up @@ -40,7 +40,7 @@
QgsGridFileWriter)

from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
from processing.algs.qgis.ui.InterpolationWidgets import ParameterInterpolationData
from processing.algs.qgis.ui.InterpolationWidgets import ParameterInterpolationData, ParameterPixelSize

pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]

Expand All @@ -49,8 +49,7 @@ class IdwInterpolation(QgisAlgorithm):

INTERPOLATION_DATA = 'INTERPOLATION_DATA'
DISTANCE_COEFFICIENT = 'DISTANCE_COEFFICIENT'
COLUMNS = 'COLUMNS'
ROWS = 'ROWS'
PIXEL_SIZE = 'PIXEL_SIZE'
EXTENT = 'EXTENT'
OUTPUT = 'OUTPUT'

Expand All @@ -73,15 +72,17 @@ def initAlgorithm(self, config=None):
self.addParameter(QgsProcessingParameterNumber(self.DISTANCE_COEFFICIENT,
self.tr('Distance coefficient P'), type=QgsProcessingParameterNumber.Double,
minValue=0.0, maxValue=99.99, defaultValue=2.0))
self.addParameter(QgsProcessingParameterNumber(self.COLUMNS,
self.tr('Number of columns'),
minValue=0, maxValue=10000000, defaultValue=300))
self.addParameter(QgsProcessingParameterNumber(self.ROWS,
self.tr('Number of rows'),
minValue=0, maxValue=10000000, defaultValue=300))
self.addParameter(QgsProcessingParameterExtent(self.EXTENT,
self.tr('Extent'),
optional=False))
pixel_size_param = ParameterPixelSize(self.PIXEL_SIZE,
self.tr('Output raster size'),
layersData=self.INTERPOLATION_DATA,
extent=self.EXTENT,
minValue=0.0,
default=0.1)
self.addParameter(pixel_size_param)

self.addParameter(QgsProcessingParameterRasterDestination(self.OUTPUT,
self.tr('Interpolated')))

Expand All @@ -94,9 +95,8 @@ def displayName(self):
def processAlgorithm(self, parameters, context, feedback):
interpolationData = ParameterInterpolationData.parseValue(parameters[self.INTERPOLATION_DATA])
coefficient = self.parameterAsDouble(parameters, self.DISTANCE_COEFFICIENT, context)
columns = self.parameterAsInt(parameters, self.COLUMNS, context)
rows = self.parameterAsInt(parameters, self.ROWS, context)
bbox = self.parameterAsExtent(parameters, self.EXTENT, context)
pixel_size = self.parameterAsDouble(parameters, self.PIXEL_SIZE, context)
output = self.parameterAsOutputLayer(parameters, self.OUTPUT, context)

if interpolationData is None:
Expand Down Expand Up @@ -127,6 +127,9 @@ def processAlgorithm(self, parameters, context, feedback):
interpolator = QgsIDWInterpolator(layerData)
interpolator.setDistanceCoefficient(coefficient)

rows = max(round(bbox.height() / pixel_size) + 1, 1)
columns = max(round(bbox.width() / pixel_size) + 1, 1)

writer = QgsGridFileWriter(interpolator,
output,
bbox,
Expand Down
25 changes: 14 additions & 11 deletions python/plugins/processing/algs/qgis/TinInterpolation.py
Expand Up @@ -44,16 +44,15 @@
QgsGridFileWriter)

from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
from processing.algs.qgis.ui.InterpolationWidgets import ParameterInterpolationData
from processing.algs.qgis.ui.InterpolationWidgets import ParameterInterpolationData, ParameterPixelSize

pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]


class TinInterpolation(QgisAlgorithm):
INTERPOLATION_DATA = 'INTERPOLATION_DATA'
METHOD = 'METHOD'
COLUMNS = 'COLUMNS'
ROWS = 'ROWS'
PIXEL_SIZE = 'PIXEL_SIZE'
EXTENT = 'EXTENT'
OUTPUT = 'OUTPUT'
TRIANGULATION = 'TRIANGULATION'
Expand Down Expand Up @@ -81,15 +80,17 @@ def initAlgorithm(self, config=None):
self.tr('Interpolation method'),
options=self.METHODS,
defaultValue=0))
self.addParameter(QgsProcessingParameterNumber(self.COLUMNS,
self.tr('Number of columns'),
minValue=0, maxValue=10000000, defaultValue=300))
self.addParameter(QgsProcessingParameterNumber(self.ROWS,
self.tr('Number of rows'),
minValue=0, maxValue=10000000, defaultValue=300))
self.addParameter(QgsProcessingParameterExtent(self.EXTENT,
self.tr('Extent'),
optional=False))
pixel_size_param = ParameterPixelSize(self.PIXEL_SIZE,
self.tr('Output raster size'),
layersData=self.INTERPOLATION_DATA,
extent=self.EXTENT,
minValue=0.0,
default=0.1)
self.addParameter(pixel_size_param)

self.addParameter(QgsProcessingParameterRasterDestination(self.OUTPUT,
self.tr('Interpolated')))

Expand All @@ -109,9 +110,8 @@ def displayName(self):
def processAlgorithm(self, parameters, context, feedback):
interpolationData = ParameterInterpolationData.parseValue(parameters[self.INTERPOLATION_DATA])
method = self.parameterAsEnum(parameters, self.METHOD, context)
columns = self.parameterAsInt(parameters, self.COLUMNS, context)
rows = self.parameterAsInt(parameters, self.ROWS, context)
bbox = self.parameterAsExtent(parameters, self.EXTENT, context)
pixel_size = self.parameterAsDouble(parameters, self.PIXEL_SIZE, context)
output = self.parameterAsOutputLayer(parameters, self.OUTPUT, context)

if interpolationData is None:
Expand Down Expand Up @@ -154,6 +154,9 @@ def processAlgorithm(self, parameters, context, feedback):
if triangulation_sink is not None:
interpolator.setTriangulationSink(triangulation_sink)

rows = max(round(bbox.height() / pixel_size) + 1, 1)
columns = max(round(bbox.width() / pixel_size) + 1, 1)

writer = QgsGridFileWriter(interpolator,
output,
bbox,
Expand Down
190 changes: 186 additions & 4 deletions python/plugins/processing/algs/qgis/ui/InterpolationWidgets.py
Expand Up @@ -28,21 +28,25 @@
import os

from qgis.PyQt import uic
from qgis.PyQt.QtCore import pyqtSlot
from qgis.PyQt.QtCore import pyqtSignal
from qgis.PyQt.QtWidgets import (QTreeWidgetItem,
QComboBox
)
from qgis.core import (QgsApplication,
QgsMapLayer,
QgsMapLayerProxyModel,
QgsWkbTypes,
QgsRectangle,
QgsReferencedRectangle,
QgsCoordinateReferenceSystem,
QgsProcessingUtils,
QgsProcessingParameterNumber,
QgsProcessingParameterDefinition
)
from qgis.core import QgsFieldProxyModel
from qgis.analysis import QgsInterpolator

from processing.gui.wrappers import WidgetWrapper
from processing.gui.wrappers import WidgetWrapper, DIALOG_STANDARD
from processing.tools import dataobjects

pluginPath = os.path.dirname(__file__)
Expand All @@ -53,7 +57,7 @@ class ParameterInterpolationData(QgsProcessingParameterDefinition):
def __init__(self, name='', description=''):
super().__init__(name, description)
self.setMetadata({
'widget_wrapper': 'processing.algs.qgis.ui.InterpolationDataWidget.InterpolationDataWidgetWrapper'
'widget_wrapper': 'processing.algs.qgis.ui.InterpolationWidgets.InterpolationDataWidgetWrapper'
})

def type(self):
Expand Down Expand Up @@ -91,7 +95,7 @@ def dataToString(data):

class InterpolationDataWidget(BASE, WIDGET):

hasChanged = pyqtSlot()
hasChanged = pyqtSignal()

def __init__(self):
super(InterpolationDataWidget, self).__init__(None)
Expand Down Expand Up @@ -218,3 +222,181 @@ def setValue(self, 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(PixelSizeWidget, self).__init__(None)
self.setupUi(self)
self.context = dataobjects.createContext()

self.extent = 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):
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)
bbox = layer.sourceExtent()
if self.extent.isEmpty():
self.extent = bbox
else:
self.extent.combineExtentWith(bbox)

self.pixelSizeChanged()

def setExtent(self, extent):
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:
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:
return
cell_size = self.extent.height() / rows
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:
return
cell_size = self.extent.width() / (cols - 1)
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):
self.widget.setLayers(layersData)

def extentChanged(self, wrapper):
self.setExtent(wrapper.parameterValue())

def setExtent(self, extent):
self.widget.setExtent(extent)

def setValue(self, value):
return self.widget.setValue(value)

def value(self):
return self.widget.value()

0 comments on commit 5813b96

Please sign in to comment.