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:
How do I align them so they are evenly spaced out along the line like this:
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.
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:
'''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
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)
Answered by Yoann Quenach de Quivillic on July 14, 2021
Get help from others!
Recent Questions
Recent Answers
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP