Skip to content

Commit 9bcd21f

Browse files
authored
Merge pull request #7194 from wonder-sk/snap-geometries-alg
[FEATURE] Snap geometries algorithm
2 parents 4ac834d + 2b55858 commit 9bcd21f

File tree

11 files changed

+547
-1
lines changed

11 files changed

+547
-1
lines changed

python/analysis/analysis_auto.sip

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
%Include auto_generated/raster/qgsrastercalcnode.sip
1414
%Include auto_generated/raster/qgstotalcurvaturefilter.sip
1515
%Include auto_generated/vector/qgsgeometrysnapper.sip
16+
%Include auto_generated/vector/qgsgeometrysnappersinglesource.sip
1617
%Include auto_generated/vector/qgszonalstatistics.sip
1718
%Include auto_generated/interpolation/qgsinterpolator.sip
1819
%Include auto_generated/interpolation/qgsgridfilewriter.sip
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/************************************************************************
2+
* This file has been generated automatically from *
3+
* *
4+
* src/analysis/vector/qgsgeometrysnappersinglesource.h *
5+
* *
6+
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
7+
************************************************************************/
8+
9+
10+
11+
12+
class QgsGeometrySnapperSingleSource
13+
{
14+
%Docstring
15+
16+
Makes sure that any two vertices of the vector layer are at least at distance given by the threshold value.
17+
The algorithm moves nearby vertices to one location and adds vertices to segments that are passing around other
18+
vertices within the threshold. It does not remove any vertices. Also, it does not modify geometries unless
19+
needed (it does not snap coordinates to a grid).
20+
21+
This algorithm comes handy when doing vector overlay operations such as intersection, union or difference
22+
to prevent possible topological errors caused by numerical errors if coordinates are very close to each other.
23+
24+
After running the algorithm some previously valid geometries may become invalid and therefore it may be useful
25+
to run Fix geometries algorithm afterwards.
26+
27+
.. note::
28+
29+
Originally ported from GRASS implementation of Vect_snap_lines_list()
30+
31+
.. versionadded:: 3.4
32+
%End
33+
34+
%TypeHeaderCode
35+
#include "qgsgeometrysnappersinglesource.h"
36+
%End
37+
public:
38+
39+
static int run( const QgsFeatureSource &source, QgsFeatureSink &sink, double thresh, QgsFeedback *feedback );
40+
%Docstring
41+
Run the algorithm on given source and output results to the sink, using threshold value in the source's map units.
42+
Returns number of modified geometries.
43+
%End
44+
};
45+
46+
/************************************************************************
47+
* This file has been generated automatically from *
48+
* *
49+
* src/analysis/vector/qgsgeometrysnappersinglesource.h *
50+
* *
51+
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
52+
************************************************************************/

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
__revision__ = '$Format:%H$'
2727

