TransWikia.com

QGIS expression with overlay-fuction: filter-condition based on comparison of attributes from two layers

Geographic Information Systems Asked on June 13, 2021

In QGIS 3.18, I have two point layers: layer_1 with attribute id_1 and layer_2 with id_2. I want to use the overlay_nearest function: overlay_nearest(layer[,expression][,filter][,limit=1][,max_distance][,cache=false])

Applied on layer_1, I want to get for each point on layer_1 all points on layer_2 where id_2 has a different value from id_1 (to connect them by a line). However, I can’t find out how the syntax of the filter-argument works, how to refer to attribute values of two different layers for comparison.

The basic expression is: overlay_nearest( 'layer_2', $geometry, [filter])

How is the syntax of the [filter] part? filter:="id1" <> "id2" obviously doesn’t work. $id <> "id2" and $id <> "id1" don’t work either.


Edit: see this example here to make the idea clearer.

  • Red dots = layer1, labeled with id1
  • White dots = layer2, labeled with id2

Each red point should be connected to it’s nearest white point, except when their id is the same. However, on the upper left (red arrows), you see that 12 is connected to 12, 17 to 17:

enter image description here

The expression used in geometry generator on layer1 for this is as follows (the filter condition is on lines 6 to 18):

collect_geometries (
    array_foreach (
        overlay_nearest( 
            'layer2', 
            $geometry, 
            filter:=attribute (
                get_feature_by_id (
                    'layer1', 
                    $id
                ), 
                'id1'
            ) <> attribute (
                get_feature_by_id (
                    'layer2', 
                    $id
                ), 
                'id2'
            ) , 
            limit:=1
        ),
        make_line (
            $geometry, 
            @element
        )
    )
)

*Edit

See more here in the responses to the feature request, especially:

The purpose of the filter parameter is to reduce the dataset (layer2)
to use for research, so it’s applied before it proceeds to any overlay
test

https://github.com/qgis/QGIS/issues/43146

One Answer

I tested some configurations, tried to understand how it works. So ! I figure it out.

The scope of the filter parameter in the expression function of overlay_nearest is purely the layer set in the layer argument, as if you put this expression in the layer select by expression.

To achieve your goal, I came up with another solution. Instead of select the nearest $geometry of the other layer, I selected the nearest $currentfeature. And in the end, instead of building a line directly, I put an if condition to return a NULL geometry if the nearest layer_2 feature id2 equals the current id1.

collect_geometries(
    array_foreach(
        overlay_nearest(
            'layer_2',
            $currentfeature,
            limit:=1
        ),
        if(
            condition:=attribute(@element, 'id2') = "id1",
            result_when_true:=NULL,
            result_when_false:=make_line(geometry(@element), $geometry)
        )
    )
)
EDIT : display more connections / increase the overlay_nearest limit

With the QGIS expression above, if you increase the overlay_nearest limit, say 2 instead of 1, if one of the collected geometries is NULL because it meets the condition, it nullifies all the geometries and it's not what we want.

Just replace in this case the NULL condition result with a zero lenght line :

collect_geometries(
    array_foreach(
        overlay_nearest(
            'layer_2',
            $currentfeature,
            limit:=2 -- update here
        ),
        if(
            condition:=attribute(@element, 'id2') = "id1",
            result_when_true:=make_line($geometry, $geometry), -- update here
            result_when_false:=make_line(geometry(@element), $geometry)
        )
    )
)
EDIT : each layer_1 point have the same number of lines

Hey ! Yes it works but when layer_1.id1 = layer_2.id2 I got only one line for this point ...

Okay ... I heard you, you are right, GIS is a demanding discipline so I'm reviewing my copy !

To achieve this, I defined a variable, placed on top for easy access, lines_per_point. You can set the number of lines per point, here 2.

With the comment of @MrXsquared, I used array_filter instead an if condition in the array_for_each, it drops all NULL values.

But ! I increased by 1 the overlay_nearest limit of features returned (I considered that layer_2 identifiants are unique, so filter can drop for one layer_1 point only one line maximum (when layer_1.id1 = layer_2.id2)).

Ok, it returns arrays of lenght 2 or 3. Here I used array_slice to get only the two first features (beware, the slice doesn't work as a Python list, it includes the 2 bounds), so [0, 1], so [0, lines_per_point - 1].

We have now an array of length 2 with layer_2 features. So, I applied an array_foreach to create the line with make_line(geometry(@element), $geometry).

(Obviously, you'll get different number of lines for each layer_1 points if the lines per point equals the number of features of layer_2)

The QGIS expression below :

with_variable('lines_per_point', 2,  -- adjust here
collect_geometries(
    array_foreach(
        array_slice(
            array_filter(
                overlay_nearest(
                    'layer_2',
                    $currentfeature,
                    limit:=@lines_per_point + 1
                ),
                attribute(@element,'id2') <> "id1"
            ),
            0, @lines_per_point - 1
        ),
        make_line(geometry(@element), $geometry)
    )
))

Correct answer by J. Monticolo on June 13, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP