Skip to content

Commit

Permalink
[FEATURE] Snap to layer algorithm accepts a mode parameter
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
nyalldawson committed Nov 7, 2016
1 parent 983fe24 commit 8acc286
Show file tree
Hide file tree
Showing 11 changed files with 369 additions and 102 deletions.
21 changes: 14 additions & 7 deletions python/analysis/vector/qgsgeometrysnapper.sip
Expand Up @@ -15,26 +15,33 @@ class QgsGeometrySnapper : QObject

public:

//! Snapping modes
enum SnapMode
{
PreferNodes, //!< Prefer to snap to nodes, even when a segment may be closer than a node
PreferClosest, //!< Snap to closest point, regardless of it is a node or a segment
};

/**
* Constructor for QgsGeometrySnapper. A reference layer which contains geometries to snap to must be
* set. The snap tolerance is specified in the layer units for the
* reference layer, and it is assumed that all geometries snapped using this object will have the
* set. It is assumed that all geometries snapped using this object will have the
* same CRS as the reference layer (ie, no reprojection is performed).
*/
QgsGeometrySnapper( QgsVectorLayer* referenceLayer, double snapTolerance );
QgsGeometrySnapper( QgsVectorLayer* referenceLayer );

/**
* Snaps a geometry to the reference layer and returns the result. The geometry must be in the same
* CRS as the reference layer.
* CRS as the reference layer, and must have the same type as the reference layer geometry. The snap tolerance
* is specified in the layer units for the reference layer.
*/
QgsGeometry snapGeometry( const QgsGeometry& geometry ) const;
QgsGeometry snapGeometry( const QgsGeometry& geometry, double snapTolerance, SnapMode mode = PreferNodes ) const;

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

signals:

Expand Down
15 changes: 12 additions & 3 deletions python/plugins/processing/algs/qgis/SnapGeometries.py
Expand Up @@ -30,7 +30,7 @@
from qgis.core import QgsFeature

from processing.core.GeoAlgorithm import GeoAlgorithm
from processing.core.parameters import ParameterVector, ParameterNumber
from processing.core.parameters import ParameterVector, ParameterNumber, ParameterSelection
from processing.core.outputs import OutputVector
from processing.tools import dataobjects, vector

Expand All @@ -41,6 +41,7 @@ class SnapGeometriesToLayer(GeoAlgorithm):
REFERENCE_LAYER = 'REFERENCE_LAYER'
TOLERANCE = 'TOLERANCE'
OUTPUT = 'OUTPUT'
BEHAVIOUR = 'BEHAVIOUR'

def defineCharacteristics(self):
self.name, self.i18n_name = self.trAlgorithm('Snap geometries to layer')
Expand All @@ -49,12 +50,20 @@ def defineCharacteristics(self):
self.addParameter(ParameterVector(self.INPUT, self.tr('Input layer')))
self.addParameter(ParameterVector(self.REFERENCE_LAYER, self.tr('Reference layer')))
self.addParameter(ParameterNumber(self.TOLERANCE, self.tr('Tolerance (layer units)'), 0.00000001, 9999999999, default=10.0))

self.modes = [self.tr('Prefer aligning nodes'),
self.tr('Prefer closest point')]
self.addParameter(ParameterSelection(
self.BEHAVIOUR,
self.tr('Behaviour'),
self.modes, default=0))
self.addOutput(OutputVector(self.OUTPUT, self.tr('Snapped geometries')))

def processAlgorithm(self, progress):
layer = dataobjects.getObjectFromUri(self.getParameterValue(self.INPUT))
reference_layer = dataobjects.getObjectFromUri(self.getParameterValue(self.REFERENCE_LAYER))
tolerance = self.getParameterValue(self.TOLERANCE)
mode = self.getParameterValue(self.BEHAVIOUR)

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

snapper = QgsGeometrySnapper(reference_layer, tolerance)
snapper = QgsGeometrySnapper(reference_layer)
snapper.featureSnapped.connect(self.featureSnapped)
snapped_features = snapper.snapFeatures(features)
snapped_features = snapper.snapFeatures(features, tolerance, mode)
for f in snapped_features:
writer.addFeature(QgsFeature(f))

Expand Down
Expand Up @@ -7,13 +7,13 @@
<gml:boundedBy>
<gml:Box>
<gml:coord><gml:X>-1</gml:X><gml:Y>-5</gml:Y></gml:coord>
<gml:coord><gml:X>11.34678899082569</gml:X><gml:Y>5.288990825688074</gml:Y></gml:coord>
<gml:coord><gml:X>11</gml:X><gml:Y>5</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>

