5

I am trying to write a piece of PyQGIS code that runs after a mouse click to collect the Map Coordinate position, and the value of a polygon vector layer within the project under the cursor position.

The vector layer is called 'Parish Boundaries'. The attribute I would like the value of is called 'name'. The first bit in the canvasReleaseEvent makes sure that the correct layer is selected, regardless of how its named.

I then have got very confused as to how to get the attribute value.

So far, I have the following:

class FaultTool(QgsMapTool):

def __init__(self, canvas):
    QgsMapTool.__init__(self, canvas)
    self.canvas = canvas

def canvasReleaseEvent(self, event):

    parish_layer = None
    layers = self.canvas.layers()
    for each_layer in layers:
        if each_layer.source() == "C:/MapData/QGIS/Base Maps/Other Layers/Parish Boundaries.shp":
            parish_layer = each_layer.name()


    x = event.pos().x()
    y = event.pos().y()
    parishName = # This is the bit I can't work out!
                 # something like....event.pos().attributes()[0]...?

    point = self.canvas.getCoordinateTransform().toMapCoordinates(x, y)

3 Answers 3

6

What you are trying to do is identify a feature of a particular layer and then get a value of a specific attribute.

To do that you will need to use the QgsMapToolIdentify which allows you to identify layers on the map and it returns a set of features

features = QgsMapToolIdentify(self.canvas).identify(event.x(), event.y(), self.canvas.layers(),QgsMapToolIdentify.TopDownStopAtFirst)

And after getting the features then you can easily get the value of any attribute you want by the usual methode feature['attributeName']

so your code should look something like this

def canvasReleaseEvent(self, event):

    parish_layer = None
    layers = self.canvas.layers()
    for each_layer in layers:
        if each_layer.source() == "C:/MapData/QGIS/Base Maps/Other Layers/Parish Boundaries.shp":
            # Here you need to get the layer not the name
            parish_layer = each_layer
            break

    #you neet to set which layers will you identify here which is in your case is just 'parish_layer' 
    features = QgsMapToolIdentify(self.canvas).identify(event.x(), event.y(), [parish_layer], QgsMapToolIdentify.TopDownStopAtFirst)

    if len(features) > 0:
        #here you get the selected feature
        feature = features[0].mFeature
        #And here you get the attribute's value
        parishName = feature['name']


    x = event.pos().x()
    y = event.pos().y()

    point = self.canvas.getCoordinateTransform().toMapCoordinates(x, y)
3
  • Thank you so much to both xunilk and Chiller - there's always more than one way it seems!
    – Tom
    Commented Jun 15, 2017 at 15:27
  • @Chiller yal khshine :D Commented Jan 12, 2019 at 20:54
  • @bRIMOsBor nta howa lekhchine XD
    – Chiller
    Commented Jan 13, 2019 at 7:20
3

You can also use QgsMapToolEmitPoint class to generate a click mouse point and search specifically in 'Parish Boundaries' shapefile for this point within feature geometry. I tried out next code:

from qgis.gui import QgsMapToolEmitPoint

def display_point(point, mouse_button): 

    coords = "Map Coordinates: {:.4f}, {:.4f}".format(point.x(), point.y())

    print coords

    layer = iface.activeLayer()
    feats = [ feat for feat in layer.getFeatures() ]

    geo_pt = QgsGeometry.fromPoint(QgsPoint(point.x(), point.y()))

    id = -1

    for feat in feats:
        if geo_pt.within(feat.geometry()):
            id = feat.id()
            break

    if id != -1:
        print feats[id].attribute('name')
    else:
        print "no feature selected"

# a reference to our map canvas 
canvas = iface.mapCanvas() 

# this QGIS tool emits as QgsPoint after each click on the map canvas
pointTool = QgsMapToolEmitPoint(canvas)

pointTool.canvasClicked.connect(display_point)

canvas.setMapTool(pointTool)

with a shapefile arbitrarily named 'Parish Boundaries' and a 'name' field (see next image).

enter image description here

After running the script, each mouse click at the Map Canvas area is printed at Python Console of QGIS as a sequence of Map Coordinates and attribute 'name' values; as can be observed at above image. First point was clicked outside of any feature of 'Parish Boundaries'. It works.

0
2

Thought it might be useful to others if I post the code that worked for me. The code posted above worked well - thanks.

In the end I decided to just find the nearest feature in two layers (in this case a layer of Parish Boundaries and another of Path Numbers). This meant a result was always returned, even if the user clicked outside the boundary.

If neither of the layers are open, it opens them up invisibly (using a style qml) then closes them down once the attribute is gained.

There is probably a neater way of doing this, but here it is:

