# Length of line on specific point (from point layer)

I have a line and a point layer. The points are manually set next to the line at specific places. Now I would like to get the distance between the end/start of the line and the points.

Distance from the black point along red line to start and to end of line:

• Distance from Blackpoint #1 along the red Line to Start (i.e. 5km)
• Distance from Blackpoint #1 along the red Line to End (i.e. 50km)
• Distance from Blackpoint #2 along the red Line to Start (i.e. 10km)
• Distance from Blackpoint #2 along the red Line to End (i.e. 45km)

As you can see in the image, the black points are not exactly ON the line. The closest line-node should give the result. I need to know the distances for every black point (I have hundreds of them!).

I'm looking for a QGIS, Python or PostGIS solution. Because the black points and the line are changing some times, it would be great to have a "dynamic" approach without changing the geometry of the line.

• Really interesting, but I have two questions: 1) for each point, do you need the distance form the start/end or the shortest distance from the start/end? And 2) the red dotted line is a closed line?
– mgri
Commented Jan 30, 2017 at 11:05
• I need the distance along the red dotted line. The line can be closed. Commented Jan 30, 2017 at 11:13
• Yes, along the line but, in other words: taking as example the point No.15, do you need the shortest distance or the longest distance along the line?
– mgri
Commented Jan 30, 2017 at 11:20
• In my special case, I always need the distance from point to the end of the line. Example: point 1 to end (50km) point 15 to end (5km) Commented Jan 30, 2017 at 11:27
• You can follow the approach in this question, and then calculate the lenght of each of the split line segments: gis.stackexchange.com/questions/64006/… Commented Jan 31, 2017 at 15:41

NOTE I edited the code because the questioner preferred to choose the track and the points he wanted to measure. The update in the code is the possibility to create a number of point output layers equal to the number of the line features but, if selected in the main dialog, this number could be lower.

I propose a solution using PyQGIS. My idea was creating a certain number of temporary points along the line and then using the nearest neighbor of them from each point of the input points (i.e. your Blackpoints) for measuring the distance from the start/end of the line.

Before posting the code, I need to do some remarks:

1. in addition to the point layer and the line layer, my solution requires an additional parameter, called `step`, which is simply the spatial interval for the creation of the temporary points (remember that the accuracy of the result heavily depend from this parameter!);
2. all the distances are calculated from the nearest neighbor of each Blackpoint to the start/end of the line (this means that the distance between the Blackpoint and its nearest neighbor is NOT evaluated, but I can immediately and easily add it to the code if needed).

The code will return a point memory layer which stores the same geometries and attributes of the input layer, plus two additional fields:

• `"DIST_START"`, which stores the distance from the start of the line;
• `"DIST_END"`, which stores the distance from the end of the line.

This is the code:

``````##Points=vector point
##Only_use_selected_points=boolean False
##Line=vector line
##Only_use_selected_lines=boolean False
##step=number 1

from qgis.core import *
from qgis.PyQt.QtCore import QVariant

layer1 = processing.getObject(Points)
crs = layer1.crs().toWkt()
layer2 = processing.getObject(Line)

if Only_use_selected_lines:
line_feats = layer2.selectedFeatures()
else:
line_feats = layer2.getFeatures()

for ft in line_feats:
index = QgsSpatialIndex()
tmp_points = {}
line_geom = ft.geometry()
len = line_geom.length()
current = 0

# This layer contains all the points created along the line (it isn't an output)
temp_pts = QgsVectorLayer('Point?crs='+ crs, 'temp_pts' , 'memory')
prov = temp_pts.dataProvider()
# Uncomment the next line if you want to see the 'temp_pts' layer

while current < len:
point = line_geom.interpolate(current) # Create a point along the line at the current distance
fet = QgsFeature()
fet.setGeometry(point)
tmp_points[feat[0].id()] = current
index.insertFeature(feat[0])
current += step # Increase the distance by the step provided

# This layer is the final output which stores the distances from the closest point along the line and the start/end of the line
out = 'output_%s' % (ft.id())
output = QgsVectorLayer('Point?crs='+ crs, out, 'memory')
prov2 = output.dataProvider()
fields = layer1.pendingFields() # Fields from the input layer
fields.append(QgsField('DIST_START', QVariant.Double, '', 10, 3)) # Name for the new field in the output layer
fields.append(QgsField('DIST_END', QVariant.Double, '', 10, 3)) # Name for the new field in the output layer
output.updateFields()

if Only_use_selected_points:
point_feats = layer1.selectedFeatures()
else:
point_feats = layer1.getFeatures()

for feat in point_feats:
geom = feat.geometry()
attrs = feat.attributes()
nearest = index.nearestNeighbor(geom.asPoint(), 1)
dist_start = tmp_points[nearest[0]]
attrs.append(dist_start)
dist_end = len - tmp_points[nearest[0]]
attrs.append(dist_end)
outGeom = QgsFeature()
outGeom.setGeometry(geom)
outGeom.setAttributes(attrs)

# Add the 'output' layer to the Layers panel
``````

Now let's give an example. Starting from these sample layers (14 points, the line has a length of about 16 km) and using a `step` of 1 m:

I obtain this point layer:

with this Attribute Table (the values are expressed in meters):

• The code is working very well. The result is accurate and the usage is simple. It would be great, if I could also choose the track and the points I want to measure (if there are more then one track). The LRS-Plugin (Answer from Nate Wanner) is able to handle more tracks at once. A "only use selection"-Option would also be great. Commented Feb 7, 2017 at 7:46
• @MAP I edited the code. I have perfomed some tests and it seems to work by now, but please let me know if it's the same for you. With this edit, you can select the points and/or the lines to be used in the analysis.
– mgri
Commented Feb 7, 2017 at 18:37
• Very impressive =) Commented Feb 8, 2017 at 10:20

Here's a PostGIS solution based on the answer here.

Suppose your points table looks something like this (i.e., a geometry column and an id column):

``````CREATE TABLE points (geom Geometry(point), ptid int);
INSERT INTO points VALUES (ST_MakePoint(2.1,2.1),1);
INSERT INTO points VALUES (ST_MakePoint(3.1,3.1),2);
``````

Then you can use `ST_LineLocatePoint()` to snap each point to the closest point on the line, and `ST_LineSubstring()` to split the line. Technically, `ST_LineLocatePoint()` returns the fraction along the line, which is the argument required by `ST_LineSubstring()`.

``````WITH line AS (SELECT ST_MakeLine(ARRAY[ST_Point(1,2), ST_Point(2,2), ST_Point(3,3), ST_Point(4,3)]) AS geom)
SELECT ptid,
ST_Length(ST_LineSubstring(line.geom,0,ST_LineLocatePoint(line.geom, points.geom)))
AS distance_to_start,
ST_Length(ST_LineSubstring(line.geom,ST_LineLocatePoint(line.geom, points.geom), 1))
AS distance_to_end
FROM line, points;
``````
• This worked nicely for me. Just a note - if you aren't using a SRID that is already in km you will need to ST_Transform each linestring to a projection using meters and then divide by 1000. Commented Feb 2, 2017 at 23:59
• Since only the distance is required, there's no need to use `ST_LineSubstring`. The distance from start is given by `ST_Length(line.geom) * ST_LineLocatePoint(line.geom, points.geom)`, and to the end is `ST_Length(line.geom) * (1 - ST_LineLocatePoint(line.geom, points.geom))` Commented Apr 20, 2023 at 16:19

If I were doing this today I would take a look at the Postgis idea @amball posted in the comments. If you prefer a QGIS solution, you can use the LRS plugin in QGIS. It is, unfortunately, not a dynamic solution, but it will handle a few hundred points. I used successfully it to locate around 100 GPS coordinates along a stream, which were never precisely on the stream line. There is some additional information on the plugin available in this post.