<gml:featureMember>
<ogr:snap_lines_to_lines fid="lines.0">
<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>
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>6,2 9,2 9,3 11,5</gml:coordinates></gml:LineString></ogr:geometryProperty>
</ogr:snap_lines_to_lines>
</gml:featureMember>
<gml:featureMember>
Expand All @@ -23,7 +23,7 @@
</gml:featureMember>
<gml:featureMember>
<ogr:snap_lines_to_lines fid="lines.2">
<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>
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>2,0 2,2 2,2 3,2</gml:coordinates></gml:LineString></ogr:geometryProperty>
</ogr:snap_lines_to_lines>
</gml:featureMember>
<gml:featureMember>
Expand All @@ -38,7 +38,7 @@
</gml:featureMember>
<gml:featureMember>
<ogr:snap_lines_to_lines fid="lines.5">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>6,-3 10,1 10.208073394495411,0.849724770642202</gml:coordinates></gml:LineString></ogr:geometryProperty>
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>6,-3 10,1</gml:coordinates></gml:LineString></ogr:geometryProperty>
</ogr:snap_lines_to_lines>
</gml:featureMember>
<gml:featureMember>
Expand Down
@@ -0,0 +1,16 @@
<GMLFeatureClassList>
<GMLFeatureClass>
<Name>snap_point_to_lines_prefer_closest</Name>
<ElementPath>snap_point_to_lines_prefer_closest</ElementPath>
<!--POINT-->
<GeometryType>1</GeometryType>
<SRSName>EPSG:4326</SRSName>
<DatasetSpecificInfo>
<FeatureCount>9</FeatureCount>
<ExtentXMin>0.20114</ExtentXMin>
<ExtentXMax>7.97127</ExtentXMax>
<ExtentYMin>-4.82759</ExtentYMin>
<ExtentYMax>2.74139</ExtentYMax>
</DatasetSpecificInfo>
</GMLFeatureClass>
</GMLFeatureClassList>
@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=""
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy>
<gml:Box>
<gml:coord><gml:X>0.201140250855188</gml:X><gml:Y>-4.827594070695552</gml:Y></gml:coord>
<gml:coord><gml:X>7.971265678449257</gml:X><gml:Y>2.74139110604333</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>

<gml:featureMember>
<ogr:snap_point_to_lines_prefer_closest fid="points.0">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>1,1</gml:coordinates></gml:Point></ogr:geometryProperty>
</ogr:snap_point_to_lines_prefer_closest>
</gml:featureMember>
<gml:featureMember>
<ogr:snap_point_to_lines_prefer_closest fid="points.1">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>3.0,2.74139110604333</gml:coordinates></gml:Point></ogr:geometryProperty>
</ogr:snap_point_to_lines_prefer_closest>
</gml:featureMember>
<gml:featureMember>
<ogr:snap_point_to_lines_prefer_closest fid="points.2">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>2,2</gml:coordinates></gml:Point></ogr:geometryProperty>
</ogr:snap_point_to_lines_prefer_closest>
</gml:featureMember>
<gml:featureMember>
<ogr:snap_point_to_lines_prefer_closest fid="points.3">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>5,1</gml:coordinates></gml:Point></ogr:geometryProperty>
</ogr:snap_point_to_lines_prefer_closest>
</gml:featureMember>
<gml:featureMember>
<ogr:snap_point_to_lines_prefer_closest fid="points.4">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>3.683922462941847,1.0</gml:coordinates></gml:Point></ogr:geometryProperty>
</ogr:snap_point_to_lines_prefer_closest>
</gml:featureMember>
<gml:featureMember>
<ogr:snap_point_to_lines_prefer_closest fid="points.5">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>0.201140250855188,-4.827594070695552</gml:coordinates></gml:Point></ogr:geometryProperty>
</ogr:snap_point_to_lines_prefer_closest>
</gml:featureMember>
<gml:featureMember>
<ogr:snap_point_to_lines_prefer_closest fid="points.6">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>7.971265678449257,0.609122006841505</gml:coordinates></gml:Point></ogr:geometryProperty>
</ogr:snap_point_to_lines_prefer_closest>
</gml:featureMember>
<gml:featureMember>
<ogr:snap_point_to_lines_prefer_closest fid="points.7">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>7.456898517673889,-1.543101482326111</gml:coordinates></gml:Point></ogr:geometryProperty>
</ogr:snap_point_to_lines_prefer_closest>
</gml:featureMember>
<gml:featureMember>
<ogr:snap_point_to_lines_prefer_closest fid="points.8">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>0.220296465222349,-1.0</gml:coordinates></gml:Point></ogr:geometryProperty>
</ogr:snap_point_to_lines_prefer_closest>
</gml:featureMember>
</ogr:FeatureCollection>
@@ -0,0 +1,16 @@
<GMLFeatureClassList>
<GMLFeatureClass>
<Name>snap_point_to_lines_prefer_nodes</Name>
<ElementPath>snap_point_to_lines_prefer_nodes</ElementPath>
<!--POINT-->
<GeometryType>1</GeometryType>
<SRSName>EPSG:4326</SRSName>
<DatasetSpecificInfo>
<FeatureCount>9</FeatureCount>
<ExtentXMin>0.20114</ExtentXMin>
<ExtentXMax>7.97127</ExtentXMax>
<ExtentYMin>-4.82759</ExtentYMin>
<ExtentYMax>3.00000</ExtentYMax>
</DatasetSpecificInfo>
</GMLFeatureClass>
</GMLFeatureClassList>
@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=""
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy>
<gml:Box>
<gml:coord><gml:X>0.201140250855188</gml:X><gml:Y>-4.827594070695552</gml:Y></gml:coord>
<gml:coord><gml:X>7.971265678449257</gml:X><gml:Y>3</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>

