Ever since I added expression based labels, including the new expression builder UI, something that I always wanted to add is the ability to define custom user defined functions in Python (or C++) and use them in an expression. The expression engine is used for labels, rule based rendering, layer actions, and atlas composer tags. Thanks to the all the awesome work on the expression engine by Martin all this cool stuff is now possible.
Today I pushed a commit into master that adds the ability to define a function in Python (or C++), register it in the expression engine, then use it anywhere expressions are used.
The good stuff
Lets take a use case from Ujaval Gandhi and his example of counting vertices for each feature.
First
we need to import the
new qgsfunction
decorator function
from qgis.utils
.
The qgsfunction
decorator will take a
normal Python
function, wrap it up
in the class used to
define a function, and
register it in the
engine.
So what does an empty function look like:
from qgis.utils import qgsfunction from qgis.core import QGis @qgsfunction(0, "Python") def vertices(values, feature, parent): pass
@qgsfunction(0,
"Python")
means we are defining
a new vertices
function that takes 0
args and lives in the
"python" group in the
expression builder UI.
Any custom function
must take (values,
feature, parent)
as python args. values
is a list of QVariants
passed into the
function, feature
is the current QgsFeature
,
and parent
is expression engine
node (you use this to
raise errors).
Lets stick some more logic in there:
from qgis.utils import qgsfunction from qgis.core import QGis @qgsfunction(0, "Python") def vertices(values, feature, parent): """ Returns the number of vertices for a features geometry """ count = None geom = feature.geometry() if geom is None: return None if geom.type() == QGis.Polygon: count = 0 if geom.isMultipart(): polygons = geom.asMultiPolygon() else: polygons = [ geom.asPolygon() ] for polygon in polygons: for ring in polygon: count += len(ring) return count
Pretty simple. Get the geometry from the feature, check if it's a polygon, if it is then count the number of vertices and return that number.
Now
that we have that all
done we can save it
into a file in our .qgis/python
folder,
lets call it userfunctions.py
(note you don't have
to save it here,
anywhere that QGIS can
find it will do.
Anywhere on PATH)
Lets open QGIS and run import userfunctions.py:
Now open the label properties for the layer:
Nice! Notice also that the function doc string is used as the function help. How cool is that. You can also see the $ sign in front of the function, this is because any functions that take no args are considered special and use the $ sign as a convention, this is all automatic when the function is registered.
And the result is:
You can even use it in the rule based rendering:
Enjoy!
Notes
- You must unregister a function once you are finished with it using QgsExpression.unregisterFunction(name). This mainly applies to plugins where the user might unload your plugin and the code is no longer available. In the above example we could import userfunctions and never unregister because we plan on using it for the whole session.
- You can't override the built-in methods.