def canvasReleaseEvent(self, event):

    fx = event.pos().x()
    fy = event.pos().y()

    location = self.canvas.getCoordinateTransform().toMapCoordinates(fx, fy)
    boundbox = QgsRectangle(4999.99,4999.69,660000.06,1225000.12)

    combined = str(location)

    EastingString = str(combined[1:7])
    NorthingString = str(combined[8:14])

    Easting = int(EastingString)
    Northing = int(NorthingString)

    shortestDistance = float("inf")
    closestFeatureId = -1

    parish_layer = None
    path_layer = None
    parish_close = False
    path_close = False

    #Checks if the Parish Layer is Open and assigns it to parish_layer

    for layer in QgsMapLayerRegistry.instance().mapLayers().values():
        if "C:/MapData/QGIS/Base Maps/Other Layers/Parish Boundaries.shp" in layer.source():
            parish_layer = layer
            break
    #If the Parish Layer is not open then it trys to open it up and assigns it to parish_layer

    if str(parish_layer) == "None":
        layer = QgsVectorLayer("C:/MapData/QGIS/Base Maps/Other Layers/Parish Boundaries.shp", "Parish Boundaries", "ogr")
        QgsMapLayerRegistry.instance().addMapLayer(layer)
        layer.loadNamedStyle('C:/MapData/QGIS/Base Maps/Other Layers/blankparish.qml')
        layer.triggerRepaint()
        parish_close = True

        for layer in QgsMapLayerRegistry.instance().mapLayers().values():
            if "C:/MapData/QGIS/Base Maps/Other Layers/Parish Boundaries.shp" in layer.source():
                parish_layer = layer
                break

    #Check that the layer is open and then take the point value

    if str(parish_layer) != "None":
        pPnt = QgsGeometry.fromPoint(QgsPoint(Easting, Northing))
        for f in parish_layer.getFeatures():
            pathdist = f.geometry().distance(QgsGeometry(pPnt))
            if pathdist < shortestDistance:
                shortestDistance = pathdist
                closestFeatureId = f.id()

        testlength = str(closestFeatureId)

    #Grab the first attribute as thats the Parish Name. Remove the letters CP from the end if they are present

    if len(testlength) > 0:
        fid = closestFeatureId
        iterator = parish_layer.getFeatures(QgsFeatureRequest().setFilterFid(fid))
        featuree = next(iterator)
        attrs = featuree.attributes()
        parishName = (attrs[0])
        lastTwo = parishName[-2:]
        if lastTwo == "CP":
            parishName = parishName[:-3]


    else:
        parishName = None

    if parish_close == True:
        QgsMapLayerRegistry.instance().removeMapLayer(parish_layer)

    #Repeat the process above for the path layer

    shortestDistance = float("inf")
    closestFeatureId = -1
    layer = None

    for layer in QgsMapLayerRegistry.instance().mapLayers().values():
        if "C:/MapData/QGIS/Rights of Way/PROW.shp" in layer.source():
            path_layer = layer
            break
    #If the Path Layer is not open then it trys to open it up and assigns it to path_layer

    if str(path_layer) == "None":
        layer = QgsVectorLayer("C:/MapData/QGIS/Rights of Way/PROW.shp", "PROW", "ogr")
        QgsMapLayerRegistry.instance().addMapLayer(layer)
        layer.loadNamedStyle('C:/MapData/QGIS/Base Maps/Rights of Way/blankpath.qml')
        layer.triggerRepaint()
        path_close = True


    for layer in QgsMapLayerRegistry.instance().mapLayers().values():
        if "C:/MapData/QGIS/Rights of Way/PROW.shp" in layer.source():
            path_layer = layer
            break

    #Check that the layer is open and then take the point value

    if str(path_layer) != "None":
        pPnt = QgsGeometry.fromPoint(QgsPoint(Easting, Northing))
        for f in path_layer.getFeatures():
            pathdist = f.geometry().distance(QgsGeometry(pPnt))
            if pathdist < shortestDistance:
                shortestDistance = pathdist
                closestFeatureId = f.id()

        testlength = str(closestFeatureId)

    if len(testlength) > 0:
        fid = closestFeatureId
        iterator = path_layer.getFeatures(QgsFeatureRequest().setFilterFid(fid))
        featuree = next(iterator)
        attrs = featuree.attributes()
        pathName = (attrs[0])

    else:
        pathName = None

    if path_close == True:
        QgsMapLayerRegistry.instance().removeMapLayer(path_layer)


    #As a test, just display the info in a message box


    if boundbox.contains(location):

        QMessageBox.information(None, "Ticket Info", "Parish: " + str(parishName) + " Path: " + str(pathName) + " Easting: " + str(Easting) + " Northing: " + str(Northing))
    else:
        QMessageBox.information(None, "Fault Can't Be Placed", "Point out of bounds")

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.