<gml:featureMember>
<ogr:snap_point_to_lines_prefer_nodes fid="points.0">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>1,1</gml:coordinates></gml:Point></ogr:geometryProperty>
</ogr:snap_point_to_lines_prefer_nodes>
</gml:featureMember>
<gml:featureMember>
<ogr:snap_point_to_lines_prefer_nodes fid="points.1">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>3,3</gml:coordinates></gml:Point></ogr:geometryProperty>
</ogr:snap_point_to_lines_prefer_nodes>
</gml:featureMember>
<gml:featureMember>
<ogr:snap_point_to_lines_prefer_nodes fid="points.2">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>2,2</gml:coordinates></gml:Point></ogr:geometryProperty>
</ogr:snap_point_to_lines_prefer_nodes>
</gml:featureMember>
<gml:featureMember>
<ogr:snap_point_to_lines_prefer_nodes fid="points.3">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>5,1</gml:coordinates></gml:Point></ogr:geometryProperty>
</ogr:snap_point_to_lines_prefer_nodes>
</gml:featureMember>
<gml:featureMember>
<ogr:snap_point_to_lines_prefer_nodes fid="points.4">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>3,1</gml:coordinates></gml:Point></ogr:geometryProperty>
</ogr:snap_point_to_lines_prefer_nodes>
</gml:featureMember>
<gml:featureMember>
<ogr:snap_point_to_lines_prefer_nodes fid="points.5">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>0.201140250855188,-4.827594070695552</gml:coordinates></gml:Point></ogr:geometryProperty>
</ogr:snap_point_to_lines_prefer_nodes>
</gml:featureMember>
<gml:featureMember>
<ogr:snap_point_to_lines_prefer_nodes fid="points.6">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>7.971265678449257,0.609122006841505</gml:coordinates></gml:Point></ogr:geometryProperty>
</ogr:snap_point_to_lines_prefer_nodes>
</gml:featureMember>
<gml:featureMember>
<ogr:snap_point_to_lines_prefer_nodes fid="points.7">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>7.456898517673889,-1.543101482326111</gml:coordinates></gml:Point></ogr:geometryProperty>
</ogr:snap_point_to_lines_prefer_nodes>
</gml:featureMember>
<gml:featureMember>
<ogr:snap_point_to_lines_prefer_nodes fid="points.8">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>1,-1</gml:coordinates></gml:Point></ogr:geometryProperty>
</ogr:snap_point_to_lines_prefer_nodes>
</gml:featureMember>
</ogr:FeatureCollection>
Expand Up @@ -1431,4 +1431,36 @@ tests:
results:
OUTPUT:
name: expected/snap_points_to_points.gml
type: vector
type: vector

- algorithm: qgis:snapgeometriestolayer
name: Snap points to lines (prefer nodes)
params:
BEHAVIOUR: '0'
INPUT:
name: snap_points.gml
type: vector
REFERENCE_LAYER:
name: lines.gml
type: vector
TOLERANCE: 1.0
results:
OUTPUT:
name: expected/snap_point_to_lines_prefer_nodes.gml
type: vector

- algorithm: qgis:snapgeometriestolayer
name: Snap points to lines (prefer closest)
params:
BEHAVIOUR: '1'
INPUT:
name: snap_points.gml
type: vector
REFERENCE_LAYER:
name: lines.gml
type: vector
TOLERANCE: 1.0
results:
OUTPUT:
name: expected/snap_point_to_lines_prefer_closest.gml
type: vector

0 comments on commit 8acc286

Please sign in to comment.