Пример #1
0
    def test_make_features_compatible_geometry(self):
        """Test corner cases for geometries"""

        # Make a feature with no geometry
        layer = self._make_layer('Point')
        self.assertTrue(layer.isValid())
        self.assertTrue(layer.startEditing())
        f1 = QgsFeature(layer.fields())
        f1.setAttributes([1])

        # Check that it is accepted on a Point layer
        new_features = QgsVectorLayerUtils.makeFeaturesCompatible([f1], layer)
        self.assertEqual(len(new_features), 1)
        self.assertEqual(new_features[0].geometry().asWkt(), '')

        # Make a geometry-less layer
        nogeom_layer = QgsMemoryProviderUtils.createMemoryLayer(
            'nogeom_layer', layer.fields(), QgsWkbTypes.NoGeometry,
            QgsCoordinateReferenceSystem(4326))
        # Check that a geometry-less feature is accepted
        new_features = QgsVectorLayerUtils.makeFeaturesCompatible([f1],
                                                                  nogeom_layer)
        self.assertEqual(len(new_features), 1)
        self.assertEqual(new_features[0].geometry().asWkt(), '')

        # Make a geometry-less layer
        nogeom_layer = QgsMemoryProviderUtils.createMemoryLayer(
            'nogeom_layer', layer.fields(), QgsWkbTypes.NoGeometry,
            QgsCoordinateReferenceSystem(4326))
        # Check that a Point feature is accepted but geometry was dropped
        f1.setGeometry(QgsGeometry.fromWkt('Point(9 45)'))
        new_features = QgsVectorLayerUtils.makeFeaturesCompatible([f1],
                                                                  nogeom_layer)
        self.assertEqual(len(new_features), 1)
        self.assertEqual(new_features[0].geometry().asWkt(), '')
Пример #2
0
    def test_make_features_compatible_geometry(self):
        """Test corner cases for geometries"""

        # Make a feature with no geometry
        layer = self._make_layer('Point')
        self.assertTrue(layer.isValid())
        self.assertTrue(layer.startEditing())
        f1 = QgsFeature(layer.fields())
        f1.setAttributes([1])

        # Check that it is accepted on a Point layer
        new_features = QgsVectorLayerUtils.makeFeaturesCompatible([f1], layer)
        self.assertEqual(len(new_features), 1)
        self.assertEqual(new_features[0].geometry().asWkt(), '')

        # Make a geometry-less layer
        nogeom_layer = QgsMemoryProviderUtils.createMemoryLayer(
            'nogeom_layer', layer.fields(), QgsWkbTypes.NoGeometry, QgsCoordinateReferenceSystem(4326))
        # Check that a geometry-less feature is accepted
        new_features = QgsVectorLayerUtils.makeFeaturesCompatible([f1], nogeom_layer)
        self.assertEqual(len(new_features), 1)
        self.assertEqual(new_features[0].geometry().asWkt(), '')

        # Make a geometry-less layer
        nogeom_layer = QgsMemoryProviderUtils.createMemoryLayer(
            'nogeom_layer', layer.fields(), QgsWkbTypes.NoGeometry, QgsCoordinateReferenceSystem(4326))
        # Check that a Point feature is accepted but geometry was dropped
        f1.setGeometry(QgsGeometry.fromWkt('Point(9 45)'))
        new_features = QgsVectorLayerUtils.makeFeaturesCompatible([f1], nogeom_layer)
        self.assertEqual(len(new_features), 1)
        self.assertEqual(new_features[0].geometry().asWkt(), '')
Пример #3
0
    def test_make_features_compatible_attributes(self):
        """Test corner cases for attributes"""

        # Test feature without attributes
        fields = QgsFields()
        fields.append(QgsField('int_f', QVariant.Int))
        fields.append(QgsField('str_f', QVariant.String))
        layer = QgsMemoryProviderUtils.createMemoryLayer(
            'mkfca_layer', fields, QgsWkbTypes.Point,
            QgsCoordinateReferenceSystem(4326))
        self.assertTrue(layer.isValid())
        f1 = QgsFeature(layer.fields())
        f1['int_f'] = 1
        f1['str_f'] = 'str'
        f1.setGeometry(QgsGeometry.fromWkt('Point(9 45)'))
        new_features = QgsVectorLayerUtils.makeFeaturesCompatible([f1], layer)
        self.assertEqual(new_features[0].attributes(), f1.attributes())
        self.assertTrue(new_features[0].geometry().asWkt(),
                        f1.geometry().asWkt())

        # Test pad with 0 with fields
        f1.setAttributes([])
        new_features = QgsVectorLayerUtils.makeFeaturesCompatible([f1], layer)
        self.assertEqual(len(new_features[0].attributes()), 2)
        self.assertEqual(new_features[0].attributes()[0], QVariant())
        self.assertEqual(new_features[0].attributes()[1], QVariant())

        # Test pad with 0 without fields
        f1 = QgsFeature()
        f1.setGeometry(QgsGeometry.fromWkt('Point(9 45)'))
        new_features = QgsVectorLayerUtils.makeFeaturesCompatible([f1], layer)
        self.assertEqual(len(new_features[0].attributes()), 2)
        self.assertEqual(new_features[0].attributes()[0], QVariant())
        self.assertEqual(new_features[0].attributes()[1], QVariant())

        # Test drop extra attrs
        f1 = QgsFeature(layer.fields())
        f1.setAttributes([1, 'foo', 'extra'])
        f1.setGeometry(QgsGeometry.fromWkt('Point(9 45)'))
        new_features = QgsVectorLayerUtils.makeFeaturesCompatible([f1], layer)
        self.assertEqual(len(new_features[0].attributes()), 2)
        self.assertEqual(new_features[0].attributes()[0], 1)
        self.assertEqual(new_features[0].attributes()[1], 'foo')
Пример #4
0
    def test_make_features_compatible_different_field_length(self):
        """Test regression #21497"""
        fields = QgsFields()
        fields.append(QgsField('int_f1', QVariant.Int))
        f1 = QgsFeature(fields)
        f1.setAttributes([12345])
        f1.setGeometry(QgsGeometry.fromWkt('Point(9 45)'))

        fields = QgsFields()
        fields.append(QgsField('int_f2', QVariant.Int))
        fields.append(QgsField('int_f1', QVariant.Int))
        vl2 = QgsMemoryProviderUtils.createMemoryLayer(
            'mymultiplayer', fields, QgsWkbTypes.Point, QgsCoordinateReferenceSystem(4326))
        new_features = QgsVectorLayerUtils.makeFeaturesCompatible([f1], vl2)
        self.assertEqual(new_features[0].attributes(), [None, 12345])

        f1.setGeometry(QgsGeometry.fromWkt('MultiPoint((9 45))'))
        new_features = QgsVectorLayerUtils.makeFeaturesCompatible([f1], vl2)
        self.assertEqual(new_features[0].attributes(), [None, 12345])
Пример #5
0
    def test_make_features_compatible_different_field_length(self):
        """Test regression #21497"""
        fields = QgsFields()
        fields.append(QgsField('int_f1', QVariant.Int))
        f1 = QgsFeature(fields)
        f1.setAttributes([12345])
        f1.setGeometry(QgsGeometry.fromWkt('Point(9 45)'))

        fields = QgsFields()
        fields.append(QgsField('int_f2', QVariant.Int))
        fields.append(QgsField('int_f1', QVariant.Int))
        vl2 = QgsMemoryProviderUtils.createMemoryLayer(
            'mymultiplayer', fields, QgsWkbTypes.Point, QgsCoordinateReferenceSystem(4326))
        new_features = QgsVectorLayerUtils.makeFeaturesCompatible([f1], vl2)
        self.assertEqual(new_features[0].attributes(), [None, 12345])

        f1.setGeometry(QgsGeometry.fromWkt('MultiPoint((9 45))'))
        new_features = QgsVectorLayerUtils.makeFeaturesCompatible([f1], vl2)
        self.assertEqual(new_features[0].attributes(), [None, 12345])
