Exploring The QGIS Expression Engine, Part 7: Cross-Layer Relationships

 I dabbled a bit in working with other layers in my second post about the expression engine, there I showed how to select by location within distance, but in this post I'll answer a question I saw on Facebook:

 How can I get features from one layer intersecting with only the selected features from another layer?

Now the first, best and simplest response would be: "Select By Location", and that was my first answer too. But then I got thinking, this should probably be possible with expressions, and I got working on that expression.

I started by creating a grid layer, aptly named "Grid", and a lines layer, named "lines", and selected one of the lines.



 To check if a feature was selected I added a simple unique identifier field for the lines layer, using the @row_number variable in the field calculator. This new field will let me see which feature I am testing against when using the select by expression window.




The feature I selected manually has a feature ID of 2, so the first thing I am going to do is check if I can see it is selected from the lines layer before moving on to cross layer selection.

    is_selected( $currentfeature )


 We can, now what about from the grid layer's select by expression? I'll use get_feature_by_id which does what its name says and returns a sepcific feature from a layer by id

    is_selected(
        get_feature_by_id( 'lines',2)
    )

That doesn't work, what we are missing is another way of calling the is_selected function, where we specify both the feature and the layer it belongs to, we need to do this even though we got the feature using get_feature_by_id which already had us specify the layer.



 So we know how to get features from another layer, and we know how check if that feature is selected, but how we compare all of the selected features from another layer with our grid layer? We use aggregate and array function. First we'll get all the available IDs from the lines layer using the aggregate function with 'array_agg' aggregation which will return an array of IDs the lines layer is using.

    aggregate(
        layer:='lines',
        aggregate:='array_agg',
        expression:=$id
    )

Next we'll wrap the aggregate function in array_foreach which lets us use every element in an array separately, meaning now that we have all the IDs we can use them to get their features back using get_feature_by_id.

To use an element in the array inside the array_foreach function we use @element as if it was a variable, in this case our elements are numbers representing IDs, but they can just as well be strings, geometry, features or even other arrays.

To check whether a feature from another layer is  both selected and intersetcs with our feature, we are going to combine the is_selected  with the intersects (not intersection, which return the intersection of two geometries, i.e a geometry, instead of the boolean yes/no answer which we need) function inside an if function that will return 1 if the lines feature is selected and intersects with the current feature and 0 if either or both of those answers is false.

    array_foreach(  
        aggregate(
        layer:='lines',
        aggregate:='array_agg',
        expression:=$id
        ),
        if(
            is_selected('lines',
                        get_feature_by_id( 'lines',@element))
            and
            intersects(
                geometry(
                    get_feature_by_id( 'lines',@element)),
                $geometry) ,
            1,0)
    )


 

 We are getting pretty close, we got back an array of answers, and one of them is 1 so we know that the current feature intersects with at least one selected feature from the lines layer. All we have to do now is add yet another function to wrap our current expression to get back a boolean value of true/false (or 1/0 which the expression engine reads as true/false), for that we'll use the array_contains functions which check whether a value exists in an array.

    array_contains(
        array_foreach( 
            aggregate(
            layer:='lines',
            aggregate:='array_agg',
            expression:=$id
            ),
            if(
                is_selected('lines',
                            get_feature_by_id( 'lines',@element))
                and
                intersects(
                    geometry(
                        get_feature_by_id( 'lines',@element)),
                    $geometry),
                1,0)
        ),
    1)




We got back 4 features, which are the only 4 that intersect with the selected line from the first image, so lets summarize what we learned:

1. How to get features from another layer, and we know how check if that feature is selected

2. How to get all the IDs of a layer into an array

3. How to perform a test of both a state (is selected) and  a spatial relationship across layers

4. How to summarize an array into a value needed to perform a selection.

A lot of this post assumes you either know some of these actions or have read previous posts in this series, so don't make an ass out of you and me and check out the previous posts in this series.

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

Exploring The QGIS Expression Engine, Part 5: Fun With Arrays

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


Comments