2828
from qgis.analysis import (QgsGeometrySnapper,
29+
QgsGeometrySnapperSingleSource,
2930
QgsInternalGeometrySnapper)
3031
from qgis.core import (QgsFeatureSink,
3132
QgsProcessing,
@@ -71,7 +72,8 @@ def initAlgorithm(self, config=None):
7172
self.tr('Prefer closest point, don\'t insert new vertices'),
7273
self.tr('Move end points only, prefer aligning nodes'),
7374
self.tr('Move end points only, prefer closest point'),
74-
self.tr('Snap end points to end points only')]
75+
self.tr('Snap end points to end points only'),
76+
self.tr('Snap to anchor nodes (single layer only)')]
7577
self.addParameter(QgsProcessingParameterEnum(
7678
self.BEHAVIOR,
7779
self.tr('Behavior'),
@@ -106,6 +108,9 @@ def processAlgorithm(self, parameters, context, feedback):
106108
total = 100.0 / source.featureCount() if source.featureCount() else 0
107109

108110
if parameters[self.INPUT] != parameters[self.REFERENCE_LAYER]:
111+
if mode == 7:
112+
raise QgsProcessingException(self.tr('This mode applies when the input and reference layer are the same.'))
113+
109114
snapper = QgsGeometrySnapper(reference_source)
110115
processed = 0
111116
for f in features:
@@ -119,6 +124,10 @@ def processAlgorithm(self, parameters, context, feedback):
119124
sink.addFeature(f)
120125
processed += 1
121126
feedback.setProgress(processed * total)
127+
elif mode == 7:
128+
# input layer == ref layer
129+
modified_count = QgsGeometrySnapperSingleSource.run(source, sink, tolerance, feedback)
130+
feedback.pushInfo(self.tr('Snapped {} geometries.').format(modified_count))
122131
else:
123132
# snapping internally
124133
snapper = QgsInternalGeometrySnapper(tolerance, mode)
Binary file not shown.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"type": "FeatureCollection",
3+
"name": "snap_geometries",
4+
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:EPSG::3857" } },
5+
"features": [
6+
{ "type": "Feature", "properties": { }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 20.123, 10.123 ], [ 20.123, 20.456 ], [ 20.2, 20.456 ], [ 20.2, 20.5 ], [ 30.0, 20.5 ], [ 30.0, 10.123 ], [ 29.8, 10.123 ], [ 21.0, 10.123 ], [ 20.123, 10.123 ] ] ] } },
7+
{ "type": "Feature", "properties": { }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 31.0, 10.0 ], [ 20.0, 10.0 ], [ 20.0, 5.0 ], [ 31.0, 10.0 ] ] ] } }
8+
]
9+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<ogr:FeatureCollection
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://ogr.maptools.org/ snap_geometries.xsd"
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>20</gml:X><gml:Y>5</gml:Y></gml:coord>
10+
<gml:coord><gml:X>31</gml:X><gml:Y>20.5</gml:Y></gml:coord>
11+
</gml:Box>
12+
</gml:boundedBy>
13+
14+
<gml:featureMember>
15+
<ogr:snap_geometries fid="snap_geometries.0">
16+
<ogr:geometryProperty><gml:Polygon srsName="EPSG:3857"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>20.123,10.123 20.123,20.456 20.123,20.456 20.123,20.456 30.0,20.5 30.0,10.123 30.0,10.123 21.0,10.123 20.123,10.123</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
17+
</ogr:snap_geometries>
18+
</gml:featureMember>
19+
<gml:featureMember>
20+
<ogr:snap_geometries fid="snap_geometries.1">
21+
<ogr:geometryProperty><gml:Polygon srsName="EPSG:3857"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>31,10 30.0,10.123 21.0,10.123 20.123,10.123 20,5 31,10</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
22+
</ogr:snap_geometries>
23+
</gml:featureMember>
24+
</ogr:FeatureCollection>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<xs:schema targetNamespace="http://ogr.maptools.org/" xmlns:ogr="http://ogr.maptools.org/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:gml="http://www.opengis.net/gml" elementFormDefault="qualified" version="1.0">
3+
<xs:import namespace="http://www.opengis.net/gml" schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd"/>
4+
<xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:_FeatureCollection"/>
5+
<xs:complexType name="FeatureCollectionType">
6+
<xs:complexContent>
7+
<xs:extension base="gml:AbstractFeatureCollectionType">
8+
<xs:attribute name="lockId" type="xs:string" use="optional"/>
9+
<xs:attribute name="scope" type="xs:string" use="optional"/>
10+
</xs:extension>
11+
</xs:complexContent>
12+
</xs:complexType>
13+
<xs:element name="snap_geometries" type="ogr:snap_geometries_Type" substitutionGroup="gml:_Feature"/>
14+
<xs:complexType name="snap_geometries_Type">
15+
<xs:complexContent>
16+
<xs:extension base="gml:AbstractFeatureType">
17+
<xs:sequence>
18+
<xs:element name="geometryProperty" type="gml:PolygonPropertyType" nillable="true" minOccurs="0" maxOccurs="1"/>
19+
</xs:sequence>
20+
</xs:extension>
21+
</xs:complexContent>
22+
</xs:complexType>
23+
</xs:schema>

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2045,6 +2045,22 @@ tests:
20452045
name: expected/snap_internal.gml
20462046
type: vector
20472047

2048+
- algorithm: qgis:snapgeometries
2049+
name: Test Snap Geometries (to each other)
2050+
params:
2051+
BEHAVIOR: '7'
2052+
INPUT:
2053+
name: custom/snap_geometries.geojson
2054+
type: vector
2055+
REFERENCE_LAYER:
2056+
name: custom/snap_geometries.geojson
2057+
type: vector
2058+
TOLERANCE: 0.5
2059+
results:
2060+
OUTPUT:
2061+
name: expected/snap_geometries.gml
2062+
type: vector
2063+
20482064
- algorithm: qgis:poleofinaccessibility
20492065
name: Pole of inaccessibility (polygons)
20502066
params:

src/analysis/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ SET(QGIS_ANALYSIS_SRCS
123123
raster/qgsrastermatrix.cpp
124124
vector/mersenne-twister.cpp
125125
vector/qgsgeometrysnapper.cpp
126+
vector/qgsgeometrysnappersinglesource.cpp
126127
vector/qgszonalstatistics.cpp
127128

128129
network/qgsgraph.cpp
@@ -231,6 +232,7 @@ SET(QGIS_ANALYSIS_HDRS
231232

232233
vector/mersenne-twister.h
233234
vector/qgsgeometrysnapper.h
235+
vector/qgsgeometrysnappersinglesource.h
234236
vector/qgszonalstatistics.h
235237
vector/geometry_checker/qgsgeometrycheckerutils.h
236238
vector/geometry_checker/qgsfeaturepool.h

0 commit comments

Comments
 (0)