Пример #6
0
    def test_make_features_compatible_attributes(self):
        """Test corner cases for attributes"""

        # Test feature without attributes
        fields = QgsFields()
        fields.append(QgsField('int_f', QVariant.Int))
        fields.append(QgsField('str_f', QVariant.String))
        layer = QgsMemoryProviderUtils.createMemoryLayer(
            'mkfca_layer', fields, QgsWkbTypes.Point, QgsCoordinateReferenceSystem(4326))
        self.assertTrue(layer.isValid())
        f1 = QgsFeature(layer.fields())
        f1['int_f'] = 1
        f1['str_f'] = 'str'
        f1.setGeometry(QgsGeometry.fromWkt('Point(9 45)'))
        new_features = QgsVectorLayerUtils.makeFeaturesCompatible([f1], layer)
        self.assertEqual(new_features[0].attributes(), f1.attributes())
        self.assertTrue(new_features[0].geometry().asWkt(), f1.geometry().asWkt())

        # Test pad with 0 with fields
        f1.setAttributes([])
        new_features = QgsVectorLayerUtils.makeFeaturesCompatible([f1], layer)
        self.assertEqual(len(new_features[0].attributes()), 2)
        self.assertEqual(new_features[0].attributes()[0], QVariant())
        self.assertEqual(new_features[0].attributes()[1], QVariant())

        # Test pad with 0 without fields
        f1 = QgsFeature()
        f1.setGeometry(QgsGeometry.fromWkt('Point(9 45)'))
        new_features = QgsVectorLayerUtils.makeFeaturesCompatible([f1], layer)
        self.assertEqual(len(new_features[0].attributes()), 2)
        self.assertEqual(new_features[0].attributes()[0], QVariant())
        self.assertEqual(new_features[0].attributes()[1], QVariant())

        # Test drop extra attrs
        f1 = QgsFeature(layer.fields())
        f1.setAttributes([1, 'foo', 'extra'])
        f1.setGeometry(QgsGeometry.fromWkt('Point(9 45)'))
        new_features = QgsVectorLayerUtils.makeFeaturesCompatible([f1], layer)
        self.assertEqual(len(new_features[0].attributes()), 2)
        self.assertEqual(new_features[0].attributes()[0], 1)
        self.assertEqual(new_features[0].attributes()[1], 'foo')
Пример #7
0
    def _make_compatible_tester(self, feature_wkt, layer_wkb_name, attrs=[1]):
        layer = self._make_layer(layer_wkb_name)
        layer.startEditing()

        f = QgsFeature(layer.fields())
        f.setAttributes(attrs)
        f.setGeometry(QgsGeometry.fromWkt(feature_wkt))
        self.assertTrue(f.isValid())

        context = QgsProcessingContext()
        context.setProject(QgsProject.instance())

        # Fix it!
        new_features = QgsVectorLayerUtils.makeFeaturesCompatible([f], layer)

        for new_f in new_features:
            self.assertEqual(new_f.geometry().wkbType(), layer.wkbType())

        self.assertTrue(layer.addFeatures(new_features), "Fail: %s - %s - %s" % (feature_wkt, attrs, layer_wkb_name))
        return layer, new_features
Пример #8
0
    def _make_compatible_tester(self, feature_wkt, layer_wkb_name, attrs=[1]):
        layer = self._make_layer(layer_wkb_name)
        layer.startEditing()

        f = QgsFeature(layer.fields())
        f.setAttributes(attrs)
        f.setGeometry(QgsGeometry.fromWkt(feature_wkt))
        self.assertTrue(f.isValid())

        context = QgsProcessingContext()
        context.setProject(QgsProject.instance())

        # Fix it!
        new_features = QgsVectorLayerUtils.makeFeaturesCompatible([f], layer)

        for new_f in new_features:
            self.assertEqual(new_f.geometry().wkbType(), layer.wkbType())

        self.assertTrue(layer.addFeatures(new_features), "Fail: %s - %s - %s" % (feature_wkt, attrs, layer_wkb_name))
        return layer, new_features
