You may run the following code from the Python Console:
searching_dist = 20 # Set it as you want
lines = QgsMapLayerRegistry.instance().mapLayersByName('line')[0] # Replace 'line' with the name of the line layer
points = QgsMapLayerRegistry.instance().mapLayersByName('points')[0] # Replace 'points' with the name of the point layer
cr = points.crs()
dist_calc = QgsDistanceArea()
dist_calc.setSourceCrs(cr)
dist_calc.setEllipsoid(cr.ellipsoidAcronym())
dist_calc.setEllipsoidalMode(cr.geographicFlag())
# This layer contains all the points created along the line (it isn't an output)
crs = points.crs().toWkt()
temp_pts = QgsVectorLayer('Point?crs='+ crs, 'temp_pts' , 'memory')
prov = temp_pts.dataProvider()
tmp_points = {}
index = QgsSpatialIndex()
for line in lines.getFeatures():
line_geom = line.geometry()
(first_line_point, end_line_point) = (line_geom.interpolate(0).asPoint(), line_geom.interpolate(line.geometry().length()).asPoint())
pt1 = QgsFeature()
pt1.setGeometry(QgsGeometry.fromPoint(first_line_point))
(result, feat) = prov.addFeatures([pt1])
tmp_points[feat[0].id()] = feat[0]
index.insertFeature(feat[0])
pt2 = QgsFeature()
pt2.setGeometry(QgsGeometry.fromPoint(end_line_point))
(result, feat) = prov.addFeatures([pt2])
tmp_points[feat[0].id()] = feat[0]
index.insertFeature(feat[0])
for point in points.getFeatures():
geom = point.geometry()
nearest = index.nearestNeighbor(geom.asPoint(), 1)
tmp_pt_geom = tmp_points[nearest[0]].geometry()
dist = dist_calc.measureLine(geom.asPoint(), tmp_pt_geom.asPoint())
if dist <= searching_dist:
points.dataProvider().changeGeometryValues({point.id(): tmp_pt_geom})
points.triggerRepaint()
For get it working, you only need to preliminary load the point and line layers in the Layers Panel and then type their names where needed (I left a comment in the code above). Furthermore, you need to specify a searching distance (I used 20
meters, set it as you want).
Finally, the above code will directly edit the geometries within the point layer, so remember to create a backup copy of your data before trying it.
As an example, starting from this situation:
you will get this result:
and, zooming more on it (as you did in your original question):
EDIT
If you want to snap the points by considering the final line nodes only, you may use this slightly edited code:
searching_dist = 20 # Set it as you want
lines = QgsMapLayerRegistry.instance().mapLayersByName('line')[0] # Replace 'line' with the name of the line layer
points = QgsMapLayerRegistry.instance().mapLayersByName('points')[0] # Replace 'points' with the name of the point layer
cr = points.crs()
dist_calc = QgsDistanceArea()
dist_calc.setSourceCrs(cr)
dist_calc.setEllipsoid(cr.ellipsoidAcronym())
dist_calc.setEllipsoidalMode(cr.geographicFlag())
# This layer contains all the points created along the line (it isn't an output)
crs = points.crs().toWkt()
temp_pts = QgsVectorLayer('Point?crs='+ crs, 'temp_pts' , 'memory')
prov = temp_pts.dataProvider()
tmp_points = {}
index = QgsSpatialIndex()
for line in lines.getFeatures():
line_geom = line.geometry()
end_line_point = line_geom.interpolate(line.geometry().length()).asPoint()
pt1 = QgsFeature()
pt1.setGeometry(QgsGeometry.fromPoint(end_line_point))
(result, feat) = prov.addFeatures([pt1])
tmp_points[feat[0].id()] = feat[0]
index.insertFeature(feat[0])
for point in points.getFeatures():
geom = point.geometry()
nearest = index.nearestNeighbor(geom.asPoint(), 1)
tmp_pt_geom = tmp_points[nearest[0]].geometry()
dist = dist_calc.measureLine(geom.asPoint(), tmp_pt_geom.asPoint())
if dist <= searching_dist:
points.dataProvider().changeGeometryValues({point.id(): tmp_pt_geom})
points.triggerRepaint()