QGIS PyQGIS – Editing Bug When Adding Layers with Python Script

editingpyqgis

Does anyone know why when I’m editing my polygon (geojson) that the vertices return back to the original spot. I add them through a python script  with QgsProject.instance().addMapLayer(). I start an edit mode, move the vertex, then go move the next vertex and then first vertex moves goes back to its original position.
 
When I add the layer ( created from the python script) manually from the file explorer, I can edit the shape as expected.
 
I tried running my code directly in the QGIS python console and the editing works as expected.
 
So I know it has something to do with running the script from the Processing Toolbox -> Scripts.  

In the image below you can see the red outline of the original polygon when I hover over the shape. So what I did was moved vertex from (1) to (2) (which works) but then when I go to move my next vertex it still uses the old shape and when I move another vertex it will move the original back .

enter image description here

Even with this simple code. I still get the same bug.

enter image description here

from qgis.PyQt.QtCore import (QCoreApplication, QVariant)
from qgis.core import (QgsProcessing,
                       QgsFeatureSink,
                       QgsProcessingException,
                       QgsProcessingAlgorithm,
                       QgsProcessingParameterFeatureSource,
                       QgsProcessingParameterFeatureSink,
                       QgsProcessingParameterString,
                       QgsProcessingParameterField,
                       QgsProcessingParameterBoolean,
                       QgsProcessingParameterFolderDestination,
                       QgsProcessingParameterFile,
                       QgsProcessingParameterVectorLayer,
                       QgsProcessingParameterEnum,
                       QgsVectorLayer,
                       QgsProject,
                       QgsVectorDataProvider,
                       QgsDistanceArea,
                       QgsField,
                       QgsUnitTypes,
                       QgsVectorFileWriter)
from qgis.core import QgsProcessing
from qgis.core import QgsProcessingAlgorithm
from qgis.core import QgsProcessingMultiStepFeedback
from qgis.core import QgsProcessingParameterFile


class CheckGcpPoints(QgsProcessingAlgorithm):
    
    def initAlgorithm(self, config=None):
        self.addParameter(QgsProcessingParameterFile('input_csv', 'Properties CSV', behavior=QgsProcessingParameterFile.File))

        #self.addParameter(QgsProcessingParameterBoolean('thermal_parameter', 'Thermal', defaultValue=False))
        # Use a multi-step feedback, so that individual child algorithm progress reports are adjusted for the
        # overall progress through the model
    
    def processAlgorithm(self, parameters, context, model_feedback):
        registry = QgsProject.instance()
        
        # Define qgis instance

        # # Create Property layer
        property_layer = QgsVectorLayer(parameters['input_csv'],'Properties')
        registry.addMapLayer(property_layer)
        
        results = {}
        return ()

    def name(self):
        return 'PropertyEstimates2'

    def displayName(self):
        return 'Property Estimates2'

    def group(self):
        return 'SITE scripts'

    def groupId(self):
        return 'SITE scripts'

    def createInstance(self):
        return CheckGcpPoints()

Another case with Points.
You can see that I moved the point to the right, but when I try to move it again the point is still in the original place. I have to hover with my mouse to see the red original vertex as if the point didnt really move. enter image description here

Best Answer

I can see a few potential problems with your script. Firstly, you have not passed a provider argument to your vector layer constructor. Secondly, your processAlgorithm() method is returning an empty tuple instead of a dictionary. And finally, an important piece of knowledge is that processing algorithms by default, are run in a background thread. Therefore, doing non thread-safe operations in the processAlgorithm() method (such as adding layers to the project) will often result in (at worst) crashes or at least unpredictable behaviour. To avoid this problem, it is necessary to re-implement the flags() method and return the QgsProcessingAlgorithm.FlagNoThreading flag.

The simplified algorithm below works for me and I am able to successfully edit vertices of the loaded layer.

from qgis.core import (QgsProcessingAlgorithm,
                    QgsProcessingParameterFile,
                    QgsProject,
                    QgsVectorLayer)

class CheckGcpPoints(QgsProcessingAlgorithm):

    def initAlgorithm(self, config=None):
        self.addParameter(QgsProcessingParameterFile('input_geojson', 'Properties GEOJSON', behavior=QgsProcessingParameterFile.File, extension='geojson'))

    def processAlgorithm(self, parameters, context, model_feedback):
        project = QgsProject.instance()
        property_layer = QgsVectorLayer(parameters['input_geojson'],'Properties', 'ogr')
        project.addMapLayer(property_layer)
        
        return {}

    def flags(self):
        return QgsProcessingAlgorithm.FlagNoThreading

    def name(self):
        return 'PropertyEstimates2'

    def displayName(self):
        return 'Property Estimates2'

    def group(self):
        return 'SITE scripts'

    def groupId(self):
        return 'SITE scripts'

    def createInstance(self):
        return CheckGcpPoints()

I strongly encourage you to thoroughly read the documentation about Writing new Processing algorithms as Python scripts, especially the section on Flags.

P.S. there is one other slightly confusing element to your question in that you state you are trying to load a geojson, but your script makes several references to csv. This is confusing because delimited text layers cannot be edited directly in QGIS. Therefore, I have referenced only geojson in my answer and restricted the input file extension to 'geojson'.

Further Explanation

Of course, it doesn't make much sense to write a processing script just to load a layer. If you are doing further processing, you can just define an output parameter such as a QgsProcessingParameterFeatureSink for your algorithm, and pass it to the 'OUTPUT' parameter of the processing.run() call. This way, any output layer, either temporary or file, will be handled/loaded safely, so you don't need to return the no threading flag. For example, here we run Fix Geometries on the input layer, and output the result to the feature sink.

from qgis.core import (QgsProcessingAlgorithm,
                    QgsProcessingParameterFile,
                    QgsProcessingParameterFeatureSink,
                    QgsProject,
                    QgsVectorLayer)

import processing

class CheckGcpPoints(QgsProcessingAlgorithm):

    def initAlgorithm(self, config=None):
        self.addParameter(QgsProcessingParameterFile('input_geojson', 'Properties GEOJSON', behavior=QgsProcessingParameterFile.File, extension='geojson'))
        self.addParameter(QgsProcessingParameterFeatureSink('output', 'Result layer'))

    def processAlgorithm(self, parameters, context, model_feedback):
        params = {'INPUT': parameters['input_geojson'],
                'OUTPUT': parameters['output']}
        
        fixed = processing.run("native:fixgeometries", params, context=context, feedback=model_feedback, is_child_algorithm=True)
                
        return {'RESULT': fixed['OUTPUT']}

    def name(self):
        return 'PropertyEstimates2'

    def displayName(self):
        return 'Property Estimates2'

    def group(self):
        return 'SITE scripts'

    def groupId(self):
        return 'SITE scripts'

    def createInstance(self):
        return CheckGcpPoints()
Related Question