Пример #9
0
def execute_in_place_run(alg, parameters, context=None, feedback=None, raise_exceptions=False):
    """Executes an algorithm modifying features in-place in the input layer.

    :param alg: algorithm to run
    :type alg: QgsProcessingAlgorithm
    :param parameters: parameters of the algorithm
    :type parameters: dict
    :param context: context, defaults to None
    :type context: QgsProcessingContext, optional
    :param feedback: feedback, defaults to None
    :type feedback: QgsProcessingFeedback, optional
    :param raise_exceptions: useful for testing, if True exceptions are raised, normally exceptions will be forwarded to the feedback
    :type raise_exceptions: boo, default to False
    :raises QgsProcessingException: raised when there is no active layer, or it cannot be made editable
    :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)

    # Only feature based algs have sourceFlags
    try:
        if alg.sourceFlags() & QgsProcessingFeatureSource.FlagSkipGeometryValidityChecks:
            context.setInvalidGeometryCheck(QgsFeatureRequest.GeometryNoCheck)
    except AttributeError:
        pass

    active_layer = parameters['INPUT']

    # Run some checks and prepare the layer for in-place execution by:
    # - getting the active layer and checking that it is a vector
    # - making the layer editable if it was not already
    # - selecting all features if none was selected
    # - checking in-place support for the active layer/alg/parameters
    # If one of the check fails and raise_exceptions is True an exception
    # is raised, else the execution is aborted and the error reported in
    # the feedback
    try:
        if active_layer is None:
            raise QgsProcessingException(tr("There is not active layer."))

        if not isinstance(active_layer, QgsVectorLayer):
            raise QgsProcessingException(tr("Active layer is not a vector layer."))

        if not active_layer.isEditable():
            if not active_layer.startEditing():
                raise QgsProcessingException(tr("Active layer is not editable (and editing could not be turned on)."))

        if not alg.supportInPlaceEdit(active_layer):
            raise QgsProcessingException(tr("Selected algorithm and parameter configuration are not compatible with in-place modifications."))
    except QgsProcessingException as e:
        if raise_exceptions:
            raise e
        QgsMessageLog.logMessage(str(sys.exc_info()[0]), 'Processing', Qgis.Critical)
        if feedback is not None:
            feedback.reportError(getattr(e, 'msg', str(e)), fatalError=True)
        return False, {}

    if not active_layer.selectedFeatureIds():
        active_layer.selectAll()

    # Make sure we are working on selected features only
    parameters['INPUT'] = QgsProcessingFeatureSourceDefinition(active_layer.id(), True)
    parameters['OUTPUT'] = 'memory:'

    req = QgsFeatureRequest(QgsExpression(r"$id < 0"))
    req.setFlags(QgsFeatureRequest.NoGeometry)
    req.setSubsetOfAttributes([])

    # Start the execution
    # If anything goes wrong and raise_exceptions is True an exception
    # is raised, else the execution is aborted and the error reported in
    # the feedback
    try:
        new_feature_ids = []

        active_layer.beginEditCommand(alg.displayName())

        # Checks whether the algorithm has a processFeature method
        if hasattr(alg, 'processFeature'):  # in-place feature editing
            # Make a clone or it will crash the second time the dialog
            # is opened and run
            alg = alg.create()
            if not alg.prepare(parameters, context, feedback):
                raise QgsProcessingException(tr("Could not prepare selected algorithm."))
            # Check again for compatibility after prepare
            if not alg.supportInPlaceEdit(active_layer):
                raise QgsProcessingException(tr("Selected algorithm and parameter configuration are not compatible with in-place modifications."))
            field_idxs = range(len(active_layer.fields()))
            iterator_req = QgsFeatureRequest(active_layer.selectedFeatureIds())
            iterator_req.setInvalidGeometryCheck(context.invalidGeometryCheck())
            feature_iterator = active_layer.getFeatures(iterator_req)
            step = 100 / len(active_layer.selectedFeatureIds()) if active_layer.selectedFeatureIds() else 1
            for current, f in enumerate(feature_iterator):
                feedback.setProgress(current * step)
                if feedback.isCanceled():
                    break

                # need a deep copy, because python processFeature implementations may return
                # a shallow copy from processFeature
                input_feature = QgsFeature(f)
                new_features = alg.processFeature(input_feature, context, feedback)
                new_features = QgsVectorLayerUtils.makeFeaturesCompatible(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)])
                    if not active_layer.addFeatures(new_features):
                        raise QgsProcessingException(tr("Error adding processed features back into the layer."))
                    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

            # There is no way to know if some features have been skipped
            # due to invalid geometries
            if context.invalidGeometryCheck() == QgsFeatureRequest.GeometrySkipInvalid:
                selected_ids = active_layer.selectedFeatureIds()
            else:
                selected_ids = []

            results, ok = alg.run(parameters, context, feedback)

            if ok:
                result_layer = QgsProcessingUtils.mapLayerFromString(results['OUTPUT'], context)
                # TODO: check if features have changed before delete/add cycle

                new_features = []

                # Check if there are any skipped features
                if context.invalidGeometryCheck() == QgsFeatureRequest.GeometrySkipInvalid:
                    missing_ids = list(set(selected_ids) - set(result_layer.allFeatureIds()))
                    if missing_ids:
                        for f in active_layer.getFeatures(QgsFeatureRequest(missing_ids)):
                            if not f.geometry().isGeosValid():
                                new_features.append(f)

                active_layer.deleteFeatures(active_layer.selectedFeatureIds())

                for f in result_layer.getFeatures():
                    new_features.extend(QgsVectorLayerUtils.
                                        makeFeaturesCompatible([f], active_layer))

                # Get the new ids
                old_ids = set([f.id() for f in active_layer.getFeatures(req)])
                if not active_layer.addFeatures(new_features):
                    raise QgsProcessingException(tr("Error adding processed features back into the layer."))
                new_ids = set([f.id() for f in active_layer.getFeatures(req)])
                new_feature_ids += list(new_ids - old_ids)

        active_layer.endEditCommand()

        if ok and new_feature_ids:
            active_layer.selectByIds(new_feature_ids)
        elif not ok:
            active_layer.rollBack()

        return ok, results

    except QgsProcessingException as e:
        active_layer.endEditCommand()
        active_layer.rollBack()
        if raise_exceptions:
            raise e
        QgsMessageLog.logMessage(str(sys.exc_info()[0]), 'Processing', Qgis.Critical)
        if feedback is not None:
            feedback.reportError(getattr(e, 'msg', str(e)), fatalError=True)

    return False, {}
Пример #10
0
def execute_in_place_run(alg,
                         parameters,
                         context=None,
                         feedback=None,
                         raise_exceptions=False):
    """Executes an algorithm modifying features in-place in the input layer.

    :param alg: algorithm to run
    :type alg: QgsProcessingAlgorithm
    :param parameters: parameters of the algorithm
    :type parameters: dict
    :param context: context, defaults to None
    :type context: QgsProcessingContext, optional
    :param feedback: feedback, defaults to None
    :type feedback: QgsProcessingFeedback, optional
    :param raise_exceptions: useful for testing, if True exceptions are raised, normally exceptions will be forwarded to the feedback
    :type raise_exceptions: boo, default to False
    :raises QgsProcessingException: raised when there is no active layer, or it cannot be made editable
    :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)

    # Only feature based algs have sourceFlags
    try:
        if alg.sourceFlags(
        ) & QgsProcessingFeatureSource.FlagSkipGeometryValidityChecks:
            context.setInvalidGeometryCheck(QgsFeatureRequest.GeometryNoCheck)
    except AttributeError:
        pass

    active_layer = parameters['INPUT']

    # Run some checks and prepare the layer for in-place execution by:
    # - getting the active layer and checking that it is a vector
    # - making the layer editable if it was not already
    # - selecting all features if none was selected
    # - checking in-place support for the active layer/alg/parameters
    # If one of the check fails and raise_exceptions is True an exception
    # is raised, else the execution is aborted and the error reported in
    # the feedback
    try:
        if active_layer is None:
            raise QgsProcessingException(tr("There is not active layer."))

        if not isinstance(active_layer, QgsVectorLayer):
            raise QgsProcessingException(
                tr("Active layer is not a vector layer."))

        if not active_layer.isEditable():
            if not active_layer.startEditing():
                raise QgsProcessingException(
                    tr("Active layer is not editable (and editing could not be turned on)."
                       ))

        if not alg.supportInPlaceEdit(active_layer):
            raise QgsProcessingException(
                tr("Selected algorithm and parameter configuration are not compatible with in-place modifications."
                   ))
    except QgsProcessingException as e:
        if raise_exceptions:
            raise e
        QgsMessageLog.logMessage(str(sys.exc_info()[0]), 'Processing',
                                 Qgis.Critical)
        if feedback is not None:
            feedback.reportError(getattr(e, 'msg', str(e)), fatalError=True)
        return False, {}

    if not active_layer.selectedFeatureIds():
        active_layer.selectAll()

    # Make sure we are working on selected features only
    parameters['INPUT'] = QgsProcessingFeatureSourceDefinition(
        active_layer.id(), True)
    parameters['OUTPUT'] = 'memory:'

    req = QgsFeatureRequest(QgsExpression(r"$id < 0"))
    req.setFlags(QgsFeatureRequest.NoGeometry)
    req.setSubsetOfAttributes([])

    # Start the execution
    # If anything goes wrong and raise_exceptions is True an exception
    # is raised, else the execution is aborted and the error reported in
    # the feedback
    try:
        new_feature_ids = []

        active_layer.beginEditCommand(alg.displayName())

        # Checks whether the algorithm has a processFeature method
        if hasattr(alg, 'processFeature'):  # in-place feature editing
            # Make a clone or it will crash the second time the dialog
            # is opened and run
            alg = alg.create()
            if not alg.prepare(parameters, context, feedback):
                raise QgsProcessingException(
                    tr("Could not prepare selected algorithm."))
            # Check again for compatibility after prepare
            if not alg.supportInPlaceEdit(active_layer):
                raise QgsProcessingException(
                    tr("Selected algorithm and parameter configuration are not compatible with in-place modifications."
                       ))
            field_idxs = range(len(active_layer.fields()))
            iterator_req = QgsFeatureRequest(active_layer.selectedFeatureIds())
            iterator_req.setInvalidGeometryCheck(
                context.invalidGeometryCheck())
            feature_iterator = active_layer.getFeatures(iterator_req)
            step = 100 / len(active_layer.selectedFeatureIds()
                             ) if active_layer.selectedFeatureIds() else 1
            for current, f in enumerate(feature_iterator):
                feedback.setProgress(current * step)
                if feedback.isCanceled():
                    break

                # need a deep copy, because python processFeature implementations may return
                # a shallow copy from processFeature
                input_feature = QgsFeature(f)
                new_features = alg.processFeature(input_feature, context,
                                                  feedback)
                new_features = QgsVectorLayerUtils.makeFeaturesCompatible(
                    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)])
                    if not active_layer.addFeatures(new_features):
                        raise QgsProcessingException(
                            tr("Error adding processed features back into the layer."
                               ))
                    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

            # There is no way to know if some features have been skipped
            # due to invalid geometries
            if context.invalidGeometryCheck(
            ) == QgsFeatureRequest.GeometrySkipInvalid:
                selected_ids = active_layer.selectedFeatureIds()
            else:
                selected_ids = []

            results, ok = alg.run(parameters, context, feedback)

            if ok:
                result_layer = QgsProcessingUtils.mapLayerFromString(
                    results['OUTPUT'], context)
                # TODO: check if features have changed before delete/add cycle

                new_features = []

                # Check if there are any skipped features
                if context.invalidGeometryCheck(
                ) == QgsFeatureRequest.GeometrySkipInvalid:
                    missing_ids = list(
                        set(selected_ids) - set(result_layer.allFeatureIds()))
                    if missing_ids:
                        for f in active_layer.getFeatures(
                                QgsFeatureRequest(missing_ids)):
                            if not f.geometry().isGeosValid():
                                new_features.append(f)

                active_layer.deleteFeatures(active_layer.selectedFeatureIds())

                for f in result_layer.getFeatures():
                    new_features.extend(
                        QgsVectorLayerUtils.makeFeaturesCompatible(
                            [f], active_layer))

                # Get the new ids
                old_ids = set([f.id() for f in active_layer.getFeatures(req)])
                if not active_layer.addFeatures(new_features):
                    raise QgsProcessingException(
                        tr("Error adding processed features back into the layer."
                           ))
                new_ids = set([f.id() for f in active_layer.getFeatures(req)])
                new_feature_ids += list(new_ids - old_ids)

        active_layer.endEditCommand()

        if ok and new_feature_ids:
            active_layer.selectByIds(new_feature_ids)
        elif not ok:
            active_layer.rollBack()

        return ok, results

    except QgsProcessingException as e:
        active_layer.endEditCommand()
        active_layer.rollBack()
        if raise_exceptions:
            raise e
        QgsMessageLog.logMessage(str(sys.exc_info()[0]), 'Processing',
                                 Qgis.Critical)
        if feedback is not None:
            feedback.reportError(getattr(e, 'msg', str(e)), fatalError=True)

    return False, {}
