[GIS] Specifying input layer in custom QGIS function to rank features

functionlayerspythonqgisqgis-custom-function

How do I specify the input layer so it refers to the layer from which the function was called (without having to explicitly identify it by name)?

I have written a custom function to use in the expression editor. It returns the rank of each feature based on the specified attribute. For example, if I want to highlight the smallest 10 cities I can use an expression along the lines of: top_rank('citypopulation')<=10
to filter the top 10 ranked features.

The function sort of works, however, if I use it on different layers at the same time the outputs for each layer change. After some consideration, I think it might be due to using aLayer = qgis.utils.iface.activeLayer() to specify the layer and that each time I select a new layer it changes aLayer throughout the project.

from qgis.core import *
from qgis.gui import *
from qgis.utils import qgsfunction
from qgis.core import QGis

# this line puts  function into 'Custom' group in the expression builder
@qgsfunction(args="auto", group='Custom')

# custom functions must take (fields,feature,parent as arguments)
# usage is : top_rank('attribute_name')
def top_rank(field, feature, parent):
    aLayer = qgis.utils.iface.activeLayer()
    target_field=feature.fieldNameIndex(field) # gets index of target field - checked
    f_id=feature.id() #gets target feature id - checked
    aList= [(feat[target_field], feat.id()) for feat in aLayer.getFeatures()]
    aList.sort()  # sort list of tuples
    i = 1
    row_index=0
    for feat in aList:
        if feat[1]==f_id:
            row_index=i
        i  += 1
    return(row_index)

(I initially asked this on Stack Overflow but I think it is more relevant here)

(Previously, I have written a function that adds a new column to the attribute table with rank based on an attribute but I don't want to have to add a new column containing the rank for each attribute individually.)

Best Answer

Thanks to Nathan W, I have got the function working properly. This is what I did:

  • I upgraded to QGIS 2.14 (I was on 2.10).
  • I changed the first two lines of the function (the rest remained the same):

    # usage is now: top_rank(@layer_name,'attribute_name')        
    def top_rank(layer, field, feature, parent):
        aLayer = QgsMapLayerRegistry.instance().mapLayersByName(layer)[0]
    
Related Question