3

I struggle with creating a deep copy of a memory layer in QGIS. Whenever I modify the layer copy, the original gets modified, too. Can anybody point me to the mistake I am making in the following code:

def createMemoryLayer(self, layerName, sourceLayer):
    # create empty memory layer
    memoryLayer = QgsVectorLayer(
        QgsWkbTypes.geometryDisplayString(sourceLayer.geometryType()) +
        "?crs=" + sourceLayer.crs().authid() +
        "&index=yes",
        layerName,
        "memory"
    )
    memoryLayerDataProvider = memoryLayer.dataProvider()

    # copy the table structure
    memoryLayer.startEditing()
    memoryLayerDataProvider.addAttributes(
        sourceLayer.fields().toList()
    )
    memoryLayer.commitChanges()

    # copy the features
    #memoryLayerDataProvider.addFeatures(
    #    [QgsFeature(feature) for feature in sourceLayer.getFeatures()]
    #)

    # DEEP copy the features
    features= []
    for feature in sourceLayer.getFeatures():
        f = QgsFeature(feature)
        f.setGeometry(QgsGeometry(feature.geometry()))
        features.append(f)
    memoryLayerDataProvider.addFeatures(features)

    return memoryLayer

see the commented-out lines for what I had thought to fix with the lines thereafter. According to the API docs, QgsFeature(QgsFeature) and QgsGeometry(QgsGeometry) both create deep copy of the original object.

Any clues?

  • tested in QGIS 2.18.x and 2.99.x – christoph Mar 14 '17 at 17:37
  • In QGIS 2.18.4 I changed line 4/5 in "Polygon?crs=" + sourceLayer.crs().authid() +, then used QgsMapLayerRegistry.instance().addMapLayer(createMemoryLayer(None, 'dest', QgsMapLayerRegistry.instance().mapLayersByName('source')[0])) interactively to copy a memory polygon layer. I could then edit both layers independently. Which szenario did you test? – Redoute Mar 15 '17 at 14:12
  • My use case is copying an input layer to a “work” layer, on which a plugin processes in a separate QThread. Note that I only add it to the mapLayerRegistry after the processing has finished. The curious thing is that it seems to depend on whether the original layer is a memory layer too (all works as expected, if the input layer is a shape file) Just compiling qgis-2.18 to test your code, thanks! – christoph Mar 16 '17 at 13:40
  • I just stumbled over a first bread crump directing me into the right direction: the QgsAbstractGeometry objects remain the same – even if the QgsFeatures and QgsGeometrys change. [repr(f.geometry().geometry()) for f in memoryLayer.getFeatures()]and [repr(f.geometry().geometry()) for f in sourceLayer.getFeatures()] return the same memory addresses. Working on it – I’m going to report back once I know more – christoph Mar 16 '17 at 15:13
1

tl;dr

The error was not in the code presented. Rather, later on, I modified the features’ QgsAbstractGeometry without first creating a deep copy of it. The trick is the .clone():

abstractGeometry = feature.geometry().geometry().clone()
# … do something with the geometry
feature.setGeometry(abstractGeometry)

long answer

I still have not figured out how exactly QGIS copies layers/features/geometries/abstractGeometries.

It seems though, that upon copying a QgsMemoryLayer’s features to another QgsMemoryLayer, the QgsAbstractGeometrys are kept as mere references and in effect point to the same place in memory.

My plugin’s script then changed single vertices of the QgsAbstractGeometrys:

abstractGeometry = features[featureId].geometry()
abstractGeometry.moveVertex(
    QgsVertexId(p, r, v, QgsVertexId.SegmentVertex),
    QgsPointV2(x, y)
)
features[featureId].setGeometry(abstractGeometry)

… and by that inadvertently changed the QgsAbstractGeometry which actually was shared between the original and the copied memory layer.

There is nothing to be changed in the code listed in the question. Rather, a deep copy of the QgsAbstractGeometry is needed before it is modified (by calling its clone() method). The above code needs only minor modification:

abstractGeometry = features[featureId].geometry().clone() abstractGeometry.moveVertex( QgsVertexId(p, r, v, QgsVertexId.SegmentVertex), QgsPointV2(x, y) ) features[featureId].setGeometry(abstractGeometry)

0

You do not need to start an edit session for copying attributes/features. After adding the attributes you should update the fields and I think your also over complicating adding the features, try:

# copy the table structure
memoryLayerDataProvider.addAttributes(sourceLayer.fields().toList())
memoryLayer.updateFields()

features= []
for feature in sourceLayer.getFeatures():
    features.append(feature)
memoryLayerDataProvider.addFeatures(features)
  • Sorry, but that does not address my question. I know how to create a memory layer in general, and your for-loop just replaces my pythonic list comprehension. Thanks for reminding me of the updateFields() method, though. – christoph Mar 16 '17 at 13:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.