Skip to content

Commit 8acc286

Browse files
committed
[FEATURE] Snap to layer algorithm accepts a mode parameter
With a new option to prefer to snap to closest point on geometry. The old behaviour was to prefer to snap to nodes, even if a node was further from the input geometry than a segment. The new option allows you to snap geometries to the closest point, regardless of whether it's a node or segment.
1 parent 983fe24 commit 8acc286

11 files changed

+369
-102
lines changed

python/analysis/vector/qgsgeometrysnapper.sip

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,33 @@ class QgsGeometrySnapper : QObject
1515

1616
public:
1717

18+
//! Snapping modes
19+
enum SnapMode
20+
{
21+
PreferNodes, //!< Prefer to snap to nodes, even when a segment may be closer than a node
22+
PreferClosest, //!< Snap to closest point, regardless of it is a node or a segment
23+
};
24+
1825
/**
1926
* Constructor for QgsGeometrySnapper. A reference layer which contains geometries to snap to must be
20-
* set. The snap tolerance is specified in the layer units for the
21-
* reference layer, and it is assumed that all geometries snapped using this object will have the
27+
* set. It is assumed that all geometries snapped using this object will have the
2228
* same CRS as the reference layer (ie, no reprojection is performed).
2329
*/
24-
QgsGeometrySnapper( QgsVectorLayer* referenceLayer, double snapTolerance );
30+
QgsGeometrySnapper( QgsVectorLayer* referenceLayer );
2531

2632
/**
2733
* Snaps a geometry to the reference layer and returns the result. The geometry must be in the same
28-
* CRS as the reference layer.
34+
* CRS as the reference layer, and must have the same type as the reference layer geometry. The snap tolerance
35+
* is specified in the layer units for the reference layer.
2936
*/
30-
QgsGeometry snapGeometry( const QgsGeometry& geometry ) const;
37+
QgsGeometry snapGeometry( const QgsGeometry& geometry, double snapTolerance, SnapMode mode = PreferNodes ) const;
3138

3239
/**
3340
* Snaps a set of features to the reference layer and returns the result. This operation is
3441
* multithreaded for performance. The featureSnapped() signal will be emitted each time a feature
35-
* is processed. This method is not safe to call from multiple threads concurrently.
42+
* is processed. The snap tolerance is specified in the layer units for the reference layer.
3643
*/
37-
QgsFeatureList snapFeatures( const QgsFeatureList& features );
44+
QgsFeatureList snapFeatures( const QgsFeatureList& features, double snapTolerance, SnapMode mode = PreferNodes );
3845

3946
signals:
4047

python/plugins/processing/algs/qgis/SnapGeometries.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
from qgis.core import QgsFeature
3131

3232
from processing.core.GeoAlgorithm import GeoAlgorithm
33-
from processing.core.parameters import ParameterVector, ParameterNumber
33+
from processing.core.parameters import ParameterVector, ParameterNumber, ParameterSelection
3434
from processing.core.outputs import OutputVector
3535
from processing.tools import dataobjects, vector
3636

@@ -41,6 +41,7 @@ class SnapGeometriesToLayer(GeoAlgorithm):
4141
REFERENCE_LAYER = 'REFERENCE_LAYER'
4242
TOLERANCE = 'TOLERANCE'
4343
OUTPUT = 'OUTPUT'
44+
BEHAVIOUR = 'BEHAVIOUR'
4445

4546
def defineCharacteristics(self):
4647
self.name, self.i18n_name = self.trAlgorithm('Snap geometries to layer')
@@ -49,12 +50,20 @@ def defineCharacteristics(self):
4950
self.addParameter(ParameterVector(self.INPUT, self.tr('Input layer')))
5051
self.addParameter(ParameterVector(self.REFERENCE_LAYER, self.tr('Reference layer')))
5152
self.addParameter(ParameterNumber(self.TOLERANCE, self.tr('Tolerance (layer units)'), 0.00000001, 9999999999, default=10.0))
53+
54+
self.modes = [self.tr('Prefer aligning nodes'),
55+
self.tr('Prefer closest point')]
56+
self.addParameter(ParameterSelection(
57+
self.BEHAVIOUR,
58+
self.tr('Behaviour'),
59+
self.modes, default=0))
5260
self.addOutput(OutputVector(self.OUTPUT, self.tr('Snapped geometries')))
5361

5462
def processAlgorithm(self, progress):
5563
layer = dataobjects.getObjectFromUri(self.getParameterValue(self.INPUT))
5664
reference_layer = dataobjects.getObjectFromUri(self.getParameterValue(self.REFERENCE_LAYER))
5765
tolerance = self.getParameterValue(self.TOLERANCE)
66+
mode = self.getParameterValue(self.BEHAVIOUR)
5867

5968
writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(
6069
layer.fields(), layer.wkbType(), layer.crs())
@@ -65,9 +74,9 @@ def processAlgorithm(self, progress):
6574
self.progress = progress
6675
self.total = 100.0 / len(features)
6776

68-
snapper = QgsGeometrySnapper(reference_layer, tolerance)
77+
snapper = QgsGeometrySnapper(reference_layer)
6978
snapper.featureSnapped.connect(self.featureSnapped)
70-
snapped_features = snapper.snapFeatures(features)
79+
snapped_features = snapper.snapFeatures(features, tolerance, mode)
7180
for f in snapped_features:
7281
writer.addFeature(QgsFeature(f))
7382

python/plugins/processing/tests/testdata/expected/snap_lines_to_lines.gml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@
77
<gml:boundedBy>
88
<gml:Box>
99
<gml:coord><gml:X>-1</gml:X><gml:Y>-5</gml:Y></gml:coord>
10-
<gml:coord><gml:X>11.34678899082569</gml:X><gml:Y>5.288990825688074</gml:Y></gml:coord>
10+
<gml:coord><gml:X>11</gml:X><gml:Y>5</gml:Y></gml:coord>
1111
</gml:Box>
1212
</gml:boundedBy>
13-
13+
1414
<gml:featureMember>
1515
<ogr:snap_lines_to_lines fid="lines.0">
16-
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>6,2 9,2 9,3 11,5 11.346788990825686,5.288990825688074</gml:coordinates></gml:LineString></ogr:geometryProperty>
16+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>6,2 9,2 9,3 11,5</gml:coordinates></gml:LineString></ogr:geometryProperty>
1717
</ogr:snap_lines_to_lines>
1818
</gml:featureMember>
1919
<gml:featureMember>
@@ -23,7 +23,7 @@
2323
</gml:featureMember>
2424
<gml:featureMember>
2525
<ogr:snap_lines_to_lines fid="lines.2">
26-
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>2,0 2,2 2,2 3,2 3,2</gml:coordinates></gml:LineString></ogr:geometryProperty>
26+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>2,0 2,2 2,2 3,2</gml:coordinates></gml:LineString></ogr:geometryProperty>
2727
</ogr:snap_lines_to_lines>
2828
</gml:featureMember>
2929
<gml:featureMember>
@@ -38,7 +38,7 @@
3838
</gml:featureMember>
3939
<gml:featureMember>
4040
<ogr:snap_lines_to_lines fid="lines.5">
41-
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>6,-3 10,1 10.208073394495411,0.849724770642202</gml:coordinates></gml:LineString></ogr:geometryProperty>
41+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>6,-3 10,1</gml:coordinates></gml:LineString></ogr:geometryProperty>
4242
</ogr:snap_lines_to_lines>
4343
</gml:featureMember>
4444
<gml:featureMember>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<GMLFeatureClassList>
2+
<GMLFeatureClass>
3+
<Name>snap_point_to_lines_prefer_closest</Name>
4+
<ElementPath>snap_point_to_lines_prefer_closest</ElementPath>
5+
<!--POINT-->
6+
<GeometryType>1</GeometryType>
7+
<SRSName>EPSG:4326</SRSName>
8+
<DatasetSpecificInfo>
9+
<FeatureCount>9</FeatureCount>
10+
<ExtentXMin>0.20114</ExtentXMin>
11+
<ExtentXMax>7.97127</ExtentXMax>
12+
<ExtentYMin>-4.82759</ExtentYMin>
13+
<ExtentYMax>2.74139</ExtentYMax>
14+
</DatasetSpecificInfo>
15+
</GMLFeatureClass>
16+
</GMLFeatureClassList>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<ogr:FeatureCollection
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation=""
5+
xmlns:ogr="http://ogr.maptools.org/"
6+
xmlns:gml="http://www.opengis.net/gml">
7+
<gml:boundedBy>
8+
<gml:Box>
9+
<gml:coord><gml:X>0.201140250855188</gml:X><gml:Y>-4.827594070695552</gml:Y></gml:coord>
10+
<gml:coord><gml:X>7.971265678449257</gml:X><gml:Y>2.74139110604333</gml:Y></gml:coord>
11+
</gml:Box>
12+
</gml:boundedBy>
13+
14+
<gml:featureMember>
15+
<ogr:snap_point_to_lines_prefer_closest fid="points.0">
16+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>1,1</gml:coordinates></gml:Point></ogr:geometryProperty>
17+
</ogr:snap_point_to_lines_prefer_closest>
18+
</gml:featureMember>
19+
<gml:featureMember>
20+
<ogr:snap_point_to_lines_prefer_closest fid="points.1">
21+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>3.0,2.74139110604333</gml:coordinates></gml:Point></ogr:geometryProperty>
22+
</ogr:snap_point_to_lines_prefer_closest>
23+
</gml:featureMember>
24+
<gml:featureMember>
25+
<ogr:snap_point_to_lines_prefer_closest fid="points.2">
26+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>2,2</gml:coordinates></gml:Point></ogr:geometryProperty>
27+
</ogr:snap_point_to_lines_prefer_closest>
28+
</gml:featureMember>
29+
<gml:featureMember>
30+
<ogr:snap_point_to_lines_prefer_closest fid="points.3">
31+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>5,1</gml:coordinates></gml:Point></ogr:geometryProperty>
32+
</ogr:snap_point_to_lines_prefer_closest>
33+
</gml:featureMember>
34+
<gml:featureMember>
35+
<ogr:snap_point_to_lines_prefer_closest fid="points.4">
36+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>3.683922462941847,1.0</gml:coordinates></gml:Point></ogr:geometryProperty>
37+
</ogr:snap_point_to_lines_prefer_closest>
38+
</gml:featureMember>
39+
<gml:featureMember>
40+
<ogr:snap_point_to_lines_prefer_closest fid="points.5">
41+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>0.201140250855188,-4.827594070695552</gml:coordinates></gml:Point></ogr:geometryProperty>
42+
</ogr:snap_point_to_lines_prefer_closest>
43+
</gml:featureMember>
44+
<gml:featureMember>
45+
<ogr:snap_point_to_lines_prefer_closest fid="points.6">
46+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>7.971265678449257,0.609122006841505</gml:coordinates></gml:Point></ogr:geometryProperty>
47+
</ogr:snap_point_to_lines_prefer_closest>
48+
</gml:featureMember>
49+
<gml:featureMember>
50+
<ogr:snap_point_to_lines_prefer_closest fid="points.7">
51+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>7.456898517673889,-1.543101482326111</gml:coordinates></gml:Point></ogr:geometryProperty>
52+
</ogr:snap_point_to_lines_prefer_closest>
53+
</gml:featureMember>
54+
<gml:featureMember>
55+
<ogr:snap_point_to_lines_prefer_closest fid="points.8">
56+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>0.220296465222349,-1.0</gml:coordinates></gml:Point></ogr:geometryProperty>
57+
</ogr:snap_point_to_lines_prefer_closest>
58+
</gml:featureMember>
59+
</ogr:FeatureCollection>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<GMLFeatureClassList>
2+
<GMLFeatureClass>
3+
<Name>snap_point_to_lines_prefer_nodes</Name>
4+
<ElementPath>snap_point_to_lines_prefer_nodes</ElementPath>
5+
<!--POINT-->
6+
<GeometryType>1</GeometryType>
7+
<SRSName>EPSG:4326</SRSName>
8+
<DatasetSpecificInfo>
9+
<FeatureCount>9</FeatureCount>
10+
<ExtentXMin>0.20114</ExtentXMin>
11+
<ExtentXMax>7.97127</ExtentXMax>
12+
<ExtentYMin>-4.82759</ExtentYMin>
13+
<ExtentYMax>3.00000</ExtentYMax>
14+
</DatasetSpecificInfo>
15+
</GMLFeatureClass>
16+
</GMLFeatureClassList>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<ogr:FeatureCollection
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation=""
5+
xmlns:ogr="http://ogr.maptools.org/"
6+
xmlns:gml="http://www.opengis.net/gml">
7+
<gml:boundedBy>
8+
<gml:Box>
9+
<gml:coord><gml:X>0.201140250855188</gml:X><gml:Y>-4.827594070695552</gml:Y></gml:coord>
10+
<gml:coord><gml:X>7.971265678449257</gml:X><gml:Y>3</gml:Y></gml:coord>
11+
</gml:Box>
12+
</gml:boundedBy>
13+
14+
<gml:featureMember>
15+
<ogr:snap_point_to_lines_prefer_nodes fid="points.0">
16+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>1,1</gml:coordinates></gml:Point></ogr:geometryProperty>
17+
</ogr:snap_point_to_lines_prefer_nodes>
18+
</gml:featureMember>
19+
<gml:featureMember>
20+
<ogr:snap_point_to_lines_prefer_nodes fid="points.1">
21+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>3,3</gml:coordinates></gml:Point></ogr:geometryProperty>
22+
</ogr:snap_point_to_lines_prefer_nodes>
23+
</gml:featureMember>
24+
<gml:featureMember>
25+
<ogr:snap_point_to_lines_prefer_nodes fid="points.2">
26+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>2,2</gml:coordinates></gml:Point></ogr:geometryProperty>
27+
</ogr:snap_point_to_lines_prefer_nodes>
28+
</gml:featureMember>
29+
<gml:featureMember>
30+
<ogr:snap_point_to_lines_prefer_nodes fid="points.3">
31+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>5,1</gml:coordinates></gml:Point></ogr:geometryProperty>
32+
</ogr:snap_point_to_lines_prefer_nodes>
33+
</gml:featureMember>
34+
<gml:featureMember>
35+
<ogr:snap_point_to_lines_prefer_nodes fid="points.4">
36+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>3,1</gml:coordinates></gml:Point></ogr:geometryProperty>
37+
</ogr:snap_point_to_lines_prefer_nodes>
38+
</gml:featureMember>
39+
<gml:featureMember>
40+
<ogr:snap_point_to_lines_prefer_nodes fid="points.5">
41+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>0.201140250855188,-4.827594070695552</gml:coordinates></gml:Point></ogr:geometryProperty>
42+
</ogr:snap_point_to_lines_prefer_nodes>
43+
</gml:featureMember>
44+
<gml:featureMember>
45+
<ogr:snap_point_to_lines_prefer_nodes fid="points.6">
46+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>7.971265678449257,0.609122006841505</gml:coordinates></gml:Point></ogr:geometryProperty>
47+
</ogr:snap_point_to_lines_prefer_nodes>
48+
</gml:featureMember>
49+
<gml:featureMember>
50+
<ogr:snap_point_to_lines_prefer_nodes fid="points.7">
51+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>7.456898517673889,-1.543101482326111</gml:coordinates></gml:Point></ogr:geometryProperty>
52+
</ogr:snap_point_to_lines_prefer_nodes>
53+
</gml:featureMember>
54+
<gml:featureMember>
55+
<ogr:snap_point_to_lines_prefer_nodes fid="points.8">
56+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>1,-1</gml:coordinates></gml:Point></ogr:geometryProperty>
57+
</ogr:snap_point_to_lines_prefer_nodes>
58+
</gml:featureMember>
59+
</ogr:FeatureCollection>

python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1431,4 +1431,36 @@ tests:
14311431
results:
14321432
OUTPUT:
14331433
name: expected/snap_points_to_points.gml
1434-
type: vector
1434+
type: vector
1435+
1436+
- algorithm: qgis:snapgeometriestolayer
1437+
name: Snap points to lines (prefer nodes)
1438+
params:
1439+
BEHAVIOUR: '0'
1440+
INPUT:
1441+
name: snap_points.gml
1442+
type: vector
1443+
REFERENCE_LAYER:
1444+
name: lines.gml
1445+
type: vector
1446+
TOLERANCE: 1.0
1447+
results:
1448+
OUTPUT:
1449+
name: expected/snap_point_to_lines_prefer_nodes.gml
1450+
type: vector
1451+
1452+
- algorithm: qgis:snapgeometriestolayer
1453+
name: Snap points to lines (prefer closest)
1454+
params:
1455+
BEHAVIOUR: '1'
1456+
INPUT:
1457+
name: snap_points.gml
1458+
type: vector
1459+
REFERENCE_LAYER:
1460+
name: lines.gml
1461+
type: vector
1462+
TOLERANCE: 1.0
1463+
results:
1464+
OUTPUT:
1465+
name: expected/snap_point_to_lines_prefer_closest.gml
1466+
type: vector

0 commit comments

Comments
 (0)