[FEATURE] Port Extract by Expression to new API, allow saving

non matching features to separate output
This commit is contained in:
Nyall Dawson 2017-06-09 15:05:25 +10:00
parent a6a3027ea6
commit d8260b8c47
4 changed files with 76 additions and 31 deletions

View File

@ -27,7 +27,12 @@ __revision__ = '$Format:%H$'
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
@ -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")
@ -56,11 +62,16 @@ class ExtractByExpression(QgisAlgorithm):
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'
@ -69,18 +80,48 @@ class ExtractByExpression(QgisAlgorithm):
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():
req = QgsFeatureRequest().setFilterExpression(expression_string)
else:
if expression.hasParserError():
raise GeoAlgorithmExecutionException(expression.parserErrorString())
expression_context = self.createExpressionContext(parameters, context)
for f in layer.getFeatures(req):
writer.addFeature(f)
if not nonmatching_sink:
# not saving failing features - so only fetch good features
req = QgsFeatureRequest().setFilterExpression(expression_string)
req.setExpressionContext(expression_context)
del writer
for f in source.getFeatures(req):
if feedback.isCanceled():
break
matching_sink.addFeature(f)
else:
# 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)
feedback.setProgress(int(current * total))
results = {self.OUTPUT: matching_sink_id}
if nonmatching_sink:
results[self.FAIL_OUTPUT] = non_matching_sink_id
return results

View File

@ -48,7 +48,7 @@ from .QgisAlgorithm import QgisAlgorithm
# 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
@ -247,7 +247,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider):
# Slope(), Ruggedness(), Hillshade(),
# Relief(), ZonalStatisticsQgis(),
# IdwInterpolation(), TinInterpolation(),
# RemoveNullGeometry(), ExtractByExpression(),
# RemoveNullGeometry(),
# ExtendLines(), ExtractSpecificNodes(),
# GeometryByExpression(), SnapGeometriesToLayer(),
# PoleOfInaccessibility(), CreateAttributeIndex(),
@ -268,7 +268,8 @@ class QGISAlgorithmProvider(QgsProcessingProvider):
CheckValidity(),
Clip(),
DeleteColumn(),
ExtentFromLayer()
ExtentFromLayer(),
ExtractByExpression()
]
if hasPlotly:

View File

@ -38,6 +38,7 @@ from qgis.core import (
QgsApplication,
QgsCoordinateReferenceSystem,
QgsExpression,
QgsExpressionContextGenerator,
QgsFieldProxyModel,
QgsMapLayerProxyModel,
QgsWkbTypes,
@ -1004,6 +1005,8 @@ class ExpressionWidgetWrapper(WidgetWrapper):
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)

View File

@ -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: