10

I would like to align multiple points to a line, or lines within a layer, using a specified tolerance or buffer around the line objects. Please refer to the sample sketch attached.

For the sake of this example, the points closest to the line in the BEFORE picture are within 5 map units of the line, while the outermost points are over 10 map units away. I would like to snap the closest points onto the nearest line, using a tolerance of 5 map units to achieve the result in the AFTER picture.

enter image description here

  • So the point needs to be 0 map units laterally of the line, but do you care about where the point ends up longitudinally of the line with respect to the point's original location? – Joe Apr 19 '17 at 1:00
  • The ideal scenario would be to move the points using a perpendicular path to the line. However, the intention is to use fairly small tolerances. If the points are moved longitudinally or latitudinally, towards the line, it wont offset the points that much from a preferable perpendicular position. – Ed Camden Apr 19 '17 at 1:18
  • Not sure if this is the best way, but one way I can think of is to write some python code to analyse the two datasets and produce some point coordinates. If this is what you think you want, then let me know and i can provide an answer for you. E.g. for every point, if the absolute value of lat dist from line <= 5 units, then lateral distance = 0. You'd have to import a gdal library to convert the x,y values into coords. See comments in: gis.stackexchange.com/questions/185445/… – Joe Apr 19 '17 at 2:13
  • With PyQGIS, it can be produced a memory layer where points are snapped according to previously considered tolerance of 5 map units and a perpendicular path to the line. See my answer. – xunilk Jul 4 '17 at 3:35
13

There's a built-in tool to do this in the (unreleased) QGIS 3.0 version. You can get a nightly snapshot from the QGIS website to test this in advance.

To do this:

  1. Run the "Snap geometries to layer" processing algorithm
  2. Select your points layer as the "input layer"
  3. Select the line layer as the "reference layer"
  4. Enter a suitable tolerance (maximum distance to move points while snapping)
  5. Change the behavior to "Prefer closest point"

enter image description here

Here's the result, showing the original points as "x", and the snapped points as green dots. I've used a tolerance here so that only some of the input points are snapped.

enter image description here

  • This is exactly what I need. Unfortunately my employer only installs LTR versions of QGIS and we are all restricted from downloading and installing test versions. (sigh) I guess it's a matter of waiting. Is this a standard/built in function or a plug-in? – Ed Camden Apr 19 '17 at 3:02
  • Standard functionality relying on changes in the c++ classes - there's no way to manually copy this to an older version. You could potentially try installing using OSGEO4W on a different machine, and then copying the osgeo4w folder to a USB stick to run on your workstation. I've had luck with that approach in the past. – ndawson Apr 19 '17 at 3:23
  • 1
    For old version have a look on this plugin. docs.qgis.org/2.14/en/docs/user_manual/plugins/… – iRfAn Apr 27 '17 at 8:07
  • it seems the plugin does not support points' layers. – Mykola Kozyr Apr 27 '17 at 12:07
5

This can be afforded with PyQGIS. For next situation:

enter image description here

following code, considering a tolerance of 5 map units, was ran at the Python Console of QGIS:

from math import sqrt

registry = QgsMapLayerRegistry.instance()

points = registry.mapLayersByName('points')
line = registry.mapLayersByName('line')

feat_points = [ feat for feat in points[0].getFeatures() ]
feat_line = line[0].getFeatures().next()

new_points = []

for feat in feat_points:
    pt = feat.geometry().asPoint()
    sqrdist, point, vertex = feat_line.geometry().closestSegmentWithContext(pt)
    if sqrt(sqrdist) <= 5:
        new_points.append(point)
    else:
        new_points.append(pt)

epsg = points[0].crs().postgisSrid()

uri = "Point?crs=epsg:" + str(epsg) + "&field=id:integer""&index=yes"

mem_layer = QgsVectorLayer(uri,
                           'new_points',
                           'memory')

prov = mem_layer.dataProvider()

feats = [ QgsFeature() for i in range(len(new_points)) ]

for i, feat in enumerate(feats):
    feat.setAttributes([i])
    feat.setGeometry(QgsGeometry.fromPoint(new_points[i]))

prov.addFeatures(feats)

QgsMapLayerRegistry.instance().addMapLayer(mem_layer)

It was produced a memory layer where points were snapped according to previously considered tolerance of 5 map units and a perpendicular path to the line.

enter image description here

1

You can also do this in the Field Calculator with the refFunctions plugin. You can use the Field Calculator to update a layer geometry as well as fields. refFunctions gives you a "geomdistance" function to find the nearest line within a given distance(or "geomnearest" if you don't want a threshold) and will return an attribute or the geometry, and the "closest_point" function will find the closest point on a given geometry. String them together like so to calculate new geometries for your point layer:

closest_point(geom_from_wkt(geomdistance('snap_lines','$geometry',10)) , $geometry)

Instead of directly updating the geometry you can calculate a field with the snapped geometry instead. I store multiple geometries for snapping culvert points to different stream layers and I can easily update the point geometry in the Field Calculator depending on whose stream lines I need to use.

There are some limitations with this, both layers need to be the the same CRS and the geomdistance function will give you an error if you have more than 100,000 points but you can change this limit if you edit refFunctions plugin file.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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