The documentation is found here. When I used the plugin a little over a year ago it took me a few reads and some head scratching to get it working but saved a load of time once it was going. It is important to understand that you need to go through the "Calbiration" tab (more like a "Setup") before you start. This is how it figures out distances along your lines. Because it was developed for bus routes, I found you also need to include a route field in both my line and point layers so the plugin can match them. After I got through that, it worked like a charm to correlate GPS points along a stream that weren't quite on the line.

If I understand your comments correctly, you want the distance from the point to the end of the line, which would be equivalent to riding the bus backwards. I haven't tried it, but expect you can account for this in the calibration step. You will have your red line as the lines layer. Then create a points layer with the start and end of the line marked to use for calibration. For the measure field in your calibration field, enter the total length of your line as the start point and 0 as your end point. The calibration will then figure out node distances along the line in reverse.

Once you have completed the Calibration tab, you should be able to skip over to the "Measures" tab and input the layer with black points. Running this creates an in-memory layer with all the point info, plus the made-up route ID from your calibration and a measurement column. Make sure you use the rigth units on the "Max point distance", which tells it how far off the point can be and still be considered a stop on the line.

Because it is an in-memory layer, if you want the information to be available after you exit QGIS you need to right-click and Save As.

• I also tried the the LRS plugin. It's a well suitable solution for handling more then one track (like in my case). That you can define the start- and end-point is also very helpful for my tracks-situation. The only problem is, that there is no result if two or more tracks share the same nodes - but I can see, that this is a real difficult task. Commented Feb 7, 2017 at 7:37
• @MAP That is an interesting point and scenario. I'm trying to think through how to organize the paths/lines as an array or within a table referencing line segments, but nothing seems straightforward in my mind. Commented Feb 7, 2017 at 12:59

For a solution using Shapely, take a look at this gist:

Basically this is a matter of using the linear referencing functionality of Shapely, with some added handling of distances etc.

Line 38 in my script calculates the closest point on a line to a given point using a combination of line.interpolate() and line.project()

For the distance from start/end to the snapped point, we are using line.project() with normalized true. This gives us "how far into the line is the point" as a fraction, i.e: the start is 0, the end is 1 and halfway on the line is 0.5 (see line 39)

If we know the length of the line the distance from start/end is simple (lines 41 & 42). Since Shapely used euclidean distance calculations, I've added a simple helper using the Vincenty library

The rest of the script is basically just reading geojsonfiles and looping.

For expression enthusiasts (QGIS 3.30.1).

A very handy feature of the `line_locate_point` function is that it automatically finds the closest point on the line geometry from the point of interest.

Furthermore, by calculating the expression into a virtual field the distances will update dynamically with any edits made to either the point or line layer.

##### From the help pane of the field calculator

function line_locate_point
Returns the distance along a linestring corresponding to the closest position the linestring comes to a specified point geometry.

syntax
line_locate_point(geometry,point)

##### Expression

Note, the two `line_locate_point` functions can be calculated separately into different fields, but here they are combined into one string field called `"dist"`.

``````-- get the line by finding the nearest line to the point (another method may be more appropriate, depending on the situation)
with_variable('line', overlay_nearest('line', \$geometry, limit:=1)[0],

-- find the distance from the start of the line and save it to a variable
with_variable('from_start',
to_int(line_locate_point(@line, \$geometry)),

-- find the distance from the end of the line and save it to a variable
with_variable('from_end',
-- here the line is reversed to get the distance from the end
to_int(line_locate_point(reverse(@line), \$geometry)),

-- concatenate the distance values, separated by a comma
@from_start || ', ' || @from_end
)
)
)
``````

To extract the distance from start and end respectively:

``````-- dist from start
string_to_array("dist")[0]

-- dist from end
string_to_array("dist")[1]
``````