Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[processing] evaluate parameters before executing algorithm
This allows a better use of expressions
  • Loading branch information
volaya committed Oct 5, 2016
1 parent 01f3808 commit e353d22
Show file tree
Hide file tree
Showing 8 changed files with 264 additions and 422 deletions.
68 changes: 9 additions & 59 deletions python/plugins/processing/core/GeoAlgorithm.py
Expand Up @@ -199,15 +199,15 @@ def execute(self, progress=SilentProgress(), model=None):
self.setOutputCRS()
self.resolveTemporaryOutputs()
self.resolveDataObjects()
self.resolveMinCoveringExtent()
self.evaluateParameterValues()
self.checkOutputFileExtensions()
self.runPreExecutionScript(progress)
self.processAlgorithm(progress)
progress.setPercentage(100)
self.convertUnsupportedFormats(progress)
self.runPostExecutionScript(progress)
except GeoAlgorithmExecutionException as gaee:
lines = [self.tr('Uncaught error while executing algorithm')]
lines = [self.tr('Error while executing algorithm')]
lines.append(traceback.format_exc())
ProcessingLog.addToLog(ProcessingLog.LOG_ERROR, gaee.msg)
raise GeoAlgorithmExecutionException(gaee.msg, lines, gaee)
Expand Down Expand Up @@ -340,11 +340,9 @@ def checkOutputFileExtensions(self):
if not os.path.isabs(out.value):
continue
if isinstance(out, OutputRaster):
exts = \
dataobjects.getSupportedOutputRasterLayerExtensions()
exts = dataobjects.getSupportedOutputRasterLayerExtensions()
elif isinstance(out, OutputVector):
exts = \
dataobjects.getSupportedOutputVectorLayerExtensions()
exts = dataobjects.getSupportedOutputVectorLayerExtensions()
elif isinstance(out, OutputTable):
exts = dataobjects.getSupportedOutputTableExtensions()
elif isinstance(out, OutputHTML):
Expand All @@ -360,60 +358,12 @@ def checkOutputFileExtensions(self):
out.value = out.value + '.' + exts[0]


def canUseAutoExtent(self):
for param in self.parameters:
if isinstance(param, (ParameterRaster, ParameterVector)):
return True
if isinstance(param, ParameterMultipleInput):
return True
return False

def resolveMinCoveringExtent(self):
for param in self.parameters:
if isinstance(param, ParameterExtent):
if param.value is None:
param.value = self.getMinCoveringExtent()

def getMinCoveringExtent(self):
first = True
found = False
def evaluateParameterValues(self):
for param in self.parameters:
if param.value:
if isinstance(param, (ParameterRaster, ParameterVector)):
if isinstance(param.value, (QgsRasterLayer,
QgsVectorLayer)):
layer = param.value
else:
layer = dataobjects.getObject(param.value)
if layer:
found = True
self.addToRegion(layer, first)
first = False
elif isinstance(param, ParameterMultipleInput):
layers = param.value.split(';')
for layername in layers:
layer = dataobjects.getObject(layername)
if layer:
found = True
self.addToRegion(layer, first)
first = False
if found:
return '{},{},{},{}'.format(
self.xmin, self.xmax, self.ymin, self.ymax)
else:
return None

def addToRegion(self, layer, first):
if first:
self.xmin = layer.extent().xMinimum()
self.xmax = layer.extent().xMaximum()
self.ymin = layer.extent().yMinimum()
self.ymax = layer.extent().yMaximum()
else:
self.xmin = min(self.xmin, layer.extent().xMinimum())
self.xmax = max(self.xmax, layer.extent().xMaximum())
self.ymin = min(self.ymin, layer.extent().yMinimum())
self.ymax = max(self.ymax, layer.extent().yMaximum())
try:
param.evaluate(self)
except ValueError, e:
raise GeoAlgorithmExecutionException(str(e))

def resolveTemporaryOutputs(self):
"""Sets temporary outputs (output.value = None) with a
Expand Down
180 changes: 161 additions & 19 deletions python/plugins/processing/core/parameters.py
Expand Up @@ -33,11 +33,12 @@
from inspect import isclass
from copy import deepcopy


from qgis.utils import iface
from qgis.PyQt.QtCore import QCoreApplication
from qgis.core import QgsRasterLayer, QgsVectorLayer, QgsMapLayer, QgsCoordinateReferenceSystem
from qgis.core import (QgsRasterLayer, QgsVectorLayer, QgsMapLayer, QgsCoordinateReferenceSystem,
QgsExpressionContext, QgsExpressionContextUtils, QgsExpression, QgsExpressionContextScope)

from processing.tools.vector import resolveFieldIndex, features
from processing.tools.system import isWindows
from processing.tools import dataobjects

def parseBool(s):
Expand All @@ -58,7 +59,47 @@ def _splitParameterOptions(line):
def _createDescriptiveName(s):
return s.replace('_', ' ')

class Parameter(object):
def _expressionContext():
context = QgsExpressionContext()
context.appendScope(QgsExpressionContextUtils.globalScope())
context.appendScope(QgsExpressionContextUtils.projectScope())
processingScope = QgsExpressionContextScope()
layers = dataobjects.getAllLayers()
for layer in layers:
name = layer.name()
processingScope.setVariable('%s_minx' % name, layer.extent().xMinimum())
processingScope.setVariable('%s_miny' % name, layer.extent().yMinimum())
processingScope.setVariable('%s_maxx' % name, layer.extent().xMaximum())
processingScope.setVariable('%s_maxy' % name, layer.extent().yMaximum())
if isinstance(layer, QgsRasterLayer):
cellsize = (layer.extent().xMaximum()
- layer.extent().xMinimum()) / layer.width()
processingScope.setVariable('%s_cellsize' % name, cellsize)

layers = dataobjects.getRasterLayers()
for layer in layers:
for i in range(layer.bandCount()):
stats = layer.dataProvider().bandStatistics(i + 1)
processingScope.setVariable('%s_band%i_avg' % (name, i + 1), stats.mean)
processingScope.setVariable('%s_band%i_stddev' % (name, i + 1), stats.stdDev)
processingScope.setVariable('%s_band%i_min' % (name, i + 1), stats.minimumValue)
processingScope.setVariable('%s_band%i_max' % (name, i + 1), stats.maximumValue)

extent = iface.mapCanvas().extent()
processingScope.setVariable('canvasextent_minx', extent.xMinimum())
processingScope.setVariable('canvasextent_miny', extent.yMinimum())
processingScope.setVariable('canvasextent_maxx', extent.xMaximum())
processingScope.setVariable('canvasextent_maxy', extent.yMaximum())

extent = iface.mapCanvas().fullExtent()
processingScope.setVariable('fullextent_minx', extent.xMinimum())
processingScope.setVariable('fullextent_miny', extent.yMinimum())
processingScope.setVariable('fullextent_maxx', extent.xMaximum())
processingScope.setVariable('fullextent_maxy', extent.yMaximum())
context.appendScope(processingScope)
return context

class Parameter:

"""
Base class for all parameters that a geoalgorithm might
Expand Down Expand Up @@ -148,6 +189,9 @@ def wrapper(self, dialog, row=0, col=0):
wrapper = wrapper(self, dialog, row, col)
# or a wrapper instance
return wrapper

def evaluate(self, alg):
pass

class ParameterBoolean(Parameter):

Expand Down Expand Up @@ -330,6 +374,51 @@ def fromScriptCode(self, line):
default = definition.strip()[len('extent') + 1:] or None
return ParameterExtent(name, descName, default, isOptional)

def evaluate(self, alg):
if self.optional and not bool(self.value):
self.value = self.getMinCoveringExtent()

def getMinCoveringExtent(self, alg):
first = True
found = False
for param in alg.parameters:
if param.value:
if isinstance(param, (ParameterRaster, ParameterVector)):
if isinstance(param.value, (QgsRasterLayer,
QgsVectorLayer)):
layer = param.value
else:
layer = dataobjects.getObject(param.value)
if layer:
found = True
self.addToRegion(layer, first)
first = False
elif isinstance(param, ParameterMultipleInput):
layers = param.value.split(';')
for layername in layers:
layer = dataobjects.getObject(layername)
if layer:
found = True
self.addToRegion(layer, first)
first = False
if found:
return '{},{},{},{}'.format(
self.xmin, self.xmax, self.ymin, self.ymax)
else:
return None

def addToRegion(self, layer, first):
if first:
self.xmin = layer.extent().xMinimum()
self.xmax = layer.extent().xMaximum()
self.ymin = layer.extent().yMinimum()
self.ymax = layer.extent().yMaximum()
else:
self.xmin = min(self.xmin, layer.extent().xMinimum())
self.xmax = max(self.xmax, layer.extent().xMaximum())
self.ymin = min(self.ymin, layer.extent().yMinimum())
self.ymax = max(self.ymax, layer.extent().yMaximum())


