Skip to content

Commit d8260b8

Browse files
committedJun 11, 2017
[FEATURE] Port Extract by Expression to new API, allow saving
non matching features to separate output
1 parent a6a3027 commit d8260b8

File tree

4 files changed

+75
-30
lines changed

4 files changed

+75
-30
lines changed
 

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

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,12 @@
2727
from qgis.core import (QgsExpression,
2828
QgsFeatureRequest,
2929
QgsApplication,
30-
QgsProcessingUtils)
30+
QgsProcessingUtils,
31+
QgsProcessingParameterFeatureSource,
32+
QgsProcessingParameterExpression,
33+
QgsProcessingParameterFeatureSink,
34+
QgsProcessingOutputVectorLayer,
35+
QgsProcessingParameterDefinition)
3136

3237
from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
3338
from processing.core.parameters import ParameterVector
@@ -41,6 +46,7 @@ class ExtractByExpression(QgisAlgorithm):
4146
INPUT = 'INPUT'
4247
EXPRESSION = 'EXPRESSION'
4348
OUTPUT = 'OUTPUT'
49+
FAIL_OUTPUT = 'FAIL_OUTPUT'
4450

4551
def icon(self):
4652
return QgsApplication.getThemeIcon("/providerQgis.svg")
@@ -56,11 +62,16 @@ def group(self):
5662

5763
def __init__(self):
5864
super().__init__()
59-
self.addParameter(ParameterVector(self.INPUT,
60-
self.tr('Input Layer')))
61-
self.addParameter(ParameterExpression(self.EXPRESSION,
62-
self.tr("Expression"), parent_layer=self.INPUT))
63-
self.addOutput(OutputVector(self.OUTPUT, self.tr('Extracted (expression)')))
65+
self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT,
66+
self.tr('Input layer')))
67+
self.addParameter(QgsProcessingParameterExpression(self.EXPRESSION,
68+
self.tr('Expression'), None, self.INPUT))
69+
70+
self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Matching features')))
71+
self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Matching (expression)')))
72+
self.addParameter(QgsProcessingParameterFeatureSink(self.FAIL_OUTPUT, self.tr('Non-matching'),
73+
QgsProcessingParameterDefinition.TypeVectorAny, None, True))
74+
self.addOutput(QgsProcessingOutputVectorLayer(self.FAIL_OUTPUT, self.tr('Non-matching (expression)')))
6475

6576
def name(self):
6677
return 'extractbyexpression'
@@ -69,18 +80,48 @@ def displayName(self):
6980
return self.tr('Extract by expression')
7081

7182
def processAlgorithm(self, parameters, context, feedback):
72-
layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT), context)
73-
expression_string = self.getParameterValue(self.EXPRESSION)
74-
writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(layer.fields(), layer.wkbType(), layer.crs(),
75-
context)
83+
source = self.parameterAsSource(parameters, self.INPUT, context)
84+
expression_string = self.parameterAsExpression(parameters, self.EXPRESSION, context)
85+
86+
(matching_sink, matching_sink_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
87+
source.fields(), source.wkbType(), source.sourceCrs())
88+
(nonmatching_sink, non_matching_sink_id) = self.parameterAsSink(parameters, self.FAIL_OUTPUT, context,
89+
source.fields(), source.wkbType(), source.sourceCrs())
7690

7791
expression = QgsExpression(expression_string)
78-
if not expression.hasParserError():
92+
if expression.hasParserError():
93+
raise GeoAlgorithmExecutionException(expression.parserErrorString())
94+
expression_context = self.createExpressionContext(parameters, context)
95+
96+
if not nonmatching_sink:
97+
# not saving failing features - so only fetch good features
7998
req = QgsFeatureRequest().setFilterExpression(expression_string)
99+
req.setExpressionContext(expression_context)
100+
101+
for f in source.getFeatures(req):
102+
if feedback.isCanceled():
103+
break
104+
matching_sink.addFeature(f)
80105
else:
81-
raise GeoAlgorithmExecutionException(expression.parserErrorString())
106+
# saving non-matching features, so we need EVERYTHING
107+
expression_context.setFields(source.fields())
108+
expression.prepare(expression_context)
109+
110+
total = 100.0 / source.featureCount()
111+
112+
for current, f in enumerate(source.getFeatures()):
113+
if feedback.isCanceled():
114+
break
115+
116+
expression_context.setFeature(f)
117+
if expression.evaluate(expression_context):
118+
matching_sink.addFeature(f)
119+
else:
120+
nonmatching_sink.addFeature(f)
82121

