Align existing points along a line with even spacing
I've put together a Python script which should do what you want here.
Open the Python console, and use the tool bar icon outlined in red below to open a new editor.
Copy the code below and paste into the editor (not the Python prompt) and use the icon outlined in red below (green arrow) to run the code.
*A couple of notes:
- Both your line and point layers should be in a projected CRS (the resulting memory layer containing the evenly spaced points will be created with the same CRS as the original point layer.
- You will need to edit the first 2 (executed) lines of code to match the names of your line and point layers.
'''This script creates evenly spaced points along lines from points which
lie along the line's geometry but are randomly spaced. Order of point features
from the start point of the line are preserved. Fields and attributes are also
copied from the original point layer and added to the resulting memory layer'''
# Edit the 2 lines below to match the names or your line & point layers
line_layer_name = 'Line_layer_A'
point_layer_name = 'Random points on lines'
project = QgsProject().instance()
lines = project.mapLayersByName(line_layer_name)[0]
points = project.mapLayersByName(point_layer_name)[0]
line_fts = [f for f in lines.getFeatures()]
point_fts = [f for f in points.getFeatures()]
type_crs = 'Point?crs={}'.format(points.crs().authid())
temp_layer = QgsVectorLayer(type_crs, 'Spaced_points', 'memory')
temp_layer.dataProvider().addAttributes([f for f in points.fields()])
temp_layer.updateFields()
def distance_to_point(line_feature, point_xy):
'''Returns the distance from the start point of a line feature to a point
which lies along the line's geometry. In this script, we use this value to
preserve the order of the unevenly spaced points along each line feature'''
geom = line_feature.geometry()
verts = [v for v in geom.constGet().vertices()]
point, minDistPoint, nextVertex, leftOf = geom.closestSegmentWithContext(point_xy)
start_to_vert_after = geom.distanceToVertex(nextVertex)
point_to_vert_after = minDistPoint.distance(QgsPointXY(verts[nextVertex]))
distance_of_point_along_line = start_to_vert_after - point_to_vert_after
return distance_of_point_along_line
with edit(temp_layer):
for l in line_fts:
line_geom = l.geometry()
snapped_points = [p for p in point_fts if line_geom.buffer(0.01, 4).intersects(p.geometry())]
if snapped_points:
dist = {}
for p in snapped_points:
dist[p] = distance_to_point(l, p.geometry().asPoint())
# guard against division by zero error
if len(snapped_points) == 1:
feat = QgsFeature()
feat.setAttributes(snapped_points[0].attributes())
new_geom = line_geom.interpolate(line_geom.length()/2)
feat.setGeometry(new_geom)
temp_layer.addFeature(feat)
else:
interp_dist = line_geom.length()/(len(snapped_points)-1)
if len(dist) > 0:
n = 0
for k, v in {k: v for k, v in sorted(dist.items(), key=lambda item: item[1])}.items():
feat = QgsFeature()
feat.setAttributes(k.attributes())
new_geom = line_geom.interpolate(n)
feat.setGeometry(new_geom)
temp_layer.addFeature(feat)
n = n + interp_dist-0.001
project.addMapLayer(temp_layer)
Results:
Original, randomly spaced points along line features
After running script
Here is a slighlty modified version, that transforms the layer in-place. It also works if the line layer and point layer have different CRS :
def space_evenly_along_line(point_layer, line_layer):
points_by_line = {line.geometry(): [] for line in line_layer.getFeatures()}
# Reproject geometries if neeeded
if point_layer.crs() != line_layer.crs():
transform = QgsCoordinateTransform(
line_layer.crs(), point_layer.crs(), QgsProject.instance()
)
for k in points_by_line:
k.transform(transform)
# Sort point features by line feature
for f in point_layer.getFeatures():
min_dist = None
line_feature = None
for k in points_by_line:
dist = k.distance(f.geometry())
if min_dist is None or dist < min_dist:
min_dist = dist
line_geometry = k
points_by_line[line_geometry].append(f)
# Sort by distance along line
for line_geometry, point_features in points_by_line.items():
point_features.sort(key=lambda x: line_geometry.lineLocatePoint(x.geometry()))
with edit(point_layer):
point_layer.beginEditCommand("Even spacing")
for line_geometry, point_features in points_by_line.items():
total_dist = line_geometry.length()
# No Feature, nothhing to do
if len(point_features) == 0:
continue
# Only one feature place it in the middle
if len(point_features) == 1:
point_layer.changeGeometry(
point_features[0].id(), line_geometry.interpolate(total_dist / 2)
)
continue
# Move each feature along the line
step = total_dist / (len(point_features) - 1)
for i, point in enumerate(point_features):
point_layer.changeGeometry(
point.id(), line_geometry.interpolate(step * i)
)
point_layer.endEditCommand()
Exemple Usage
>>> project = QgsProject().instance()
>>> points = project.mapLayersByName("Points")[0]
>>> lines_3857 = project.mapLayersByName("Lines EPSG:3857")[0]
>>> space_evenly_along_line(points, lines_3857)