You can use overlay_nearest()
function that has an optional filter condition. The filter is a bit tricky, but you can use a variable @value
based on the attribute value
and than introduce it into the filter condition by concatenating it as a string and than convert it back to a function with eval()
.
Red arrowheads show the direction in whicht points are connected: 7 is linked to 5 and 5 to 7; 11 is linked to 7, too ("outgoing"), but 11 has no "incoming" connection:
You can use the expression with Geometry Generator (for visualization) or Geometry by Expression (for actual lines) - see here for differences.
The expression looks like this: you can freely change the percentage in line 3. Be sure to introduce the name of your point layer in line 11:
with_variable(
'percentage',
50, -- change this value
make_line (
$geometry,
with_variable (
'value',
value,
eval(
'overlay_nearest (
''points'', -- change to the name of your points layer
$geometry,
limit:=11,
filter:="value">=' || (@value/100*(100-@percentage)) || ' and "value" <=' || ( @value * (100+@percentage)/100) || ')'
)
)[1]
)
)
Note that both solutions will only work correctly when you use the same projected CRS on both line layers.
Solution A: Distance to the closest points on the second line:
array_mean(
array_foreach(
array_foreach(
generate_series(0,length($geometry),10),
line_interpolate_point($geometry,@element)
),
distance(aggregate('line1','collect',$geometry),@element)
)
)
What it does:
- Create distances along the line by using
generate_series()
- Create interpolated points at these distances by using
line_interpolate_point()
- Iterate over these points via
array_foreach()
and get the distance()
to the second line by using aggregate()
or some different function to get another line. distance()
Returns the minimum distance (based on spatial reference) between two geometries in projected units.
- At this point you have an array of the closest distances. Build the mean value using
array_mean()
.
Discussion:
The expression is much easier to implement and to understand, but it has one great disadvantage: It always measures the length to the closest point on the other line. As you can see in the screenshot below, this may not accurate for some usecases. However, it should work without issuses on very curvy lines, changing their main angle often, as well.
Visualized:
Expression for visualization:
collect_geometries(
array_foreach(
array_foreach(
generate_series(0,length($geometry),10),
line_interpolate_point($geometry,@element)
),
make_line(closest_point(aggregate('line1','collect',$geometry),@element),@element)
)
)
Solution B: Distance to line in a specified angle:
array_mean(
array_remove_all(
array_foreach(
array_foreach(
generate_series(0,length($geometry),10),
line_interpolate_point($geometry,@element)
),
length(make_line(intersection(
aggregate('line1','collect',$geometry),
make_line(project(@element,9999,radians(main_angle($geometry)+90)),@element)
),@element))
),
NULL)
)
What it does:
- Create distances along the line by using
generate_series()
- Create interpolated points at these distances by using
line_interpolate_point()
- Iterate over these points via
array_foreach()
and
- Create a line with a pseudo length of
9999
m (change this value to your needs) in
- a specified angle. Here we use the main angle of the line
+90°
, we get via main_angle()
. You may need to change +90
to -90
or something different, depending on your geometries and your personal needs.
- Now we need to find the intersection of these lines to the second line. We can get it via
intersection()
.
- To prevent an invalid result, we can remove all invalid geometries by using
array_remove_all()
to remove all NULL
values.
- Finally we construct a line via
make_line()
from the points from generate_series()
to the points from intersection()
- Get the
length()
of these lines
- At this point you have an array of the line lengts. Build the mean value using
array_mean()
.
Discussion:
The expression is a little more difficult to understand and implement, but it has one great advantage over solution A: It always measures the length to the other line in the same angle. However, this may be tricky to implement, if you have curvy lines changing their main angle a lot. In this case, I can suggest to split the line into different features and instead of using aggregate()
to get the second layers line, you may use get_feature()
together with geometry()
, to get only one specific, related line.
Visualized:
Expression for visualization:
collect_geometries(
array_remove_all(
array_foreach(
array_foreach(
generate_series(0,length($geometry),10),
line_interpolate_point($geometry,@element)
),
make_line(intersection(
aggregate('line1','collect',$geometry),
make_line(project(@element,9999,radians(main_angle($geometry)+90)),@element)
),@element)
),
NULL)
)
Best Answer
Use this expression. On line 3, it creates the 2nd largest value per category as variable
@max
: get an array of allpob
values for the currentCATEG
value, sort it in descending order and get the 2nd value (index[1]
); in the filter part later, we use onlypob
values larger or equal to this value. Then useoverlay_nearest()
function:The expression working on your dataset: blue=Category A, red=category B; black dotted line: mid-line between the two largest values per category:
Edit:
Challenges in this expression is how to include a filter condition so that we can compare the attribute value of the feature currently evaluated inside the
overaly_nearest()
function not to a fixed value, but a dynamic expression, which is based, as here, on aggregate functions. So the challenge is to including the parent feature or other features (when aggregating). You can't include this directly in the filter, so you have to use a trick and concatenate the wholeoverlay_nearest()
function as a text string and then evaluating it witheval()
- see here: gis.stackexchange.com/a/415248/88814.Especially tricky is that the dynamically calculated part (referring to the parent/aggregated features) has to remain outside the string so that it will be calculated correctly (on the parent feature or any other features when using aggregate functions) and to return the desired value(s). So for clarity, the value is created as variables
@max
and@cat
outside theoverlay_nearest()
function and the variable is then inserted in between the string parts by concatenating the different parts with pipes||
.On top of this, the value stored in the
@cat
variable has be be passed as a string to be concatenated, so you have to use not less than three single quotes ''' one after the other (as two single quotes are used inside a string to introduce a quote and thus prevent the string being ended).An alternative, equivalent expression to the above one, avoiding the variable and including everything in the string concatenation part, is: