Exploring The QGIS Expression Engine, Part 6: Creating And Using Variables

 Variables are values defined either for the application or the project. each of them is a value relevant for where you access it. You can find values describing the operating system you are using, the current map extent (either in canvas or a print layout), the current row number or atlas feature and so on.

We can use set variables in our project and change or use them later, for example, I can set a variable with the name of the font I want to use and point all of my labels and text features to that variable, or use it to set specific colors. When we want to use these variables in the expression engine we'll use an at sign ( @ ) and then the name of the variable, for example @layer_name or @project_crs.

Project CRS is actually a great variable to use, we can use it to make sure our geometry in the expression is in the same CRS as our project or any CRS we want to check against. For example transforming a geometry without knowing the either the layer CRS or the project CRS can be done like this:

    transform($geometry,@layer_crs,@project_crs)

So that's about as simple as it gets when you use variables, again, you can set your own in the project properties or in Settings -> Options -> Variables for global variables, but we the things we can do with the variables we already have is already pretty fun.

Using Variables In Layer Styling

I won't elaborate a lot about this, but if you want to explore using variables for labeling, styling your layers, or even creating simple dashboards in QGIS, I recommend checking out the project uploaded by Kartoza's Tim Sutton, that shows a basic dashboard inside QGIS.
The dashboard is its own layer with expressions and other styling definitions waiting in the attribute table. You can find the project here and there's also a link to a YouTube video explaining how it was created.

Getting on with two other uses for variables.

Dynamically Controlling And Changing Text

Let's assume you have a few great print layouts in your project, that you want to print occasionally, each time with different parameters, some times you want distances to be shown in kilometers and other times in miles, you might want to change the title of the map, but you always want to change it for all of them. You could change each layout for every time you want to print them, or you could use a variable in a label element.

What we'll do is open the project properties menu, and the variables tab, then simply click the green plus sign and add a custom variable with the name you want (without spaces).


We can now add a label element to our map layout and edit the expression to use our new variable.


We can go a step further, and use the length of a river for our label, and select whether we want to to show it in kilometers or miles by using a variable called print_units.
Our expression here is a bit complex, but if you read the former posts in this series, you should follow the way it's made up pretty well.

    format_number(
        length(
            transform(
                 aggregate( layer:='ne_rivers_lake_centerlines',
                         aggregate:='collect',
                         expression:=$geometry,
                         filter:="name"='Jordan'
                        ),
                      'EPSG:4326',
                      'EPSG:6991')
                   )/1000*if( @print_units = 'meters',1,0.6213)
             ,2
        )

We use aggregate to collect all the parts of the Jordan River, then transform that multiline geometry to Israel's ITM grid and transform it from meters to kilometers by dividing it by 1000. Now here we use the variable, if our print_units variable is "meters" then I multiply the result by 1 (not changing it), if not, the I multiply the length in kilometers by 0.6213 which shows the length in miles. We later wrap all of that in format_number that adds commas where needed and decides the number of decimal places shown while turning the number to string.

The full text I added to the label element also changes to text with the print_units variable, you can add multiple expressions within one label element, and in this way each one of them can change according to different variables.

    The Jordan River's Length is
    [%
        format_number(
            length(

                transform(

                     aggregate( layer:='ne_rivers_lake_centerlines',

                         aggregate:='collect',

                         expression:=$geometry,

                         filter:="name"='Jordan'),

                     'EPSG:4326',

                     'EPSG:6991')

                 )/1000,2

    )
    %] [%
    if( @print_units ='meters','Kilometers','Miles')
%]

The resulting label looks like this on the map:

 


Creating Dynamic Variables With Expressions

This is actually the example that made me split this post from the former post about arrays and drove me into the rabbit hole that is QGIS variables. What I wanted to do was use a nested array_foreach, since I wanted an expression to run on every point of every geometry part in a layer.

I accidentally came across the with_variable function, what it does, is allow you to create a variable that will only be evaluated within that function. The first parameter of the function defines a name for the variable, the second parameter defines the value of that variable and in the third parameter you simply add what ever expression you want to use your newly created variable.
Now these variables are not limited to a single string value like the ones you can add to your project, they can be anything, and array or a feature or a geometry.

What I wanted to do was convert a multipolygon layer (or a selected feature from it), to a multiline layer. You can perform conversions like these, controlled by a specific expression with the Geometry By Expression processing tool.


 

For our purposes I'm selecting Germany, which is a multipolygon, but all of its parts are relatively close together (unlike France which is spread across half the world), I'll select an output line geometry and enter the below expression



collect_geometries(
    array_foreach(
        generate_series( 1, num_geometries( $geometry)),
             with_variable('element1',
             @element, -- the current index of array_foreach
              make_line(
                 array_foreach(
                     generate_series( 1, num_points( geometry_n($geometry,@element1))),
                     point_n(geometry_n($geometry,@element1), @element))
                 )
             )
         )
    )

Starting from inside out, I'm extracting the points of each geometry, by creating an array of numbers with the length defined by the number of points ( [1,2,3...n] ). Using array_foreach I'm creating an array of this points ( [ <point>, <point>...] ), and wrapping that in make_line that takes in arrays of point geometries and creates a line geometry. The interesting part, getting @element1 as a new variable, since all I'm doing there is renaming the @element variable in the outer array_foreach, that way I can get specific points of specific geometries with a relatively simple expression (yes, I know it's not simple, but creating a nested for loop with expression is more complicated without it). The outer array_foreach returns an array of lines, which we wrap in collect_geometries that returns one multigeometry for an array of geometries.

The result after using this expression in Geometry By Expression is a layer containing multiline geometry, with the same attributes as the original. I added point markers at the beginning of each line, note that since I used polygons, the first and last point is at the same spot, if you don't want your lines to close, you can  change

    generate_series( 1, num_points( geometry_n($geometry,@element1)))

into

    generate_series( 1, num_points( geometry_n($geometry,@element1))-1)

and get lines that almost close.



These are just three ways (including the dashboard) you can use to variables in QGIS to leverage your work and create better expressions and simplify some manual changes to avoid errors. If this post helped you you can check out the former posts in this series and some of my other posts about using expressions in QGIS.

Exploring The QGIS Expression Engine, Part 1: Getting Values From JSON & HSTORE

Exploring The QGIS Expression Engine, Part 2: What's Missing From Select By Location 

Exploring The QGIS Expression Engine, Part 3: Writing Custom Expression Functions

Exploring The QGIS Expression Engine, Part 4: Selecting By Attributes And Location With One Expression
 



Comments