Skip to content

Commit 132e76a

Browse files
committedNov 11, 2016
[FEATURE][processing] New input type for expressions
This adds a new input type for expression inputs. Expression inputs can be linked to a parent layer so that the builder shows the correct fields and layer variables. It's designed for two use cases: 1. to be used when an algorithm specifically requires an expression, eg Select by Expression and Extract by Expression. 2. to be potentially used as a replacement input instead of string or number literals in algorithms. Eg - if the simplify algorithm tolerance parameter was replaced with an expression paremeter, then this expression would be evaluated for every feature before simplifying that feature. It would allow parameters to be calculated per feature, as opposed to the current approach of calculating a parameter once before running the algorithm. It would also mean algorithms like "variable distance buffer" would no longer be needed, as a single "buffer" algorithm could then be used for either a fixed distance, field based, or expression based distance.
1 parent 72118f9 commit 132e76a

File tree

6 files changed

+151
-13
lines changed

6 files changed

+151
-13
lines changed
 

‎python/plugins/processing/algs/qgis/ExtractByExpression.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
from processing.core.parameters import ParameterVector
3131
from processing.core.outputs import OutputVector
3232
from processing.core.GeoAlgorithm import GeoAlgorithm
33-
from processing.core.parameters import ParameterString
33+
from processing.core.parameters import ParameterString, ParameterExpression
3434
from processing.tools import dataobjects
3535

3636

@@ -46,8 +46,8 @@ def defineCharacteristics(self):
4646

4747
self.addParameter(ParameterVector(self.INPUT,
4848
self.tr('Input Layer')))
49-
self.addParameter(ParameterString(self.EXPRESSION,
50-
self.tr("Expression")))
49+
self.addParameter(ParameterExpression(self.EXPRESSION,
50+
self.tr("Expression"), parent_layer=self.INPUT))
5151
self.addOutput(OutputVector(self.OUTPUT, self.tr('Extracted (expression)')))
5252

5353
def processAlgorithm(self, progress):

‎python/plugins/processing/algs/qgis/GeometryByExpression.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
from processing.core.GeoAlgorithm import GeoAlgorithm
3131
from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
3232
from processing.core.ProcessingLog import ProcessingLog
33-
from processing.core.parameters import ParameterVector, ParameterSelection, ParameterBoolean, ParameterString
33+
from processing.core.parameters import ParameterVector, ParameterSelection, ParameterBoolean, ParameterExpression
3434
from processing.core.outputs import OutputVector
3535
from processing.tools import dataobjects, vector
3636

@@ -63,8 +63,8 @@ def defineCharacteristics(self):
6363
self.addParameter(ParameterBoolean(self.WITH_M,
6464
self.tr('Output geometry has m values'), False))
6565

66-
self.addParameter(ParameterString(self.EXPRESSION,
67-
self.tr("Geometry expression"), '$geometry'))
66+
self.addParameter(ParameterExpression(self.EXPRESSION,
67+
self.tr("Geometry expression"), '$geometry', parent_layer=self.INPUT_LAYER))
6868

6969
self.addOutput(OutputVector(self.OUTPUT_LAYER, self.tr('Modified geometry')))
7070

‎python/plugins/processing/algs/qgis/SelectByExpression.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
from processing.core.parameters import ParameterSelection
3131
from processing.core.outputs import OutputVector
3232
from processing.core.GeoAlgorithm import GeoAlgorithm
33-
from processing.core.parameters import ParameterString
33+
from processing.core.parameters import ParameterExpression
3434
from processing.tools import dataobjects
3535

3636

@@ -52,8 +52,8 @@ def defineCharacteristics(self):
5252

5353
self.addParameter(ParameterVector(self.LAYERNAME,
5454
self.tr('Input Layer')))
55-
self.addParameter(ParameterString(self.EXPRESSION,
56-
self.tr("Expression")))
55+
self.addParameter(ParameterExpression(self.EXPRESSION,
56+
self.tr("Expression"), parent_layer=self.LAYERNAME))
5757
self.addParameter(ParameterSelection(self.METHOD,
5858
self.tr('Modify current selection by'), self.methods, 0))
5959
self.addOutput(OutputVector(self.RESULT, self.tr('Selected (expression)'), True))

‎python/plugins/processing/core/parameters.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1210,6 +1210,55 @@ def expressionContext(self):
12101210
return _expressionContext()
12111211

12121212

