Skip to content

Commit

Permalink
[FEATURE] Port Extract by Expression to new API, allow saving
Browse files Browse the repository at this point in the history
non matching features to separate output
  • Loading branch information
nyalldawson committed Jun 11, 2017
1 parent a6a3027 commit d8260b8
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 30 deletions.
71 changes: 56 additions & 15 deletions python/plugins/processing/algs/qgis/ExtractByExpression.py
Expand Up @@ -27,7 +27,12 @@
from qgis.core import (QgsExpression,
QgsFeatureRequest,
QgsApplication,
QgsProcessingUtils)
QgsProcessingUtils,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterExpression,
QgsProcessingParameterFeatureSink,
QgsProcessingOutputVectorLayer,
QgsProcessingParameterDefinition)

from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
from processing.core.parameters import ParameterVector
Expand All @@ -41,6 +46,7 @@ class ExtractByExpression(QgisAlgorithm):
INPUT = 'INPUT'
EXPRESSION = 'EXPRESSION'
OUTPUT = 'OUTPUT'
FAIL_OUTPUT = 'FAIL_OUTPUT'

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

def __init__(self):
super().__init__()
self.addParameter(ParameterVector(self.INPUT,
self.tr('Input Layer')))
self.addParameter(ParameterExpression(self.EXPRESSION,
self.tr("Expression"), parent_layer=self.INPUT))
self.addOutput(OutputVector(self.OUTPUT, self.tr('Extracted (expression)')))
self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT,
self.tr('Input layer')))
self.addParameter(QgsProcessingParameterExpression(self.EXPRESSION,
self.tr('Expression'), None, self.INPUT))

self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Matching features')))
self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Matching (expression)')))
self.addParameter(QgsProcessingParameterFeatureSink(self.FAIL_OUTPUT, self.tr('Non-matching'),
QgsProcessingParameterDefinition.TypeVectorAny, None, True))
self.addOutput(QgsProcessingOutputVectorLayer(self.FAIL_OUTPUT, self.tr('Non-matching (expression)')))

def name(self):
return 'extractbyexpression'
Expand All @@ -69,18 +80,48 @@ def displayName(self):
return self.tr('Extract by expression')

def processAlgorithm(self, parameters, context, feedback):
layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT), context)
expression_string = self.getParameterValue(self.EXPRESSION)
writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(layer.fields(), layer.wkbType(), layer.crs(),
context)
source = self.parameterAsSource(parameters, self.INPUT, context)
expression_string = self.parameterAsExpression(parameters, self.EXPRESSION, context)

(matching_sink, matching_sink_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
source.fields(), source.wkbType(), source.sourceCrs())
(nonmatching_sink, non_matching_sink_id) = self.parameterAsSink(parameters, self.FAIL_OUTPUT, context,
source.fields(), source.wkbType(), source.sourceCrs())

expression = QgsExpression(expression_string)
if not expression.hasParserError():
if expression.hasParserError():
raise GeoAlgorithmExecutionException(expression.parserErrorString())
expression_context = self.createExpressionContext(parameters, context)

if not nonmatching_sink:
# not saving failing features - so only fetch good features
req = QgsFeatureRequest().setFilterExpression(expression_string)
req.setExpressionContext(expression_context)

for f in source.getFeatures(req):
if feedback.isCanceled():
break
matching_sink.addFeature(f)
else:
raise GeoAlgorithmExecutionException(expression.parserErrorString())
# saving non-matching features, so we need EVERYTHING
expression_context.setFields(source.fields())
expression.prepare(expression_context)

total = 100.0 / source.featureCount()

for current, f in enumerate(source.getFeatures()):
if feedback.isCanceled():
break

expression_context.setFeature(f)
if expression.evaluate(expression_context):
matching_sink.addFeature(f)
else:
nonmatching_sink.addFeature(f)

for f in layer.getFeatures(req):
writer.addFeature(f)
feedback.setProgress(int(current * total))

del writer
results = {self.OUTPUT: matching_sink_id}
if nonmatching_sink:
results[self.FAIL_OUTPUT] = non_matching_sink_id
return results
7 changes: 4 additions & 3 deletions python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py
Expand Up @@ -48,7 +48,7 @@
# from .RandomExtract import RandomExtract
# from .RandomExtractWithinSubsets import RandomExtractWithinSubsets
# from .ExtractByLocation import ExtractByLocation
# from .ExtractByExpression import ExtractByExpression
from .ExtractByExpression import ExtractByExpression
# from .PointsInPolygon import PointsInPolygon
# from .PointsInPolygonUnique import PointsInPolygonUnique
# from .PointsInPolygonWeighted import PointsInPolygonWeighted
Expand Down Expand Up @@ -247,7 +247,7 @@ def getAlgs(self):
# Slope(), Ruggedness(), Hillshade(),
# Relief(), ZonalStatisticsQgis(),
# IdwInterpolation(), TinInterpolation(),
# RemoveNullGeometry(), ExtractByExpression(),
# RemoveNullGeometry(),
# ExtendLines(), ExtractSpecificNodes(),
# GeometryByExpression(), SnapGeometriesToLayer(),
# PoleOfInaccessibility(), CreateAttributeIndex(),
Expand All @@ -268,7 +268,8 @@ def getAlgs(self):
CheckValidity(),
Clip(),
DeleteColumn(),
ExtentFromLayer()
ExtentFromLayer(),
ExtractByExpression()
]

if hasPlotly:
Expand Down
3 changes: 3 additions & 0 deletions python/plugins/processing/gui/wrappers.py
Expand Up @@ -38,6 +38,7 @@
QgsApplication,
QgsCoordinateReferenceSystem,
QgsExpression,
QgsExpressionContextGenerator,
QgsFieldProxyModel,
QgsMapLayerProxyModel,
QgsWkbTypes,
Expand Down Expand Up @@ -1004,6 +1005,8 @@ def parentLayerChanged(self, wrapper):

def setLayer(self, layer):
context = dataobjects.createContext()
if isinstance(layer, QgsProcessingFeatureSourceDefinition):
layer, ok = layer.source.valueAsString(context.expressionContext())
if isinstance(layer, str):
layer = QgsProcessingUtils.mapLayerFromString(layer, context)
self.widget.setLayer(layer)
Expand Down
24 changes: 12 additions & 12 deletions python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml
Expand Up @@ -1258,18 +1258,18 @@ tests:
# name: expected/remove_null_polys.gml
# type: vector
#
# - algorithm: qgis:extractbyexpression
# name: Extract by Expression
# params:
# EXPRESSION: left( "Name",1)='A'
# INPUT:
# name: polys.gml
# type: vector
# results:
# OUTPUT:
# name: expected/extract_expression.gml
# type: vector
#
- algorithm: qgis:extractbyexpression
name: Extract by Expression
params:
EXPRESSION: left( "Name",1)='A'
INPUT:
name: polys.gml
type: vector
results:
OUTPUT:
name: expected/extract_expression.gml
type: vector

# - algorithm: qgis:extendlines
# name: Extend lines
# params:
Expand Down

0 comments on commit d8260b8

Please sign in to comment.