mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-16 00:03:12 -04:00
[FEATURE] Port Extract by Expression to new API, allow saving
non matching features to separate output
This commit is contained in:
parent
a6a3027ea6
commit
d8260b8c47
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
Loading…
x
Reference in New Issue
Block a user