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)
PolyGeo
  • 65,136
  • 29
  • 109
  • 338
Tom
  • 111
  • 1
  • 5

3 Answers3

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)
Chiller
  • 226
  • 2
  • 7
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.

xunilk
  • 29,891
  • 4
  • 41
  • 80
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")
Tom
  • 111
  • 1
  • 5