To calculate the distance between two points along a line geometry, you can use QGIS expressions based on the following functions (see below how to combine these elements). Points not even have to be exactly on the line, it works even if the point is slightly away: it then automatically takes the nearest point on the line.
line_substring
to get the part of the initial line between two points
line_locate_point
To get the current point on the line.
length
to calculate the length of this substring (part of the line).
Let's suppose you have (change the names accordingly in the expression below if using other names):
- A line layer called
line
with an attribute line_id
.
- A point layer calles
stops
with an attribute id
(that numbers the points in the correct sequence: from the first to the next etc.) and another attribute on_line
(indicating on what line the point lies). If you don't have such a field, see below (edit 3).
On the point layer, calculate the length from each point to the next one with this expression (e.g. using Field calculator to create a new attribute). To get the previous point, replace +1
at the end of line 10 with -1
.
length(
line_substring(
geometry (get_feature ('line','line_id', on_line)),
line_locate_point(
geometry (get_feature ('line','line_id', on_line)),
geometry (get_feature_by_id (@layer, id))
),
line_locate_point(
geometry (get_feature ('line','line_id', on_line)),
geometry (get_feature_by_id (@layer, id+1))
)
)
)
Screenshot: the distance from each white point to the next one along the corresponding line is calcluated; the label of each points shows the distance to the next one on the same line: the yellow line shows one of the segments, the one that has a distance of 21.92. The last point in each line has no distance as there is no next point:

Edit 1
If you have your id
field in the stop (point) layer in a string format as on your screenshot (like Stop A1
, Stop B22
, Stop C15
etc.), use this expression to extract the digits to create an integer id-field:
regexp_substr ("id", '(\\d+)')
Edit 2
As both your layers are in CRS EPSG:4326
, the results are in degrees, not in meters. This does not make sense. There are two options to solve this: either reproject both layers to a CRS using meters as units (e.g. local UTM zone). Or use a slightly modified expressions that automatically handles this issue. Just replace 3857
in the second last line with the EPSG-code of the CRS you want to use:
length (
transform (
line_substring(
geometry (get_feature ('line','line_id', on_line)),
line_locate_point(
geometry (get_feature ('line','line_id', on_line)),
geometry (get_feature_by_id (@layer, id))
),
line_locate_point(
geometry (get_feature ('line','line_id', on_line)),
geometry (get_feature_by_id (@layer, id+1))
)
)
,@layer_crs,
'EPSG:3857'
))
Edit 3
It even works if you donf't have a field on_line
. You can get this information as part of the expression with overlay_nearest
In this case, replace geometry (get_feature ('line','line_id', on_line))
with array_first (overlay_nearest ('line', $geometry))
- so the expression looks like:
length(
line_substring(
array_first (overlay_nearest ('line', $geometry)),
line_locate_point(
array_first (overlay_nearest ('line', $geometry)),
geometry (get_feature_by_id (@layer, id))
),
line_locate_point(
array_first (overlay_nearest ('line', $geometry)),
geometry (get_feature_by_id (@layer, id+1))
)
)
)