mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-14 00:07:35 -04:00
Function to make output features compatible
This commit is contained in:
parent
d8d32ac810
commit
11aaf90393
@ -34,7 +34,12 @@ from qgis.core import (Qgis,
|
||||
QgsMessageLog,
|
||||
QgsProcessingException,
|
||||
QgsProcessingFeatureSourceDefinition,
|
||||
QgsProcessingParameters)
|
||||
QgsProcessingParameters,
|
||||
QgsProject,
|
||||
QgsFeatureRequest,
|
||||
QgsFeature,
|
||||
QgsExpression,
|
||||
QgsWkbTypes)
|
||||
from processing.gui.Postprocessing import handleAlgorithmResults
|
||||
from processing.tools import dataobjects
|
||||
from qgis.utils import iface
|
||||
@ -63,40 +68,168 @@ def execute(alg, parameters, context=None, feedback=None):
|
||||
return False, {}
|
||||
|
||||
|
||||
def execute_in_place(alg, parameters, context=None, feedback=None):
|
||||
def make_feature_compatible(new_features, input_layer):
|
||||
"""Try to make new features compatible with old feature by:
|
||||
|
||||
- converting single to multi part
|
||||
- dropping additional attributes
|
||||
- adding back M/Z values
|
||||
|
||||
|
||||
:param new_features: new features
|
||||
:type new_features: list of QgsFeatures
|
||||
:param input_layer: input layer
|
||||
:type input_layer: QgsVectorLayer
|
||||
:return: modified features
|
||||
:rtype: list of QgsFeatures
|
||||
"""
|
||||
|
||||
result_features = []
|
||||
for new_f in new_features:
|
||||
if (new_f.geometry().wkbType() != input_layer.wkbType() and
|
||||
QgsWkbTypes.isMultiType(input_layer.wkbType()) and not
|
||||
new_f.geometry().isMultipart()):
|
||||
new_geom = new_f.geometry()
|
||||
new_geom.convertToMultiType()
|
||||
new_f.setGeometry(new_geom)
|
||||
if len(new_f.fields()) > len(input_layer.fields()):
|
||||
f = QgsFeature(input_layer.fields())
|
||||
f.setGeometry(new_f.geometry())
|
||||
f.setAttributes(new_f.attributes()[:len(input_layer.fields())])
|
||||
result_features.append(new_f)
|
||||
return result_features
|
||||
|
||||
|
||||
def execute_in_place_run(alg, parameters, context=None, feedback=None):
|
||||
"""Executes an algorithm modifying features in-place in the input layer.
|
||||
|
||||
The input layer must be editable or an exception is raised.
|
||||
|
||||
:param alg: algorithm to run
|
||||
:type alg: QgsProcessingAlgorithm
|
||||
:param parameters: parameters of the algorithm
|
||||
:type parameters: dict
|
||||
:param context: context, defaults to None
|
||||
:param context: QgsProcessingContext, optional
|
||||
:param feedback: feedback, defaults to None
|
||||
:param feedback: QgsProcessingFeedback, optional
|
||||
:raises QgsProcessingException: raised when the layer is not editable or the layer cannot be found in the current project
|
||||
:return: a tuple with true if success and results
|
||||
:rtype: tuple
|
||||
"""
|
||||
|
||||
if feedback is None:
|
||||
feedback = QgsProcessingFeedback()
|
||||
if context is None:
|
||||
context = dataobjects.createContext(feedback)
|
||||
|
||||
parameters['INPUT'] = QgsProcessingFeatureSourceDefinition(iface.activeLayer().id(), True)
|
||||
# It would be nicer to get the layer from INPUT with
|
||||
# alg.parameterAsVectorLayer(parameters, 'INPUT', context)
|
||||
# but it does not work.
|
||||
active_layer_id, ok = parameters['INPUT'].source.value(context.expressionContext())
|
||||
if ok:
|
||||
active_layer = QgsProject.instance().mapLayer(active_layer_id)
|
||||
if active_layer is None or not active_layer.isEditable():
|
||||
raise QgsProcessingException(tr("Layer is not editable or layer with id '%s' could not be found in the current project.") % active_layer_id)
|
||||
else:
|
||||
return False, {}
|
||||
|
||||
parameters['OUTPUT'] = 'memory:'
|
||||
|
||||
try:
|
||||
results, ok = alg.run(parameters, context, feedback)
|
||||
new_feature_ids = []
|
||||
|
||||
layer = QgsProcessingUtils.mapLayerFromString(results['OUTPUT'], context)
|
||||
iface.activeLayer().beginEditCommand('Edit features')
|
||||
iface.activeLayer().deleteFeatures(iface.activeLayer().selectedFeatureIds())
|
||||
features = []
|
||||
for f in layer.getFeatures():
|
||||
features.append(f)
|
||||
iface.activeLayer().addFeatures(features)
|
||||
new_selection = [f.id() for f in features]
|
||||
iface.activeLayer().endEditCommand()
|
||||
#iface.activeLayer().selectByIds(new_selection)
|
||||
iface.activeLayer().triggerRepaint()
|
||||
active_layer.beginEditCommand(tr('In-place editing by %s') % alg.name())
|
||||
|
||||
req = QgsFeatureRequest(QgsExpression(r"$id < 0"))
|
||||
req.setFlags(QgsFeatureRequest.NoGeometry)
|
||||
req.setSubsetOfAttributes([])
|
||||
|
||||
# Checks whether the algorithm has a processFeature method
|
||||
if hasattr(alg, 'processFeature'): # in-place feature editing
|
||||
alg.prepare(parameters, context, feedback)
|
||||
field_idxs = range(len(active_layer.fields()))
|
||||
feature_iterator = active_layer.getFeatures(QgsFeatureRequest(active_layer.selectedFeatureIds())) if parameters['INPUT'].selectedFeaturesOnly else active_layer.getFeatures()
|
||||
for f in feature_iterator:
|
||||
new_features = alg.processFeature(f, context, feedback)
|
||||
new_features = make_feature_compatible(new_features, active_layer)
|
||||
if len(new_features) == 0:
|
||||
active_layer.deleteFeature(f.id())
|
||||
elif len(new_features) == 1:
|
||||
new_f = new_features[0]
|
||||
if not f.geometry().equals(new_f.geometry()):
|
||||
active_layer.changeGeometry(f.id(), new_f.geometry())
|
||||
if f.attributes() != new_f.attributes():
|
||||
active_layer.changeAttributeValues(f.id(), dict(zip(field_idxs, new_f.attributes())), dict(zip(field_idxs, f.attributes())))
|
||||
new_feature_ids.append(f.id())
|
||||
else:
|
||||
active_layer.deleteFeature(f.id())
|
||||
# Get the new ids
|
||||
old_ids = set([f.id() for f in active_layer.getFeatures(req)])
|
||||
active_layer.addFeatures(new_features)
|
||||
new_ids = set([f.id() for f in active_layer.getFeatures(req)])
|
||||
new_feature_ids += list(new_ids - old_ids)
|
||||
|
||||
results, ok = {}, True
|
||||
|
||||
else: # Traditional 'run' with delete and add features cycle
|
||||
results, ok = alg.run(parameters, context, feedback)
|
||||
|
||||
result_layer = QgsProcessingUtils.mapLayerFromString(results['OUTPUT'], context)
|
||||
# TODO: check if features have changed before delete/add cycle
|
||||
active_layer.deleteFeatures(active_layer.selectedFeatureIds())
|
||||
new_features = []
|
||||
for f in result_layer.getFeatures():
|
||||
new_features.append(make_feature_compatible([f], active_layer))
|
||||
|
||||
# Get the new ids
|
||||
old_ids = set([f.id() for f in active_layer.getFeatures(req)])
|
||||
active_layer.addFeatures(new_features)
|
||||
new_ids = set([f.id() for f in active_layer.getFeatures(req)])
|
||||
new_feature_ids += list(new_ids - old_ids)
|
||||
|
||||
if ok and new_feature_ids:
|
||||
active_layer.selectByIds(new_feature_ids)
|
||||
elif not ok:
|
||||
active_layer.rollback()
|
||||
|
||||
active_layer.endEditCommand()
|
||||
|
||||
return ok, results
|
||||
|
||||
except QgsProcessingException as e:
|
||||
QgsMessageLog.logMessage(str(sys.exc_info()[0]), 'Processing', Qgis.Critical)
|
||||
if feedback is not None:
|
||||
feedback.reportError(e.msg)
|
||||
|
||||
return False, {}
|
||||
|
||||
|
||||
def execute_in_place(alg, parameters, context=None, feedback=None):
|
||||
"""Executes an algorithm modifying features in-place in the active layer.
|
||||
|
||||
The input layer must be editable or an exception is raised.
|
||||
|
||||
:param alg: algorithm to run
|
||||
:type alg: QgsProcessingAlgorithm
|
||||
:param parameters: parameters of the algorithm
|
||||
:type parameters: dict
|
||||
:param context: context, defaults to None
|
||||
:param context: QgsProcessingContext, optional
|
||||
:param feedback: feedback, defaults to None
|
||||
:param feedback: QgsProcessingFeedback, optional
|
||||
:raises QgsProcessingException: raised when the layer is not editable or the layer cannot be found in the current project
|
||||
:return: a tuple with true if success and results
|
||||
:rtype: tuple
|
||||
"""
|
||||
|
||||
parameters['INPUT'] = QgsProcessingFeatureSourceDefinition(iface.activeLayer().id(), True)
|
||||
ok, results = execute_in_place_run(alg, parameters, context=context, feedback=feedback)
|
||||
if ok:
|
||||
iface.activeLayer().triggerRepaint()
|
||||
return ok, results
|
||||
|
||||
|
||||
def executeIterating(alg, parameters, paramToIter, context, feedback):
|
||||
# Generate all single-feature layers
|
||||
parameter_definition = alg.parameterDefinition(paramToIter)
|
||||
|
@ -142,6 +142,7 @@ ADD_PYTHON_TEST(PyQgsPointClusterRenderer test_qgspointclusterrenderer.py)
|
||||
ADD_PYTHON_TEST(PyQgsPointDisplacementRenderer test_qgspointdisplacementrenderer.py)
|
||||
ADD_PYTHON_TEST(PyQgsPostgresDomain test_qgspostgresdomain.py)
|
||||
ADD_PYTHON_TEST(PyQgsProcessingRecentAlgorithmLog test_qgsprocessingrecentalgorithmslog.py)
|
||||
ADD_PYTHON_TEST(PyQgsProcessingInPlace test_qgsprocessinginplace.py)
|
||||
ADD_PYTHON_TEST(PyQgsProjectionSelectionWidgets test_qgsprojectionselectionwidgets.py)
|
||||
ADD_PYTHON_TEST(PyQgsProjectMetadata test_qgsprojectmetadata.py)
|
||||
ADD_PYTHON_TEST(PyQgsRange test_qgsrange.py)
|
||||
|
Loading…
x
Reference in New Issue
Block a user