Creating fixed length segment of line around point on line

I've a layer of rivers as lines, split in segments by their size. I've created random points on those lines and selected some of them - 90 in total.

I'd like to take those selected points on lines, set them as midpoints, and get a segments of 5 km upstream and 5 km downstream from the given points on rivers. I managed to split lines at points but that doesn't give me much.

Some of the segments will be shorter than 10 km as some rivers are small and flow in the sea. On some those points are close to sea while the river is big enough to shift the 10 km segment upstream.

Is this doable or manual is the only option here to achieve this?

You can run "Service Area (From Layer)" from processing toolbox.

• Choose your river layer as input (make sure it is in a metric CRS)
• Choose "Shortest" as path type to calculate
• And your points as Vector layer with starting points (make sure it is in the same metric CRS as your river layer)
• As Travel cost enter 5000
• If needed, you can also use the advanced options and e.g. add a stream direction It will trace lines along your river network up to 5km from each point as single lines (so you get 90 output features). Example: If you need each direction from a point as single feature (so 180 in total), you can run "Split lines with points" from SAGA processing tools to split the result.

You can achieve this using a virtual layer.

It would compute what 5km means in terms of fraction of the line length. Then, it will identify at which fraction of the line length is the point located. It will look at the difference between 1.0 (full length) and the expected line fraction for the extrapolated point after the point. If less than 1, the ending fraction (point) is kept. If more than 1, the endpoint is kept and the difference is added to the start point, which is now located at current point fraction - fraction of 5km - difference between 1 and end point fraction. To avoid going before the start (0.0), another comparison is done.

At last, once the starting and ending fractions have been identified, a line substring is created. The resulting line will always be 10km long, centered on the mid point as much as possible (except if there are less than 5km from the start, though this can be changed easily)

go to the menu Layer > Add Layer > Add/Edit Virtual Layer... and enter the following query. Replace pointloc and river by your true layer name, and feel free to keep any field from either layer (like p.id and l.id).

The 2 first computed fields (fromPC and toPC) and here for information only, you can safely remove them.

select p.id, l.id,
ST_Line_Locate_Point(l.geometry,p.geometry) - 5000.0/ st_length(l.geometry) + min(0,1.0-(ST_Line_Locate_Point(l.geometry,p.geometry) + 5000.0/ st_length(l.geometry))) fromPC,
min(1.0,ST_Line_Locate_Point(l.geometry,p.geometry) + 5000.0/ st_length(l.geometry)) toPC,
st_line_substring(l.geometry,ST_Line_Locate_Point(l.geometry,p.geometry) - 5000.0/ st_length(l.geometry) + min(0,1.0-(ST_Line_Locate_Point(l.geometry,p.geometry) + 5000.0/ st_length(l.geometry))),min(1.0,ST_Line_Locate_Point(l.geometry,p.geometry) + 5000.0/ st_length(l.geometry))) as geometry
from pointloc p
join  river l
on st_intersects(l.geometry,p.geometry) You can do this in two steps:

1. Split the lines at your points.

2. Use an expression to create geometries (see here for details) using the function line_substring.

In detail:

1. First combine all connected lines to one feature (river) if that is not yet the case. Than split the lines with one of these methods or use the following procedure: convert the points to small lines that you can than use for splitting. Convert the points to lines with Geometry by expression this expression: extend( make_line( \$geometry, project( \$geometry, 1,0 )), 1, 0 ). Than use Menu Processing / Toolbox / Split with lines - use the rivers layer as input and the created lines as Split layer.

2. Than you just have to create your lines using the following expression (explanation how the expression works below):

with_variable (
'dist',
15000,
combine(
if (
intersects (
start_point (\$geometry),
aggregate( 'points', 'collect', \$geometry)
),
line_substring (\$geometry, 0, @dist),
make_line (centroid(\$geometry), centroid(\$geometry))
),
if (
intersects (
end_point (\$geometry),
aggregate( 'points', 'collect', \$geometry)
),
line_substring(
\$geometry,
length(\$geometry)-@dist,
length(\$geometry)
),
make_line (centroid(\$geometry), centroid(\$geometry))
)
)
)

Screenshot: blue river-line, red river sections with length=5000 to both sides of the white dots: Explanation: You have to create two lines with line_substring, one for each direction (starting from the point): towards the mouth and towards the source of the river. For the direction running in the same direction as the line, you can use the following expression: line_substring (\$geometry, 0,15000), for the other direction: line_substring(\$geometry,length(\$geometry) - 15000, length(\$geometry)). However, the first and last section of the line will be considered two - so I added an if-condition: a line should be drawn only if the start-point of the current line feature intersects with one of the points: you only want these distance-line where you have points. As result_when_false argument, I first used NULL - this creates a valid output, however when you combine the two lines (towards mouth and towards source) this reason, for result_when_false, I created a 0-length line (line from centroid(\$geometry) to centroid(\$geometry)). Now you can combine both sides with combine(). To have more flexibility with playing around with different length, I added a with_variable() function: now just change the value on line 3 to get outputs for different lengths.

• Thanks everyone for answers! I liked the Service area option, while the virtual layer didn't work at all for some reason. The big issue I'm facing is the tributaries. Some points are close to the points where two or more rivers join. All the automatic options see all of them equal, so the resulting segments are all over the many tributaries. There's no option to include in the query a request the selection to follow only the biggest stream, defined in feature properties, is there?
– edge
Apr 1 '21 at 13:11
• I tried the virtual layer solution as well, it works. In the initial version, there was a comma missing at the end of line 1. In the version you find now, it should work. Service area is probably indeed the easiest solution. For the tributaries, you could just consider dis-connecting the lines (creating a small gap), so no continuing line can be found there. More a hack/workaround, though. Apr 1 '21 at 13:18

What worked best for me in the end was the extraction of line segments using buffer zones.

1. Open "Buffer" from Processing Toolbox and choose a distance of 5000 m: 1. Go to Vector->Processing->Intersection and select your river layer as Input Layer and the created Buffer layer as overlay layer: 1. This will create a layer with the described line segments of 10 km each around the midpoint. This output layer is also useful for further geometry calculations (length, sinuosity,...). I mention this because the above "Service Area (From Layer)"-solution didn't return the correct length in my case.
• Indeed also a solution for a slightly modified question: the line lenght itself will not be the same as the buffer size (so not 5000m) unless the lines are absolutely straight from the center. Jun 8 '21 at 16:49

You can use QGIS expressions to generate sections along the river line of a certain distance.

Use the following expression with Geometry by expression or Geometry generator (see here for details about these two options) on the point layer. You only have to replace rivers (line 3) with the name of your line layer:

with_variable (
'river',
array_first (overlay_nearest('rivers',\$geometry)),
union (
line_substring (
@river,
line_locate_point( @river, \$geometry)-5000,
line_locate_point( @river, \$geometry)
),
line_substring (
@river,
line_locate_point( @river, \$geometry),
line_locate_point( @river, \$geometry)+5000
)
)
)

The expression in action, here with Geoemtry generator: from the white dots, a red line along the river (blue) with line of 5000 m is generated to both sides of the point: 