5

I have US interstate ramp end location data points that I need to match road network data. All the points are off by a few meters as they were collected in the field. I am hoping there is a way to automatically match/snap all the points to the end nodes of the ramps if within 20 meters. I can't do it manually as there are over 30,000 points. Road network and ramp points are different layers.


EDIT: The blue lines are the on/off ramps of an interstate. The red points are the field collected location of the end of the off ramp. I need the red points to auto snap on to end of the off ramp.

After thinking about this and the goal all I really need is the start and end points for each ramp and a way to differentiate between them. I'll ask in a different question. However for clarity sake I thought I would post what I originally was requesting.

I haven't found any information on how to automatically snap points to line ends in bulk. Anything I've found that was similar didn't translate.

enter image description here enter image description here

2
  • 1
    Could you provide a graphical example of what you precisely need? What you have done before? Then, end nodes is the end of the current line feature or the end of the whole line? Please, edit your question by adding more information.
    – mgri
    Commented Jul 18, 2017 at 9:24
  • Thanks, I have just voted for reopening your question. I will probably come back with an answer.
    – mgri
    Commented Jul 18, 2017 at 19:29

1 Answer 1

3

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:

enter image description here

you will get this result:

enter image description here

and, zooming more on it (as you did in your original question):

enter image description here

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()
3
  • Ok, is it possible to get it to only snap to the end node? As you can see from my images there is a direction for each ramp which is in the OSM data I am using. Since these points are supposed to be at the end of an exit ramp they need to snap to only end nodes. If they snap to a start node then they will be on the entrance ramp.
    – brink
    Commented Jul 19, 2017 at 17:28
  • @brink Please, see my edited code. It simply avoids creating the first_line_point variable, so you will only work with the final nodes. However, I assume that the line directions reflect the real digitizing direction.
    – mgri
    Commented Jul 19, 2017 at 17:41
  • @brink did you try my solution?
    – mgri
    Commented Jul 24, 2017 at 8:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.