Easy Line Splitting With QGIS Expressions

Came across a question regarding how to determine the length along a line between two points, figured this wouldn't take too long in QGIS (it didn't, 10 minutes while twitting the steps) and decided to write this down since I'll probably forget I did it in a week or so.

The process can be divided logically into  steps

1. Project/Snap the points to the line

2. Measure where each point is along the line 

3. Clip the line between the locations

 

I was in the middle of another map and didn't bother to open a new project, so the line I drew is about the length of the US west coast, but it's CRS is EPSG:6991 which is Israel Grid (because I needed the result length in meters). For the example I drew 2 point beside the line, and not on it, because I didn't want to count on the points being snapped. 



Snap the points to the line

This is relatively simple, we only need to do two actions here, get a specific point and snap it to the line. You can get the point by using either the get_feature or get_feature_by_id expression functions.
Your expression should look something like this

    closest_point(
       $geometry,
       geometry(get_feature_by_id( 'points',1))
      )

What I do there is grab the first point and check what is the closest place on the line to it, that's it for step 1.
If you want to see the projected points, you add a geometry generator symbol to the points and use the expression the other way around:

     closest_point(
        geometry(get_feature_by_id( 'line',1)),
        $geometry
       )


Brown points are the original, red are projected using the geometry generator

Measure where each point is along the line

Now that we have the expression to create our points, we need to determine how far along the line each one is (we need that for step 3).  The expression here is just wrapping the previous expression in another function, line_locate_point which measures just that.

    line_locate_point(
        $geometry,
        closest_point(
            $geometry,
            geometry(
                get_feature_by_id( 'points',1)
            )
         )
      )

This returns a number, in this case, the length in meters from the line's start to the projected point.


  Clip the line between the locations 

Final step now, we use the projected points and the measurements, along with the line_substring function which takes 3 arguments, geometry, and distance from start of line for both points so our final expression will get us back a new geometry which is only the part of the line between both points.

We can use this expression either to draw the "clipped" line using the geometry generator, create a new layer with the Geometry By Expression processing tool, or just wrap it in the length function if we just want the length.

      line_substring(
          $geometry,
         line_locate_point(
            $geometry,
            closest_point(
               $geometry,
               geometry(
                    get_feature_by_id( 'points',1)
                    )
              )
          ),
          line_locate_point( $geometry,
            closest_point(
           $geometry,
           geometry(
                get_feature_by_id( 'points',2)
                )
             )
          )
      )



This should be enough for most simple clips, you can notice that while the expression is long, it doesn't use a lot of functions or complex logic, in fact the most complicated part for you will probably be selecting the specific points and line you would want to use.

For some examples of selecting specific features using expressions you can check out my post about selections
 
 

UPDATE

 
I recently used this post to answer a question on GIS StackExchange and Babel (Daniel Ursprung) expanded it further, so I'm adding his code and explanation here.
The question was about getting a part of a route between points (so basically what this post teaches) and Babel expansion was:
"
Expanding the solution by @Dror Bogin you can use this expression together with array_foreach() to include not just two points, but any number of points - in the following example points 1 to 9 (first expression below):



In this case, you connect points in a regular order: 1,2,3,4... Changing the expression a bit, you can even define any arbitrary order of the points like from point 17 to 10 to 4 to 3 as can be seen on the next screenshot (second expression below):


First expression to connect points in a regular order, to be defined in line 4:

collect_geometries (
    array_filter (
        array_foreach (
            generate_series (1,9), -- define here the list of $id's of the points to be included
            line_substring(
                $geometry,
                line_locate_point(
                    $geometry,
                    closest_point(
                        $geometry,
                        geometry(get_feature_by_id ('points',@element))
                    )
                ),
                line_locate_point( 
                    $geometry,
                    closest_point(
                        $geometry,
                        geometry(get_feature_by_id ('points',@element+1))
                    )
                )
            )
        ),
        @element IS NOT NULL
    )
)
 
Second expression to connect points in any arbitrary order, to be defined in line 3:
with_variable (
    'array',
    array (17,10,4,3),  -- define here the order in which the points should be connected
    collect_geometries (
        array_filter (
            array_foreach (
                @array,
                line_substring(
                    $geometry,
                    line_locate_point(
                        $geometry,
                        closest_point(
                            $geometry,
                            geometry(get_feature_by_id ('points',@element))
                        )
                    ),
                    line_locate_point( 
                        $geometry,
                        closest_point(
                            $geometry,
                            geometry(get_feature_by_id ('points',array_get (@array,array_find (@array, @element)+1)))
                        )
                    )
                )
            ),
            @element IS NOT NULL
        )
    )
) 

"

It's a great expansion and uses a variety of array and variable expression functions to solve this problem in an elegant way.



Comments