QGIS Custom function example

Rafael Pinheiro Machado
Rafael Pinheiro Machado

January 07, 2023

QGIS Custom function example

What if you have a set of layers defined by the same geographical boundaries, and you would like to show different labels according to the top-most checked layer?


The following custom function for QGIS 3.X allows the user to do so.


Here there is an example of a function that can be used to show labels according to the top-most layer that is visible (or checked)


The code below contains comments which will help you to have a better understanding of what's being done at each command line or block.


            #######################################
            #                                     #
            #   Custom Qgis 3.X function          #
            #   Name: custom_label                #
            #   Author: Rafael Pinheiro Machado   #
            #   Date: Jan/23                      #
            #                                     #
            #######################################


from qgis.core import *
from qgis.gui import *

@qgsfunction(args='auto', group='Custom', referenced_columns=[])


def custom_label(feature, parent):
    """
    This function can be used to meet specific labeling needs. 
    It might be useful, for example, if label values are stored in a single vectorial layer to
    label other layers, either vector or raster layers.
    
    I particularily find it useful to represent raster properties for raster layers which are
    defined by the same geographic boundaries.
    
    The idea is to show labels for the top-most visible (or checked) layer inside the project
    layer tree, amongst the layer names presented in the predetermined list. 
    If none is selected, there will be no labels shown.
    
    Both predetermined lists (fields and layers) must be edited in order to meet your project
    demands.
        
    The function does not take any parameters.
    
    : returns: The name of the field from where the values will be shown.


    in order to use it efficiently, you should use <b>eval</b>.<br>
    To format it appropriately, you can type in:<br>
    <b>eval(custom_label())</b>.<br>
    By doing so, you assure it will be appropriately rounded, and it will always show one decimal.
    """
        
    # List containing fields from a predetermined vector feature
    field_list = ['Field_a',
                  'Field_b',
                  'Field_c',
                  'Field_d',
                  'Field_n']

    # List containing layers whose properties are represented by the fields above listed
    # Each list item is also a list. You can insert, for example, multiple strings to keep
    # more flexible if layer names are not exact but should have more othan one string
    # from the list.
    # Both lists should have the same size.
    layer_name_list = [['Layer_A'],
                       ['Layer_B'],
                       ['Layer_C'],
                       ['Layer_D'],
                       ['Layer_N']]
    
    # string variable to store the field name from field_list returned by the function
    field_to_show = ""


    # create a list to store all layers (visible or not) in the canvas
    layers_list = []


    # get access to the project layer tree.
    root = QgsProject.instance().layerTreeRoot()
    
    # recursive function to iterate over groups and their subgroups, returning all 
    # layers in the projectlayer tree
    def get_group_layers(group):
        for child in root.children():
            if isinstance(child, QgsLayerTreeGroup):
                get_group_layers(child)
            elif isinstance(child, QgsLayerTreeLayer):
                layers_list.append(child.name())
    
    # this 'for' loop executes the get_group_layer function, appending layer names
    # and iterating over groups and subgroups
    for child in root.children():
        if isinstance(child, QgsLayerTreeGroup):
            get_group_layers(child)
        elif isinstance(child, QgsLayerTreeLayer):
            layers_list.append(child.name())
    
    # iterate over all items in layer list ...
    for item in range(0, len(layers_list)):
        l = QgsProject().instance().mapLayersByName(layers_list[item])
        node = root.findLayer(l[-1])
        # then identify if the layer is checked (or visible) ...
        if node.isVisible() == True:
            # to compare agains the layer_name_list ...
            for layer in range(0, len(layer_name_list)):
                # returning then the corresponding field
                if layer_name_list[layer][0] in l[0].name():
                    if layer_name_list[layer][1] in l[0].name():
                        field_to_show = field_list[layer]
                        break
                    else:
                        field_to_show = ""
                if field_to_show != "":
                    break
            if field_to_show != "":
                break
        if field_to_show != "":
            break
    
    # returns the field to be shown. If "", it won't show anything.
    return feature[field_to_show]


Remember you should edit it to meet your needs.


Due to a bug in QGIS 3.X (at least in the version I tested this code (3.22.14), this function returns a string, not the field itself.

Ideally, it would return the following:


    return feature[field_to_show]


However, by doing so, the function will not be shown on the canvas.


A workaround for it is to use the following expression:


eval(custom_label)


for numeric values, you can format it to show only n decimals. The example below uses the format_number() function to show 2 decimals.


format_number(eval(custom_label()),2)


For more details regarding custom functions in QGIS 3.X, click here.













Tools used

QGIS 3.22.14 'Białowieża'

Plug-ins used

PyQGISPython

tags

LabelingPyQgisPython

You might also like

Join the community!

We're a place where geospatial professionals showcase their works and discover opportunities.