analysis howto python qgis

How to Use Function Editor in QGIS Field Calculator

In QGIS 2.8, there is a new option for users to add their own python function in the Field calculator. This is an extremely useful feature enabling users to populate data within the attribute table using customised python function.

Nathan wrote a blog post about the feature and how to write a python with arguments. But in QGIS 2.8, the function editor does not correctly support functions without arguments.

In the example below, we are going to calculate proportion of area for each SAC (Special Areas of Conservation) in Great Britain to the total area of the layer.

Add GB_SAC_20130918_Dissolved.shp layer to QGIS, right-click on the layer and open attribute table. Make the layer editable and click on the Field calculator. We are now going to create a new column (propArea) and populate proportionate area of each SAC to the total area of the layer.

Under Function Editor tab, click on New file and type area for the name and save the file. For the content, copy and paste the following lines:

"""
A custom function to calculate total area of the GIS layer.
This function has no arguments.
"""

from qgis.core import *
from qgis.gui import *
from qgis.utils import iface

@qgsfunction(args='auto', group='Custom')
def total_area(x, feature, parent):
    return sum( f.geometry().area() for f in iface.activeLayer().getFeatures() )

Click on Run Script to add total_area function to your Custom function list.

Now, click on Expression tab and type:

$area / total_area(0)

As you can see, we have passed 0 as an argument. If you click OK, your QGIS will freeze! As there are many features in the layer, the expression, calculates total area for each row.

Lets make the script a bit more elegant. Firstly, we need to add caching, so that area will be calculated only once and cached for the rest of operation. Secondly, we can make the script a bit more generic, so that we can use it to get the area of other loaded layers in QGIS:

from qgis.core import *
from qgis.gui import *
from qgis.utils import iface

cache = {}
def _layer_area(layer):
    """ Internal method """
    if layer.name() not in cache:
        area = sum( f.geometry().area() for f in layer.getFeatures() )
        cache[layer.name()] = area
    return cache[layer.name()]

@qgsfunction(args='auto', group='Custom')
def total_area(layer_name, feature, parent):
    for layer in iface.mapCanvas().layers():
        if layer.name() == layer_name:
            return _layer_area(layer)
    return 0

Now, click on Expression tab and type:

$area / total_area('GB_SAC_20130918_Dissolved')

This time, it should be quicker!