TransWikia.com

Align existing points along a line with even spacing

Geographic Information Systems Asked on July 14, 2021

I have a number of existing points which are snapped to a line, however they are unevenly spaced along the line, like this:

enter image description here

How do I align them so they are evenly spaced out along the line like this:

enter image description here

I know how to create new, evenly spaced out points along a line, however these are existing points which have attribute info that I want to keep.

I am using QGIS 3.14.16.

2 Answers

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

Correct answer by Ben W on July 14, 2021

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

Answered by Yoann Quenach de Quivillic on July 14, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP