I've been meaning to write this for a while now, but the QGIS expression engine keeps getting more and more amazing with each version, but what got me to writing specifically about arrays is the following tweet by Ujaval Gandhi (if you don't know his website https://spatialthoughts.com you probably should):
Ready for another #SpatialAnalysis challenge? Use your favorite spatial analysis tool to name each line using the nearest points from another layer. Post your solution in the comments. I'll post a #QGIS solution tomorrow. Get the test dataset from https://t.co/er4ClgElpv pic.twitter.com/h2rVdpUr4Y
— Ujaval Gandhi (@spatialthoughts) July 29, 2021
Now I missed that Ujaval asked for the names of closest point to start and end of each line, but if you just want the closest point to each line than the expression is simple:
array_to_string(
overlay_nearest( 'points',"name",limit:=2)
,'-')
A neat little trick to analyzing expression functions, is you can add that function parameter names by using the parameter name and adding := as an equal sign, like so:
array_to_string(
overlay_nearest(
layer:= 'points',
expression:="name",
limit:=2)
,'-')
What my expression does is get an array with the evaluated expression (in this case just the "name" attribute) nearest features from that layer, by default it returns one feature, but you can use the limit parameter to get back more than one. So I got back an array, which I then use `array_to_string` on to create a '-' separated string with both names.
So, what's the point of this post? Showing some of the great array functions the QGIS expression engine has to offer. Both those that create arrays and those that parse them. Like you could see in my first expression engine post (Getting values from JSON and HSTORE) you can easily parse different data structures in the expression engine. Parsing different data structures means we can do cooler things. Cooler ways to select, create or display our data.
Using Arrays To Replace For Loops
How can we create arrays? let's say we want to create an expression that works similar to a for loop. meaning we want to perform an action for a certain number of times. What we'll use is the generate_series function, what it does is generate an array from a starting number to an end number. We can also add a third argument that tells the function to use steps. for example:
will generate this array:
but adding the step parameter we can get this array from this expression:
We can use this to create either arrays of the same length and size for the entire layer, or arrays that use the number of geometry parts or points in a geometry as an end number. We can then use array_foreach on this new array and perform a function, for example, we can check which part of our geometry is larger than a certain area or intersects with a specific geometry.
Now this expression will look a bit long, but it does something relatively simple, it generates a series that is the length of geometry parts that geometry has and that's the
part of the expression, it then uses that array in array_foreach and gets the geometry part with geometry_n, note that when using array_foreach, the @element variable is the item in array we are using, since we used generate_series, our @element is a number, but it can be a geometry, a point or anything we use in an array.
After getting the geometry part, we check if it intersects with any part of Turkey (I explain about getting specific features and attributes of those features in part 2 of exploring the QGIS expression engine), what we get back is an array of 1's and 0's meaning boolean values if that part intersects or not.
generate_series(1,num_geometries($geometry)),
intersects(
geometry_n($geometry,@element),
geometry(
get_feature( 'ne_countries','name','Turkey')
)
)
)
Running this expression on Greece for example, that has a lot of geometries but few that touch Turkey will generate this array back.
We can use this to select all the Countries that have at least 2 geometry parts that touch Turkey by wrapping this expression in array_sum and add a >= 2 at the end, That only returns Turkey, but it's still a neat trick to check the results of both number of parts and what each part intersectes.
array_foreach(
generate_series(1,num_geometries($geometry)),
intersects(
geometry_n($geometry,@element),
geometry(
get_feature( 'ne_countries','name','Turkey')
)
)
)
) >=2
Using Arrays To Create Geometries
I found this trick by accident when I came across this post by Benjamin Becquet, where he shows how to create beautiful waterlines with an array of numbers and a negative value buffer, He then uses collect_geometries that takes an arrays of geometries and returns a multigeometry back, here it returns a multipolygon of all these buffers.
collect_geometries(
array_foreach(
generate_series(0, -1000, -25),
buffer($geometry, @element)
)
)
These are just some of the way you can use arrays and array functions in QGIS expressions, notes that there are multiple other array functions that can return the minimal, maximal or sum value of an array, or check if there is a specific element in it or multiple other actions, I recommend you test it out.
If you found this post interesting, feel free to check out the other 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
Comments
Post a Comment