class ParameterPoint(Parameter):

Expand Down Expand Up @@ -715,21 +804,30 @@ def setValue(self, n):
self.value = None
return True

try:
if float(n) - int(float(n)) == 0:
value = int(float(n))
else:
value = float(n)
if self.min is not None:
if value < self.min:
return False
if self.max is not None:
if value > self.max:
return False
self.value = value
return True
except:
return False
if isinstance(n, basestring):
try:
v = self._evaluate(n)
float(v)
self.value = n
return True
except:
return False
else:
try:
if float(n) - int(float(n)) == 0:
value = int(float(n))
else:
value = float(n)
if self.min is not None:
if value < self.min:
return False
if self.max is not None:
if value > self.max:
return False
self.value = value
return True
except:
return False

def getAsScriptCode(self):
param_type = ''
Expand All @@ -745,6 +843,31 @@ def fromScriptCode(self, line):
if definition.lower().strip().startswith('number'):
default = definition.strip()[len('number') + 1:] or None
return ParameterNumber(name, descName, default=default, optional=isOptional)

def _evaluate(self, v):
exp = QgsExpression(v)
if exp.hasParserError():
raise ValueError(self.tr("Error in parameter expression: ") + exp.parserErrorString())
result = exp.evaluate(_expressionContext())
if exp.hasEvalError():
raise ValueError("Error evaluating parameter expression: " + exp.evalErrorString())
return result

def evaluate(self, alg):
if isinstance(self.value, basestring):
self.value = self._evaluate(self.value)

def expressionContext(self):
return _expressionContext()

def getValueAsCommandLineParameter(self):
if self.value is None:
return str(None)
if isinstance(self.value, basestring):
return '"%s"' % self.value
return str(self.value)



class ParameterRange(Parameter):

Expand Down Expand Up @@ -919,6 +1042,13 @@ def fromScriptCode(self, line):
return ParameterSelection(name, descName, options, optional=isOptional)


class ParameterEvaluationException(Exception):

def __init__(self, param, msg):
Exception.__init__(msg)
self.param = param


class ParameterString(Parameter):

default_metadata = {
Expand Down Expand Up @@ -976,6 +1106,18 @@ def fromScriptCode(self, line):
return ParameterString(name, descName, default, multiline=True, optional=isOptional)
else:
return ParameterString(name, descName, multiline=True, optional=isOptional)

def evaluate(self, alg):
exp = QgsExpression(self.value)
if exp.hasParserError():
raise ValueError(self.tr("Error in parameter expression: ") + exp.parserErrorString())
result = exp.evaluate(_expressionContext())
if exp.hasEvalError():
raise ValueError("Error evaluating parameter expression: " + exp.evalErrorString())
self.value = result

def expressionContext(self):
return _expressionContext()

class ParameterTable(ParameterDataObject):

Expand Down
16 changes: 1 addition & 15 deletions python/plugins/processing/gui/AlgorithmDialog.py
Expand Up @@ -31,7 +31,7 @@
from qgis.PyQt.QtWidgets import QMessageBox, QApplication, QPushButton, QWidget, QVBoxLayout
from qgis.PyQt.QtGui import QCursor, QColor, QPalette

from qgis.core import QgsMapLayerRegistry, QgsExpressionContext, QgsExpressionContextUtils, QgsExpression
from qgis.core import QgsMapLayerRegistry

from processing.core.ProcessingLog import ProcessingLog
from processing.core.ProcessingConfig import ProcessingConfig
Expand Down Expand Up @@ -62,8 +62,6 @@
from processing.core.outputs import OutputVector
from processing.core.outputs import OutputTable

from processing.tools import dataobjects


class AlgorithmDialog(AlgorithmDialogBase):

Expand Down Expand Up @@ -114,18 +112,6 @@ def setParamValues(self):

return True

def evaluateExpression(self, text):
context = QgsExpressionContext()
context.appendScope(QgsExpressionContextUtils.globalScope())
context.appendScope(QgsExpressionContextUtils.projectScope())
exp = QgsExpression(text)
if exp.hasParserError():
raise Exception(exp.parserErrorString())
result = exp.evaluate(context)
if exp.hasEvalError():
raise ValueError(exp.evalErrorString())
return result

def setParamValue(self, param, wrapper, alg=None):
return param.setValue(wrapper.value())

Expand Down

0 comments on commit e353d22

Please sign in to comment.