1213+
class ParameterExpression(Parameter):
1214+
1215+
default_metadata = {
1216+
'widget_wrapper': 'processing.gui.wrappers.ExpressionWidgetWrapper'
1217+
}
1218+
1219+
NEWLINE = '\n'
1220+
ESCAPED_NEWLINE = '\\n'
1221+
1222+
def __init__(self, name='', description='', default=None, optional=False, parent_layer=None):
1223+
Parameter.__init__(self, name, description, default, optional)
1224+
self.parent_layer = parent_layer
1225+
1226+
def setValue(self, obj):
1227+
if not bool(obj):
1228+
if not self.optional:
1229+
return False
1230+
self.value = None
1231+
return True
1232+
1233+
self.value = str(obj).replace(
1234+
ParameterString.ESCAPED_NEWLINE,
1235+
ParameterString.NEWLINE
1236+
)
1237+
return True
1238+
1239+
def getValueAsCommandLineParameter(self):
1240+
return ('"' + str(self.value.replace(ParameterExpression.NEWLINE,
1241+
ParameterExpression.ESCAPED_NEWLINE)) + '"'
1242+
if self.value is not None else str(None))
1243+
1244+
def getAsScriptCode(self):
1245+
param_type = ''
1246+
if self.optional:
1247+
param_type += 'optional '
1248+
param_type += 'expression'
1249+
return '##' + self.name + '=' + param_type + self.default
1250+
1251+
@classmethod
1252+
def fromScriptCode(self, line):
1253+
isOptional, name, definition = _splitParameterOptions(line)
1254+
descName = _createDescriptiveName(name)
1255+
default = definition.strip()[len('expression') + 1:]
1256+
if default:
1257+
return ParameterExpression(name, descName, default, optional=isOptional)
1258+
else:
1259+
return ParameterExpression(name, descName, optional=isOptional)
1260+
1261+
12131262
class ParameterTable(ParameterDataObject):
12141263

12151264
default_metadata = {

‎python/plugins/processing/gui/wrappers.py

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,28 @@
3636

3737
from qgis.core import QgsCoordinateReferenceSystem, QgsVectorLayer
3838
from qgis.PyQt.QtWidgets import QCheckBox, QComboBox, QLineEdit, QPlainTextEdit
39+
from qgis.gui import QgsFieldExpressionWidget
3940
from qgis.PyQt.QtCore import pyqtSignal, QObject, QVariant
4041

4142
from processing.gui.NumberInputPanel import NumberInputPanel
4243
from processing.gui.InputLayerSelectorPanel import InputLayerSelectorPanel
4344
from processing.modeler.MultilineTextPanel import MultilineTextPanel
4445
from processing.gui.CrsSelectionPanel import CrsSelectionPanel
4546
from processing.gui.PointSelectionPanel import PointSelectionPanel
46-
from processing.core.parameters import (ParameterBoolean, ParameterPoint, ParameterFile,
47-
ParameterRaster, ParameterVector, ParameterNumber, ParameterString, ParameterTable,
48-
ParameterTableField, ParameterExtent, ParameterFixedTable, ParameterCrs, _resolveLayers)
47+
from processing.core.parameters import (ParameterBoolean,
48+
ParameterPoint,
49+
ParameterFile,
50+
ParameterRaster,
51+
ParameterVector,
52+
ParameterNumber,
53+
ParameterString,
54+
ParameterExpression,
55+
ParameterTable,
56+
ParameterTableField,
57+
ParameterExtent,
58+
ParameterFixedTable,
59+
ParameterCrs,
60+
_resolveLayers)
4961
from processing.core.ProcessingConfig import ProcessingConfig
5062
from processing.gui.FileSelectionPanel import FileSelectionPanel
5163
from processing.core.outputs import (OutputFile, OutputRaster, OutputVector, OutputNumber,
@@ -645,7 +657,7 @@ def createWidget(self):
645657
else:
646658
# strings, numbers, files and table fields are all allowed input types
647659
strings = self.dialog.getAvailableValuesOfType([ParameterString, ParameterNumber, ParameterFile,
648-
ParameterTableField], OutputString)
660+
ParameterTableField, ParameterExpression], OutputString)
649661
options = [(self.dialog.resolveValueDescription(s), s) for s in strings]
650662
if self.param.multiline:
651663
widget = MultilineTextPanel(options)
@@ -698,6 +710,54 @@ def validator(v):
698710
return self.comboValue(validator)
699711

700712

