QGIS Geometry – How to Obtain Closest Mean Distance Between Two Pseudoparallel Lines

averageclosest distancegeometry-generatorqgis

From two layers (LINE_1 and LINE_2) with a irregular pseudoparallel lines geometry, my goal is to obtain the closest mean distance between two pseudoparallel lines.

Here I publish a screenshot with the workspace approach

enter image description here

My initial idea is to do the calculation by dividing the line into a set of points. Then my goal would be to develop a single expression that integrates functions like shortest_line, length and mean, to obtain the average closest distance between the points of the second line.

Here is a representation of what I am looking for:

enter image description here

Best Answer

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:

  1. Create distances along the line by using generate_series()
  2. Create interpolated points at these distances by using line_interpolate_point()
  3. 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.
  4. 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:

enter image description here

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:

  1. Create distances along the line by using generate_series()
  2. Create interpolated points at these distances by using line_interpolate_point()
  3. Iterate over these points via array_foreach() and
  4. Create a line with a pseudo length of 9999m (change this value to your needs) in
  5. 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.
  6. Now we need to find the intersection of these lines to the second line. We can get it via intersection().
  7. To prevent an invalid result, we can remove all invalid geometries by using array_remove_all() to remove all NULL values.
  8. Finally we construct a line via make_line() from the points from generate_series() to the points from intersection()
  9. Get the length() of these lines
  10. 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:

enter image description here

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)
)
Related Question