3

I want to copy-paste values from one feature in a layer to another feature in another layer, for this, I like to have both layers activated to be able to select features from both layers.

Just as an example: I have a QGIS project with two layers running ('source' and 'target') and this script:

from qgis.core import *

def onFeatureIdentified(feature):
    fid = feature.id()
    print ("feature selected : " + str(fid))

layer = iface.activeLayer()
#print(layer.name())
mc=iface.mapCanvas()
mapTool = QgsMapToolIdentifyFeature(mc)
print(mapTool)
mapTool.setLayer(layer)
mc.setMapTool(mapTool)
mapTool.featureIdentified.connect(onFeatureIdentified)

This script does only work when I first activate a layer in the TOC in QGIS, then all applies only to that layer. However, I would like to be able to 'switch' to the other layer, so that when I click on the other (till then inactive) layer, it automatically activates the other layer.

Is there a way to do this?

There is already the Mulitlayer Select tool, which does what I want(so you can select features from more layers), but when I enable it, then the script doesn't work anymore (I think it levers out the QgsMapToolIdentifyFeature function).

So I need another way to activate both layers.

1 Answer 1

6

The best way to work with any non-trivial implementation of a QgsMapTool is to create a sub-class. Below is a fairly minimal example. The way this deals with changing between source and target layers, is to connect a slot method to the currentLayerChanged signal of the qgisinterface (iface) class and, in that slot method, set the new active layer (if it is a vector layer) to the map tool. We then override the deactivate() method to disconnect the signal/slot when the tool is deactivated.

This basic functionality of this map tool is to copy the value of a field called "Name" from the first identified feature and copy it to the field called "Name" in the second identified feature. Obviously you will need to edit as required to suit your use case. Also note- we are using an edit buffer here , so after each paste, you either save or undo layer edits. If you wanted to you could write changes directly to the dataProvider() e.g.

self.layer.dataProvider().changeAttributeValues({feat.id(): {self.layer.fields().lookupField('Name'): self.value}})

So to use:

  • Run code to activate map tool.

  • Select the source layer

  • Click on the source feature

  • Select the target layer

  • Click on the target feature

  • Save or discard the edit

class MapToolCopyPaste(QgsMapToolIdentifyFeature):
    
    def __init__(self, iface):
        self.iface = iface
        self.canvas = self.iface.mapCanvas()
        super(MapToolCopyPaste, self).__init__(self.canvas)
        self.layer = iface.activeLayer()
        if isinstance(self.layer, QgsVectorLayer):
            self.setLayer(self.layer)
        # Connect to currentLayerChanged signal of qgisinterface class
        self.iface.currentLayerChanged.connect(self.layer_changed)
        self.featureIdentified.connect(self.feature_identified)
        
        self.operation = 'copying'
        
        self.value = None
        
    def layer_changed(self, lyr):
        # Set the active (vector) layer to the map tool instance
        if isinstance(self.layer, QgsVectorLayer):
            self.layer = lyr
            self.setLayer(self.layer)
        
    def feature_identified(self, feat):
        print(feat.id())
        if self.operation == 'copying':
            self.operation = 'pasting'
            self.value = feat['Name']
        elif self.operation == 'pasting':
            self.operation = 'copying'
            if self.value:
                if not self.layer.isEditable():
                    self.layer.startEditing()
                self.layer.changeAttributeValue(feat.id(), self.layer.fields().lookupField('Name'), self.value)
                self.layer.triggerRepaint()
        
    def deactivate(self):
        # This method is called automatically when the tool is deactivated
        # print('tool_deactivated')
        # Disconnect the slot from the currentLayerChanged signal
        self.iface.currentLayerChanged.disconnect(self.layer_changed)
        
map_tool = MapToolCopyPaste(iface)
iface.mapCanvas().setMapTool(map_tool)
1
  • Thank you for the answer! I will try this out as soon as I will need this functionality again!
    – i.i.k.
    Commented Jul 5, 2023 at 21:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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