713+
class ExpressionWidgetWrapper(WidgetWrapper):
714+
715+
def createWidget(self):
716+
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
717+
widget = QgsFieldExpressionWidget()
718+
if self.param.default:
719+
widget.setExpression(self.param.default)
720+
else:
721+
strings = self.dialog.getAvailableValuesOfType([ParameterExpression, ParameterString, ParameterNumber], OutputString)
722+
options = [(self.dialog.resolveValueDescription(s), s) for s in strings]
723+
widget = QComboBox()
724+
widget.setEditable(True)
725+
for desc, val in options:
726+
widget.addItem(desc, val)
727+
widget.setEditText(self.param.default or "")
728+
return widget
729+
730+
def postInitialize(self, wrappers):
731+
for wrapper in wrappers:
732+
if wrapper.param.name == self.param.parent_layer:
733+
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
734+
self.setLayer(wrapper.value())
735+
wrapper.widgetValueHasChanged.connect(self.parentLayerChanged)
736+
break
737+
738+
def parentLayerChanged(self, wrapper):
739+
self.setLayer(wrapper.value())
740+
741+
def setLayer(self, layer):
742+
if isinstance(layer, str):
743+
layer = dataobjects.getObjectFromUri(_resolveLayers(layer))
744+
self.widget.setLayer(layer)
745+
746+
def setValue(self, value):
747+
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
748+
self.widget.setExpression(value)
749+
else:
750+
self.setComboValue(value)
751+
752+
def value(self):
753+
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
754+
return self.widget.asExpression()
755+
else:
756+
def validator(v):
757+
return bool(v) or self.param.optional
758+
return self.comboValue(validator)
759+
760+
701761
class TableWidgetWrapper(WidgetWrapper):
702762

703763
NOT_SELECTED = '[Not selected]'

‎python/plugins/processing/modeler/ModelerParameterDefinitionDialog.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
import math
3030

31+
from qgis.gui import QgsExpressionLineEdit
3132
from qgis.PyQt.QtCore import Qt
3233
from qgis.PyQt.QtWidgets import (QDialog,
3334
QVBoxLayout,
@@ -47,6 +48,7 @@
4748
ParameterMultipleInput,
4849
ParameterNumber,
4950
ParameterString,
51+
ParameterExpression,
5052
ParameterTableField,
5153
ParameterExtent,
5254
ParameterFile,
@@ -62,6 +64,7 @@ class ModelerParameterDefinitionDialog(QDialog):
6264
PARAMETER_TABLE = 'Table'
6365
PARAMETER_VECTOR = 'Vector layer'
6466
PARAMETER_STRING = 'String'
67+
PARAMETER_EXPRESSION = 'Expression'
6568
PARAMETER_BOOLEAN = 'Boolean'
6669
PARAMETER_TABLE_FIELD = 'Table field'
6770
PARAMETER_EXTENT = 'Extent'
@@ -77,6 +80,7 @@ class ModelerParameterDefinitionDialog(QDialog):
7780
PARAMETER_NUMBER,
7881
PARAMETER_RASTER,
7982
PARAMETER_STRING,
83+
PARAMETER_EXPRESSION,
8084
PARAMETER_TABLE,
8185
PARAMETER_TABLE_FIELD,
8286
PARAMETER_VECTOR,
@@ -196,6 +200,25 @@ def setupUi(self):
196200
if default:
197201
self.defaultTextBox.setText(str(default))
198202
self.verticalLayout.addWidget(self.defaultTextBox)
203+
elif (self.paramType == ModelerParameterDefinitionDialog.PARAMETER_EXPRESSION or
204+
isinstance(self.param, ParameterExpression)):
205+
self.verticalLayout.addWidget(QLabel(self.tr('Default value')))
206+
self.defaultEdit = QgsExpressionLineEdit()
207+
if self.param is not None:
208+
self.defaultEdit.setExpression(self.param.default)
209+
self.verticalLayout.addWidget(self.defaultEdit)
210+
211+
self.verticalLayout.addWidget(QLabel(self.tr('Parent layer')))
212+
self.parentCombo = QComboBox()
213+
idx = 0
214+
for param in list(self.alg.inputs.values()):
215+
if isinstance(param.param, (ParameterVector, ParameterTable)):
216+
self.parentCombo.addItem(param.param.description, param.param.name)
217+
if self.param is not None:
218+
if self.param.parent_layer == param.param.name:
219+
self.parentCombo.setCurrentIndex(idx)
220+
idx += 1
221+
self.verticalLayout.addWidget(self.parentCombo)
199222
elif (self.paramType == ModelerParameterDefinitionDialog.PARAMETER_STRING or
200223
isinstance(self.param, ParameterString)):
201224
self.verticalLayout.addWidget(QLabel(self.tr('Default value')))
@@ -314,6 +337,12 @@ def okPressed(self):
314337
QMessageBox.warning(self, self.tr('Unable to define parameter'),
315338
self.tr('Wrong or missing parameter values'))
316339
return
340+
elif (self.paramType == ModelerParameterDefinitionDialog.PARAMETER_EXPRESSION or
341+
isinstance(self.param, ParameterExpression)):
342+
parent = self.parentCombo.itemData(self.parentCombo.currentIndex())
343+
self.param = ParameterExpression(name, description,
344+
default=str(self.defaultEdit.expression()),
345+
parent_layer=parent)
317346
elif (self.paramType == ModelerParameterDefinitionDialog.PARAMETER_STRING or
318347
isinstance(self.param, ParameterString)):
319348
self.param = ParameterString(name, description,

0 commit comments

Comments
 (0)
Please sign in to comment.