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.

enter image description here

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.

enter image description here

*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

enter image description here

After running script

enter image description here


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)

space_evenly_along_line