Пример #11
0
def execute_in_place_run(alg, active_layer, parameters, context=None, feedback=None, raise_exceptions=False):
    """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 active_layer: the editable layer
    :type active_layer: QgsVectoLayer
    :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)

    if active_layer is None or not active_layer.isEditable():
        raise QgsProcessingException(tr("Layer is not editable or layer is None."))

    if not alg.supportInPlaceEdit(active_layer):
        raise QgsProcessingException(tr("Selected algorithm and parameter configuration are not compatible with in-place modifications."))

    parameters['OUTPUT'] = 'memory:'

    try:
        new_feature_ids = []

        active_layer.beginEditCommand(alg.displayName())

        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
            # Make a clone or it will crash the second time the dialog
            # is opened and run
            alg = alg.create()
            if not alg.prepare(parameters, context, feedback):
                raise QgsProcessingException(tr("Could not prepare selected algorithm."))
            # Check again for compatibility after prepare
            if not alg.supportInPlaceEdit(active_layer):
                raise QgsProcessingException(tr("Selected algorithm and parameter configuration are not compatible with in-place modifications."))
            field_idxs = range(len(active_layer.fields()))
            feature_iterator = active_layer.getFeatures(QgsFeatureRequest(active_layer.selectedFeatureIds())) if parameters['INPUT'].selectedFeaturesOnly else active_layer.getFeatures()
            step = 100 / len(active_layer.selectedFeatureIds()) if active_layer.selectedFeatureIds() else 1
            for current, f in enumerate(feature_iterator):
                feedback.setProgress(current * step)
                if feedback.isCanceled():
                    break

                # need a deep copy, because python processFeature implementations may return
                # a shallow copy from processFeature
                input_feature = QgsFeature(f)
                new_features = alg.processFeature(input_feature, context, feedback)
                new_features = QgsVectorLayerUtils.makeFeaturesCompatible(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)])
                    if not active_layer.addFeatures(new_features):
                        raise QgsProcessingException(tr("Error adding processed features back into the layer."))
                    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)

            if ok:
                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.extend(QgsVectorLayerUtils.
                                        makeFeaturesCompatible([f], active_layer))

                # Get the new ids
                old_ids = set([f.id() for f in active_layer.getFeatures(req)])
                if not active_layer.addFeatures(new_features):
                    raise QgsProcessingException(tr("Error adding processed features back into the layer."))
                new_ids = set([f.id() for f in active_layer.getFeatures(req)])
                new_feature_ids += list(new_ids - old_ids)

        active_layer.endEditCommand()

        if ok and new_feature_ids:
            active_layer.selectByIds(new_feature_ids)
        elif not ok:
            active_layer.rollBack()

        return ok, results

    except QgsProcessingException as e:
        active_layer.endEditCommand()
        active_layer.rollBack()
        if raise_exceptions:
            raise e
        QgsMessageLog.logMessage(str(sys.exc_info()[0]), 'Processing', Qgis.Critical)
        if feedback is not None:
            feedback.reportError(getattr(e, 'msg', str(e)))

    return False, {}