83-
for f in layer.getFeatures(req):
84-
writer.addFeature(f)
122+
feedback.setProgress(int(current * total))
85123

86-
del writer
124+
results = {self.OUTPUT: matching_sink_id}
125+
if nonmatching_sink:
126+
results[self.FAIL_OUTPUT] = non_matching_sink_id
127+
return results

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
# from .RandomExtract import RandomExtract
4949
# from .RandomExtractWithinSubsets import RandomExtractWithinSubsets
5050
# from .ExtractByLocation import ExtractByLocation
51-
# from .ExtractByExpression import ExtractByExpression
51+
from .ExtractByExpression import ExtractByExpression
5252
# from .PointsInPolygon import PointsInPolygon
5353
# from .PointsInPolygonUnique import PointsInPolygonUnique
5454
# from .PointsInPolygonWeighted import PointsInPolygonWeighted
@@ -247,7 +247,7 @@ def getAlgs(self):
247247
# Slope(), Ruggedness(), Hillshade(),
248248
# Relief(), ZonalStatisticsQgis(),
249249
# IdwInterpolation(), TinInterpolation(),
250-
# RemoveNullGeometry(), ExtractByExpression(),
250+
# RemoveNullGeometry(),
251251
# ExtendLines(), ExtractSpecificNodes(),
252252
# GeometryByExpression(), SnapGeometriesToLayer(),
253253
# PoleOfInaccessibility(), CreateAttributeIndex(),
@@ -268,7 +268,8 @@ def getAlgs(self):
268268
CheckValidity(),
269269
Clip(),
270270
DeleteColumn(),
271-
ExtentFromLayer()
271+
ExtentFromLayer(),
272+
ExtractByExpression()
272273
]
273274

274275
if hasPlotly:

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
QgsApplication,
3939
QgsCoordinateReferenceSystem,
4040
QgsExpression,
41+
QgsExpressionContextGenerator,
4142
QgsFieldProxyModel,
4243
QgsMapLayerProxyModel,
4344
QgsWkbTypes,
@@ -1004,6 +1005,8 @@ def parentLayerChanged(self, wrapper):
10041005

10051006
def setLayer(self, layer):
10061007
context = dataobjects.createContext()
1008+
if isinstance(layer, QgsProcessingFeatureSourceDefinition):
1009+
layer, ok = layer.source.valueAsString(context.expressionContext())
10071010
if isinstance(layer, str):
10081011
layer = QgsProcessingUtils.mapLayerFromString(layer, context)
10091012
self.widget.setLayer(layer)

‎python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1258,18 +1258,18 @@ tests:
12581258
# name: expected/remove_null_polys.gml
12591259
# type: vector
12601260
#
1261-
# - algorithm: qgis:extractbyexpression
1262-
# name: Extract by Expression
1263-
# params:
1264-
# EXPRESSION: left( "Name",1)='A'
1265-
# INPUT:
1266-
# name: polys.gml
1267-
# type: vector
1268-
# results:
1269-
# OUTPUT:
1270-
# name: expected/extract_expression.gml
1271-
# type: vector
1272-
#
1261+
- algorithm: qgis:extractbyexpression
1262+
name: Extract by Expression
1263+
params:
1264+
EXPRESSION: left( "Name",1)='A'
1265+
INPUT:
1266+
name: polys.gml
1267+
type: vector
1268+
results:
1269+
OUTPUT:
1270+
name: expected/extract_expression.gml
1271+
type: vector
1272+
12731273
# - algorithm: qgis:extendlines
12741274
# name: Extend lines
12751275
# params:

0 commit comments

Comments
 (0)
Please sign in to comment.