# How to use QGIS expressions in qgisprocess?

#### Floris Vanderhaeghe & Jannes Muenchow

####
Last update: 2024-06-26
Last run: 2024-10-06

    library(qgisprocess)
    library(sf)

# Introduction

Many QGIS processing algorithms provide the possibility to use QGIS expressions. If an algorithm argument expects a QGIS expression, this is typically marked by a button in the QGIS processing dialog that opens the QGIS expression builder (e.g. in `native:extractbyexpression`), or by a directly integrated QGIS expression builder (e.g. in `native:fieldcalculator`). Such arguments are of type `expression`, as seen in the output of `qgis_get_argument_specs()`.

    qgis_get_argument_specs("native:fieldcalculator") |> subset(name == "FORMULA")
    #> # A tibble: 1 × 6
    #>   name    description qgis_type default_value available_values acceptable_values
    #>   <chr>   <chr>       <chr>     <list>        <list>           <list>
    #> 1 FORMULA Formula     expressi… <NULL>        <NULL>           <chr [1]>

Secondly, one can use expressions for data-defined overriding. This means that an algorithm argument that is usually a fixed value (number, distance, boolean, string or color) can also take on the value of another field or the result of an expression. In the QGIS processing dialog, such arguments have a ‘data-defined override’ button. An example is provided by the `DISTANCE` argument of `native:buffer`, for which we query the acceptable values below.

    qgis_get_argument_specs("native:buffer") |>
      subset(name == "DISTANCE", acceptable_values) |>
      tidyr::unnest_longer(acceptable_values) |>
      knitr::kable()

acceptable_values
A numeric value
field:FIELD_NAME to use a data defined value taken from the FIELD_NAME field
expression:SOME EXPRESSION to use a data defined value calculated using a custom QGIS expression

# Examples where the argument expects a QGIS expression

As example data, we use a lake polygon and a set of points that have lake depth as attribute.

    longlake_path <- system.file("longlake/longlake.gpkg", package = "qgisprocess")
    longlake_depth_path <- system.file("longlake/longlake_depth.gpkg", package = "qgisprocess")

    nrow(read_sf(longlake_depth_path))
    #> [1] 64

In a first example, we use a QGIS expression to filter points by depth. We can simply pass the expression as a string:

    qgis_run_algorithm(
      "native:extractbyexpression",
      INPUT = longlake_depth_path,
      EXPRESSION = '"DEPTH_M" > 1'
    ) |>
      st_as_sf()
    #> Using `OUTPUT = qgis_tmp_vector()`
    #> Using `FAIL_OUTPUT = qgis_tmp_vector()`
    #> Simple feature collection with 39 features and 2 fields
    #> Geometry type: POINT
    #> Dimension:     XY
    #> Bounding box:  xmin: 410242.8 ymin: 5083519 xmax: 411466.8 ymax: 5084691
    #> Projected CRS: NAD83 / UTM zone 20N
    #> # A tibble: 39 × 3
    #>    WAYPOINT_I DEPTH_M               geom
    #>         <dbl>   <dbl>        <POINT [m]>
    #>  1          8     1.4 (411466.8 5084488)
    #>  2         12     1.4 (411379.1 5084490)
    #>  3         17     1.4 (411292.9 5084670)
    #>  4         19     1.5 (411290.8 5084593)
    #>  5         20     1.5 (411286.8 5084534)
    #>  6         24     1.2 (411272.2 5084333)
    #>  7         25     1.5 (411244.6 5084420)
    #>  8         27     1.6 (411220.3 5084500)
    #>  9         29     1.7 (411205.4 5084575)
    #> 10         36     1.8 (411133.2 5084691)
    #> # ℹ 29 more rows

More often, you will want to use QGIS functions in expressions, and look at the relationship between geometries or create new geometries.

Let’s calculate the distance between the points and the lake border, and add it as an attribute to the points. For that we will use the `native:fieldcalculator` algorithm.

We first create the lake border:

    lake_border_path <- qgis_run_algorithm(
      "native:polygonstolines",
      INPUT = longlake_path
    ) |>
      qgis_extract_output("OUTPUT")
    #> Using `OUTPUT = qgis_tmp_vector()`

Next, build the QGIS expression. Referring to the `INPUT` geometry in `native:fieldcalculator` is done with the `@geometry` variable.

    expr <- glue::glue("distance(
                         @geometry,
                         geometry(
                           get_feature_by_id(
                             load_layer('{lake_border_path}', 'ogr'),
                             1
                           )
                         )
                        )")

Referring to the lake border geometry in an expression is a bit trickier, since it requires several QGIS functions. The layer can be loaded from a filepath with the `load_layer()` function, then the first (and only) feature is selected with `get_feature_by_id()`, and the geometry of that feature is selected using the `geometry()` function. These steps are needed because the `distance()` function needs geometries to work on, not features, layers or filepaths.

Use the QGIS expression builder to look up function documentation, or consult the online QGIS function documentation.

Note: the `load_layer()` function is only available since QGIS 3.30.0! In earlier versions, you needed to refer to the layer’s name in an existing QGIS project, and refer to the project path in `qgis_run_algorithm()` with the special `PROJECT_PATH` argument. The `load_layer()` approach since QGIS 3.30.0 avoids the need for a QGIS project.

Now we can run the algorithm:

    qgis_run_algorithm(
      "native:fieldcalculator",
      INPUT = longlake_depth_path,
      FIELD_NAME = "distance",
      FORMULA = expr
    ) |>
      st_as_sf()
    #> Using `FIELD_TYPE = "Decimal (double)"`
    #> Argument `FIELD_LENGTH` is unspecified (using QGIS default value).
    #> Argument `FIELD_PRECISION` is unspecified (using QGIS default value).
    #> Using `OUTPUT = qgis_tmp_vector()`
    #> Simple feature collection with 64 features and 3 fields
    #> Geometry type: POINT
    #> Dimension:     XY
    #> Bounding box:  xmin: 409967.1 ymin: 5083354 xmax: 411658.7 ymax: 5084777
    #> Projected CRS: NAD83 / UTM zone 20N
    #> # A tibble: 64 × 4
    #>    WAYPOINT_I DEPTH_M distance               geom
    #>         <dbl>   <dbl>    <dbl>        <POINT [m]>
    #>  1          2     0.8     6.22 (411658.7 5084501)
    #>  2          3     0.9    38.6  (411630.3 5084560)
    #>  3          5     0.8    46.7  (411553.4 5084601)
    #>  4          6     0.8    49.3  (411476.4 5084600)
    #>  5          8     1.4   100.   (411466.8 5084488)
    #>  6         10     0.6    25.4  (411466.4 5084410)
    #>  7         12     1.4   140.   (411379.1 5084490)
    #>  8         16     0.8    39.4  (411321.2 5084721)
    #>  9         17     1.4    93.7  (411292.9 5084670)
    #> 10         19     1.5   150.   (411290.8 5084593)
    #> # ℹ 54 more rows

# Example applying a data-defined override

Suppose that we want to create a buffer around the points with a dynamic radius expressed as a function of `DEPTH_M`, e.g. 10 times the depth at each point. We will use `native:buffer` for that purpose. Note: applying a data-defined override with qgisprocess is only possible since QGIS 3.30.0!

Because `DISTANCE` by default expects a numeric value, you have to use the prefix `expression:` if you want to pass an expression string. Double quotes are used in QGIS expressions to denote fields (attributes), but you can also omit them.

Let’s try:

    buffer <- qgis_run_algorithm(
      "native:buffer",
      INPUT = longlake_depth_path,
      DISTANCE = 'expression: "DEPTH_M" * 10'
    ) |>
      st_as_sf()
    #> Argument `SEGMENTS` is unspecified (using QGIS default value).
    #> Using `END_CAP_STYLE = "Round"`
    #> Using `JOIN_STYLE = "Round"`
    #> Argument `MITER_LIMIT` is unspecified (using QGIS default value).
    #> Argument `DISSOLVE` is unspecified (using QGIS default value).
    #> Argument `SEPARATE_DISJOINT` is unspecified (using QGIS default value).
    #> Using `OUTPUT = qgis_tmp_vector()`

So from the 64 points we have created 64 polygons:

    st_geometry_type(buffer) |> as.character() |> table()
    #>
    #> MULTIPOLYGON
    #>           64

Plot the result:

    oldpar <- par(mar = rep(0.1, 4))
    plot(read_sf(lake_border_path) |> st_geometry())
    plot(buffer[, "DEPTH_M"], add = TRUE)

    par(oldpar)

If you just want to refer to the value of another attribute, then you can also use the `field:` prefix instead, without double quotes around the attribute name and without spaces:

    qgis_run_algorithm(
      "native:buffer",
      INPUT = longlake_depth_path,
      DISTANCE = "field:DEPTH_M"
    ) |>
      st_as_sf()