I have a line layer (MultiLineString) and 4 point layers (MultiPoint).
My line layer has 2 attribute fields, start_pt
and end_pt
, I need them populated with an identifier attribute (string data populated with values like A01, B01, B02, C01, etc. so it's not the regular $id
value) from any of the potential 4 point features that can be snapped to the line feature.
Here's a sample project:
This is what line layer looks like:
Here's what the populated attribute fields should be like: (filled manually)
I'm looking for a mean to do this through either field calculator expressions OR PyQGIS as I need it as part of a plugin to automate it. So I can't use DB/SQL or anything else, basically.
What I have used so far was a calculator expression (tried changing start_point/end_point and array_first and array_last):
if (
within( array_first (overlay_touches( 'point1',$geometry)), buffer (start_point ($geometry), 0.00001)),
array_first (overlay_touches( 'point1',"id")),
array_last (overlay_touches( 'point2',"id")))
Which I would then assign to a variable with QgsExpression()
and populate my tables with context.SetFeature(feature)
Back when I was limited to 2 point layers it used to work with relative success but as of right now it doesn't do the trick mainly because within()
in and of itself is limited to 2 values for either True
or False
so it doesn't work for more than 2 layers.
I'm still a beginner with Python and PyQGIS so I'm not sure how to make it work with it either. I know I could use a spatial index as a mean to detect closes features from a given point layer that are close to my line feature's start_point
or end_point
(as it is shown at pyqgis: add attributes of points to attributtable of lines) but as I understand, only one spatial index is used at a time, so I'm not sure about that either.
How can I, for instance, retrieve the "id" attribute from the retrieved nearestNeighbor from a spatial index?
In the thread linked about, there's this bit of code which theoretically explains how to retrieve the attribute id of the given feature. But it obviously only works with a single layer in mind.
fs = point_layer.getFeatures(QgsFeatureRequest().setFilterFid(nearest_to_start))
point_feature = QgsFeature()
fs.nextFeature(point_feature)
p_id = point_feature['id']
Is there a way to use multiple spatial indices to somehow achieve my goal?
I'm updating this question as the solution provided in the edit by @pigreco doesn't really solve the problem. Using his help I got on the right track. This is more of an elabored comment than an answer as I do need to provide screenshots and code.
Here's an example following his method:
Overlay_nearest returns wrong values whenever two points are from the same layer. I have tried setting the limit to 1 and max_distance to 0 but the results remain sensibly the same. Replacing the first overlay_nearest by overlay_intersects (which ensures only the interescting geometry gets returned) does limit the amount of values (thus getting rid of cases like A03 replacing D04) instead I get NULL values instead of wrong values.
Here's the code I used:
-- select id
with_variable('feature',
-- search for the nearest points
overlay_intersects(layer:=
-- search for the closest layer
with_variable('in_layer',array('point1','point2','point3','point4'), -- point layer list
expression:=with_variable('in_dist',
array_foreach(@in_layer,
distance(overlay_nearest(@element,$geometry)[0],
start_point($geometry))), -- change start or end_point
array_get(@in_layer, array_find(@in_dist, array_sort(@in_dist)[0]))))
-- search for the closest layer
,expression:= id )
-- search for the nearest points
, if(array_length(
@feature)>1,
@feature[0], -- 0: start_point; 1: end_point
@feature[0]) --
)
Here are the results I get:
I have also tried with an aggregate expression:
aggregate(layer:=with_variable('in_layer',array('point1','point2','point3','point4'),
expression:=with_variable('in_dist',
array_foreach(@in_layer, distance(overlay_nearest(@element,$geometry)[0], end_point($geometry))),
array_get(@in_layer, array_find(@in_dist, array_sort(@in_dist)[0])))),
aggregate:='max', expression:=id,
filter:=intersects( $geometry, end_point(geometry(@parent) )))
Which gives me these results:
Although and to be quite honest, I'm fairly confused at what is returned by the array_forreach(array_get()) and the relation with my intersects filter which takes the current feature's geometry.