Skip to content

Commit c3a978b

Browse files
committedNov 7, 2016
[FEATURE][processing] Snap geometries to layer algorithm
Port the Geometry Snapper plugin across to the analysis lib, and expose to python bindings Add a new algorithm which performs the snapping to layers
1 parent b4bca5b commit c3a978b

17 files changed

+1271
-1
lines changed
 

‎python/analysis/analysis.sip

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
%Import core/core.sip
1010

1111
%Include vector/qgsgeometryanalyzer.sip
12+
%Include vector/qgsgeometrysnapper.sip
1213
%Include vector/qgsoverlayanalyzer.sip
1314
%Include vector/qgspointsample.sip
1415
%Include vector/qgstransectsample.sip
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/**
2+
* \class QgsGeometrySnapper
3+
* \ingroup analysis
4+
* QgsGeometrySnapper allows a geometry to be snapped to the geometries within a
5+
* different refence layer. Vertices in the geometries will be modified to
6+
* match the reference layer features within a specified snap tolerance.
7+
* \note added in QGIS 3.0
8+
*/
9+
10+
class QgsGeometrySnapper : QObject
11+
{
12+
%TypeHeaderCode
13+
#include <qgsgeometrysnapper.h>
14+
%End
15+
16+
public:
17+
18+
/**
19+
* 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
22+
* same CRS as the reference layer (ie, no reprojection is performed).
23+
*/
24+
QgsGeometrySnapper( QgsVectorLayer* referenceLayer, double snapTolerance );
25+
26+
/**
27+
* Snaps a geometry to the reference layer and returns the result. The geometry must be in the same
28+
* CRS as the reference layer.
29+
*/
30+
QgsGeometry snapGeometry( const QgsGeometry& geometry ) const;
31+
32+
/**
33+
* Snaps a set of features to the reference layer and returns the result. This operation is
34+
* 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.
36+
*/
37+
QgsFeatureList snapFeatures( const QgsFeatureList& features );
38+
39+
signals:
40+
41+
//! Emitted each time a feature has been processed when calling snapFeatures()
42+
void featureSnapped();
43+
};

‎python/plugins/processing/algs/help/qgis.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,9 @@ qgis:smoothgeometry: >
481481

482482
The maximum angle parameter can be used to prevent smoothing of nodes with large angles. Any node where the angle of the segments to either side is larger then this will not be smoothed. Eg setting the maximum angle to 90 degrees or lower would preserve right angles in the geometry.
483483

484+
qgis:snapgeometriestolayer: >
485+
This algorithm snaps the geometries in a layer to the geometries in another layer. A tolerance is specified in layer units to control how close vertices need to be to the reference layer geometries before they are snapped. Snapping occurs to both vertices and edges, but snapping to a vertex is preferred. Vertices will be inserted or removed as required to make the geometries match the reference geometries.
486+
484487
qgis:snappointstogrid: >
485488
This algorithm modifies the position of points in a vector layer, so they fall in the coordinates of a grid.
486489

‎python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@
175175
from .ExtendLines import ExtendLines
176176
from .ExtractSpecificNodes import ExtractSpecificNodes
177177
from .GeometryByExpression import GeometryByExpression
178+
from .SnapGeometries import SnapGeometriesToLayer
178179

179180
pluginPath = os.path.normpath(os.path.join(
180181
os.path.split(os.path.dirname(__file__))[0], os.pardir))
@@ -237,7 +238,7 @@ def __init__(self):
237238
IdwInterpolationZValue(), IdwInterpolationAttribute(),
238239
TinInterpolationZValue(), TinInterpolationAttribute(),
239240
RemoveNullGeometry(), ExtractByExpression(), ExtendLines(),
240-
ExtractSpecificNodes(), GeometryByExpression()
241+
ExtractSpecificNodes(), GeometryByExpression(), SnapGeometriesToLayer()
241242
]
242243

243244
if hasMatplotlib:
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
***************************************************************************
5+
SnapGeometries.py
6+
-----------------
7+
Date : October 2016
8+
Copyright : (C) 2016 by Nyall Dawson
9+
Email : nyall dot dawson at gmail dot com
10+
***************************************************************************
11+
* *
12+
* This program is free software; you can redistribute it and/or modify *
13+
* it under the terms of the GNU General Public License as published by *
14+
* the Free Software Foundation; either version 2 of the License, or *
15+
* (at your option) any later version. *
16+
* *
17+
***************************************************************************
18+
"""
19+
from builtins import str
20+
21+
__author__ = 'Nyall Dawson'
22+
__date__ = 'October 2016'
23+
__copyright__ = '(C) 2016, Nyall Dawson'
24+
25+
# This will get replaced with a git SHA1 when you do a git archive
26+
27+
__revision__ = '$Format:%H$'
28+
29+
from qgis.analysis import QgsGeometrySnapper
30+
from qgis.core import QgsFeature
31+
32+
from processing.core.GeoAlgorithm import GeoAlgorithm
33+
from processing.core.parameters import ParameterVector, ParameterNumber
34+
from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
35+
from processing.core.outputs import OutputVector
36+
from processing.tools import dataobjects, vector
37+
38+
39+
class SnapGeometriesToLayer(GeoAlgorithm):
40+
41+
INPUT = 'INPUT'
42+
REFERENCE_LAYER = 'REFERENCE_LAYER'
43+
TOLERANCE = 'TOLERANCE'
44+
OUTPUT = 'OUTPUT'
45+
46+
def defineCharacteristics(self):
47+
self.name, self.i18n_name = self.trAlgorithm('Snap geometries to layer')
48+
self.group, self.i18n_group = self.trAlgorithm('Vector geometry tools')
49+
50+
self.addParameter(ParameterVector(self.INPUT, self.tr('Input layer')))
51+
self.addParameter(ParameterVector(self.REFERENCE_LAYER, self.tr('Reference layer')))
52+
self.addParameter(ParameterNumber(self.TOLERANCE, self.tr('Tolerance (layer units)'), 0.00000001, 9999999999, default=10.0))
53+
self.addOutput(OutputVector(self.OUTPUT, self.tr('Snapped geometries')))
54+
55+
def processAlgorithm(self, progress):
56+
layer = dataobjects.getObjectFromUri(self.getParameterValue(self.INPUT))
57+
reference_layer = dataobjects.getObjectFromUri(self.getParameterValue(self.REFERENCE_LAYER))
58+
tolerance = self.getParameterValue(self.TOLERANCE)
59+
60+
if not layer.geometryType() == reference_layer.geometryType():
61+
raise GeoAlgorithmExecutionException(
62+
self.tr('Input layer and reference layer must have the same geometry type (eg both are line layers)'))
63+
64+
writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(
65+
layer.fields(), layer.wkbType(), layer.crs())
66+
67+
features = vector.features(layer)
68+
69+
self.processed = 0
70+
self.progress = progress
71+
self.total = 100.0 / len(features)
72+
73+
snapper = QgsGeometrySnapper(reference_layer, tolerance)
74+
snapper.featureSnapped.connect(self.featureSnapped)
75+
snapped_features = snapper.snapFeatures(features)
76+
for f in snapped_features:
77+
writer.addFeature(QgsFeature(f))
78+
79+
del writer
80+
81+
def featureSnapped(self):
82+
self.processed += 1
83+
self.progress.setPercentage(int(self.processed * self.total))
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<GMLFeatureClassList>
2+
<GMLFeatureClass>
3+
<Name>snap_lines_to_lines</Name>
4+
<ElementPath>snap_lines_to_lines</ElementPath>
5+
<SRSName>EPSG:4326</SRSName>
6+
<DatasetSpecificInfo>
7+
<FeatureCount>7</FeatureCount>
8+
<ExtentXMin>-1.00000</ExtentXMin>
9+
<ExtentXMax>11.34679</ExtentXMax>
10+
<ExtentYMin>-5.00000</ExtentYMin>
11+
<ExtentYMax>5.28899</ExtentYMax>
12+
</DatasetSpecificInfo>
13+
</GMLFeatureClass>
14+
</GMLFeatureClassList>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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>-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>
11+
</gml:Box>
12+
</gml:boundedBy>
13+
14+
<gml:featureMember>
15+
<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>
17+
</ogr:snap_lines_to_lines>
18+
</gml:featureMember>
19+
<gml:featureMember>
20+
<ogr:snap_lines_to_lines fid="lines.1">
21+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>-1,-1 1,-1</gml:coordinates></gml:LineString></ogr:geometryProperty>
22+
</ogr:snap_lines_to_lines>
23+
</gml:featureMember>
24+
<gml:featureMember>
25+
<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>
27+
</ogr:snap_lines_to_lines>
28+
</gml:featureMember>
29+
<gml:featureMember>
30+
<ogr:snap_lines_to_lines fid="lines.3">
31+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>3,-3 3,-5</gml:coordinates></gml:LineString></ogr:geometryProperty>
32+
</ogr:snap_lines_to_lines>
33+
</gml:featureMember>
34+
<gml:featureMember>
35+
<ogr:snap_lines_to_lines fid="lines.4">
36+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>7,-3 10,-3</gml:coordinates></gml:LineString></ogr:geometryProperty>
37+
</ogr:snap_lines_to_lines>
38+
</gml:featureMember>
39+
<gml:featureMember>
40+
<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>
42+
</ogr:snap_lines_to_lines>
43+
</gml:featureMember>
44+
<gml:featureMember>
45+
<ogr:snap_lines_to_lines fid="lines.6">
46+
</ogr:snap_lines_to_lines>
47+
</gml:featureMember>
48+
</ogr:FeatureCollection>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<GMLFeatureClassList>
2+
<GMLFeatureClass>
3+
<Name>snap_polys_to_polys</Name>
4+
<ElementPath>snap_polys_to_polys</ElementPath>
5+
<!--POLYGON-->
6+
<GeometryType>3</GeometryType>
7+
<SRSName>EPSG:4326</SRSName>
8+
<DatasetSpecificInfo>
9+
<FeatureCount>4</FeatureCount>
10+
<ExtentXMin>-1.00000</ExtentXMin>
11+
<ExtentXMax>10.00000</ExtentXMax>
12+
<ExtentYMin>-3.00000</ExtentYMin>
13+
<ExtentYMax>5.00000</ExtentYMax>
14+
</DatasetSpecificInfo>
15+
<PropertyDefn>
16+
<Name>name</Name>
17+
<ElementPath>name</ElementPath>
18+
<Type>String</Type>
19+
<Width>5</Width>
20+
</PropertyDefn>
21+
<PropertyDefn>
22+
<Name>intval</Name>
23+
<ElementPath>intval</ElementPath>
24+
<Type>Integer</Type>
25+
</PropertyDefn>
26+
<PropertyDefn>
27+
<Name>floatval</Name>
28+
<ElementPath>floatval</ElementPath>
29+
<Type>Real</Type>
30+
</PropertyDefn>
31+
</GMLFeatureClass>
32+
</GMLFeatureClassList>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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>-1</gml:X><gml:Y>-3</gml:Y></gml:coord>
10+
<gml:coord><gml:X>10</gml:X><gml:Y>5</gml:Y></gml:coord>
11+
</gml:Box>
12+
</gml:boundedBy>
13+
14+
<gml:featureMember>
15+
<ogr:snap_polys_to_polys fid="polys.0">
16+
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>-1,-1 -1,3 3,3 3,2 2,2 2,2 2,2 2,-1 -1,-1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
17+
<ogr:name>aaaaa</ogr:name>
18+
<ogr:intval>33</ogr:intval>
19+
<ogr:floatval>44.123456</ogr:floatval>
20+
</ogr:snap_polys_to_polys>
21+
</gml:featureMember>
22+
<gml:featureMember>
23+
<ogr:snap_polys_to_polys fid="polys.1">
24+
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>5,5 6,4 4,4 5,5</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
25+
<ogr:name>Aaaaa</ogr:name>
26+
<ogr:intval>-33</ogr:intval>
27+
<ogr:floatval>0</ogr:floatval>
28+
</ogr:snap_polys_to_polys>
29+
</gml:featureMember>
30+
<gml:featureMember>
31+
<ogr:snap_polys_to_polys fid="polys.3">
32+
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>6,1 10,1 10,-3 6,-3 6,1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs><gml:innerBoundaryIs><gml:LinearRing><gml:coordinates>7,0 7,-2 9,0 7,0</gml:coordinates></gml:LinearRing></gml:innerBoundaryIs></gml:Polygon></ogr:geometryProperty>
33+
<ogr:name>ASDF</ogr:name>
34+
<ogr:intval>0</ogr:intval>
35+
</ogr:snap_polys_to_polys>
36+
</gml:featureMember>
37+
<gml:featureMember>
38+
<ogr:snap_polys_to_polys fid="polys.4">
39+
<ogr:intval>120</ogr:intval>
40+
<ogr:floatval>-100291.43213</ogr:floatval>
41+
</ogr:snap_polys_to_polys>
42+
</gml:featureMember>
43+
</ogr:FeatureCollection>

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1387,3 +1387,33 @@ tests:
13871387
OUTPUT_LAYER:
13881388
name: expected/geometry_by_expression_line.gml
13891389
type: vector
1390+
1391+
- algorithm: qgis:snapgeometriestolayer
1392+
name: Snap lines to lines
1393+
params:
1394+
INPUT:
1395+
name: snap_lines.gml
1396+
type: vector
1397+
REFERENCE_LAYER:
1398+
name: lines.gml
1399+
type: vector
1400+
TOLERANCE: 1.0
1401+
results:
1402+
OUTPUT:
1403+
name: expected/snap_lines_to_lines.gml
1404+
type: vector
1405+
1406+
- algorithm: qgis:snapgeometriestolayer
1407+
name: Snap polygons to polygons
1408+
params:
1409+
INPUT:
1410+
name: snap_polys.gml
1411+
type: vector
1412+
REFERENCE_LAYER:
1413+
name: polys.gml
1414+
type: vector
1415+
TOLERANCE: 1.0
1416+
results:
1417+
OUTPUT:
1418+
name: expected/snap_polys_to_polys.gml
1419+
type: vector
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_lines</Name>
4+
<ElementPath>snap_lines</ElementPath>
5+
<!--LINESTRING-->
6+
<GeometryType>2</GeometryType>
7+
<SRSName>EPSG:4326</SRSName>
8+
<DatasetSpecificInfo>
9+
<FeatureCount>7</FeatureCount>
10+
<ExtentXMin>-1.00000</ExtentXMin>
11+
<ExtentXMax>11.34679</ExtentXMax>
12+
<ExtentYMin>-5.00000</ExtentYMin>
13+
<ExtentYMax>5.28899</ExtentYMax>
14+
</DatasetSpecificInfo>
15+
</GMLFeatureClass>
16+
</GMLFeatureClassList>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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>-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>
11+
</gml:Box>
12+
</gml:boundedBy>
13+
14+
<gml:featureMember>
15+
<ogr:snap_lines fid="lines.0">
16+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>5.965321100917431,2.300550458715596 8.653211009174312,1.606972477064221 11.346788990825686,5.288990825688074</gml:coordinates></gml:LineString></ogr:geometryProperty>
17+
</ogr:snap_lines>
18+
</gml:featureMember>
19+
<gml:featureMember>
20+
<ogr:snap_lines fid="lines.1">
21+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>-1,-1 1,-1</gml:coordinates></gml:LineString></ogr:geometryProperty>
22+
</ogr:snap_lines>
23+
</gml:featureMember>
24+
<gml:featureMember>
25+
<ogr:snap_lines fid="lines.2">
26+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>2,0 1.826605504587156,2.138715596330275 2.211493443352086,2.093215161348839 3.27743119266055,1.815045871559633</gml:coordinates></gml:LineString></ogr:geometryProperty>
27+
</ogr:snap_lines>
28+
</gml:featureMember>
29+
<gml:featureMember>
30+
<ogr:snap_lines fid="lines.3">
31+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>3,-3 3,-5</gml:coordinates></gml:LineString></ogr:geometryProperty>
32+
</ogr:snap_lines>
33+
</gml:featureMember>
34+
<gml:featureMember>
35+
<ogr:snap_lines fid="lines.4">
36+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>7,-3 10,-3</gml:coordinates></gml:LineString></ogr:geometryProperty>
37+
</ogr:snap_lines>
38+
</gml:featureMember>
39+
<gml:featureMember>
40+
<ogr:snap_lines fid="lines.5">
41+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>5.826605504587156,-2.861284403669725 7.751422018348623,-1.641605504587157 9.092339449541285,0.323532110091743 10.208073394495413,0.849724770642202</gml:coordinates></gml:LineString></ogr:geometryProperty>
42+
</ogr:snap_lines>
43+
</gml:featureMember>
44+
<gml:featureMember>
45+
<ogr:snap_lines fid="lines.6">
46+
</ogr:snap_lines>
47+
</gml:featureMember>
48+
</ogr:FeatureCollection>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<GMLFeatureClassList>
2+
<GMLFeatureClass>
3+
<Name>snap_polys</Name>
4+
<ElementPath>snap_polys</ElementPath>
5+
<!--POLYGON-->
6+
<GeometryType>3</GeometryType>
7+
<SRSName>EPSG:4326</SRSName>
8+
<DatasetSpecificInfo>
9+
<FeatureCount>4</FeatureCount>
10+
<ExtentXMin>-1.00000</ExtentXMin>
11+
<ExtentXMax>10.00000</ExtentXMax>
12+
<ExtentYMin>-3.00000</ExtentYMin>
13+
<ExtentYMax>5.00000</ExtentYMax>
14+
</DatasetSpecificInfo>
15+
<PropertyDefn>
16+
<Name>name</Name>
17+
<ElementPath>name</ElementPath>
18+
<Type>String</Type>
19+
<Width>5</Width>
20+
</PropertyDefn>
21+
<PropertyDefn>
22+
<Name>intval</Name>
23+
<ElementPath>intval</ElementPath>
24+
<Type>Integer</Type>
25+
</PropertyDefn>
26+
<PropertyDefn>
27+
<Name>floatval</Name>
28+
<ElementPath>floatval</ElementPath>
29+
<Type>Real</Type>
30+
</PropertyDefn>
31+
</GMLFeatureClass>
32+
</GMLFeatureClassList>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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>-1</gml:X><gml:Y>-3</gml:Y></gml:coord>
10+
<gml:coord><gml:X>10</gml:X><gml:Y>5</gml:Y></gml:coord>
11+
</gml:Box>
12+
</gml:boundedBy>
13+
14+
<gml:featureMember>
15+
<ogr:snap_polys fid="polys.0">
16+
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>-1,-1 -0.784492588369441,2.795267958950969 2.849144811858609,2.773717217787913 2.978449258836944,2.0 2.300050170012289,2.312229761192763 1.730615735461802,2.183181299885975 1.804771291422702,1.306921584337844 1.670061352901153,0.930982619199196 1.754632529218785,0.447776631639586 2,-1 -1,-1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
17+
<ogr:name>aaaaa</ogr:name>
18+
<ogr:intval>33</ogr:intval>
19+
<ogr:floatval>44.123455999999997</ogr:floatval>
20+
</ogr:snap_polys>
21+
</gml:featureMember>
22+
<gml:featureMember>
23+
<ogr:snap_polys fid="polys.1">
24+
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>5,5 6,4 4,4 5,5</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
25+
<ogr:name>Aaaaa</ogr:name>
26+
<ogr:intval>-33</ogr:intval>
27+
<ogr:floatval>0.000000000000000</ogr:floatval>
28+
</ogr:snap_polys>
29+
</gml:featureMember>
30+
<gml:featureMember>
31+
<ogr:snap_polys fid="polys.3">
32+
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>6,1 10,1 10,-3 6,-3 6,1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs><gml:innerBoundaryIs><gml:LinearRing><gml:coordinates>7.431014823261117,-0.247833523375142 7,-2 9.118529076396808,0.204732041049031 7.431014823261117,-0.247833523375142</gml:coordinates></gml:LinearRing></gml:innerBoundaryIs></gml:Polygon></ogr:geometryProperty>
33+
<ogr:name>ASDF</ogr:name>
34+
<ogr:intval>0</ogr:intval>
35+
</ogr:snap_polys>
36+
</gml:featureMember>
37+
<gml:featureMember>
38+
<ogr:snap_polys fid="polys.4">
39+
<ogr:intval>120</ogr:intval>
40+
<ogr:floatval>-100291.432130000001052</ogr:floatval>
41+
</ogr:snap_polys>
42+
</gml:featureMember>
43+
</ogr:FeatureCollection>

‎src/analysis/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ SET(QGIS_ANALYSIS_SRCS
3838
raster/qgsrastermatrix.cpp
3939
vector/mersenne-twister.cpp
4040
vector/qgsgeometryanalyzer.cpp
41+
vector/qgsgeometrysnapper.cpp
4142
vector/qgspointsample.cpp
4243
vector/qgstransectsample.cpp
4344
vector/qgszonalstatistics.cpp
@@ -52,6 +53,7 @@ SET(QGIS_ANALYSIS_SRCS
5253
SET(QGIS_ANALYSIS_MOC_HDRS
5354
openstreetmap/qgsosmdownload.h
5455
openstreetmap/qgsosmimport.h
56+
vector/qgsgeometrysnapper.h
5557
)
5658

5759
INCLUDE_DIRECTORIES(SYSTEM ${SPATIALITE_INCLUDE_DIR})

‎src/analysis/vector/qgsgeometrysnapper.cpp

Lines changed: 648 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
/***************************************************************************
2+
* qgsgeometrysnapper.h *
3+
* ------------------- *
4+
* copyright : (C) 2014 by Sandro Mani / Sourcepole AG *
5+
* email : smani@sourcepole.ch *
6+
***************************************************************************/
7+
8+
/***************************************************************************
9+
* *
10+
* This program is free software; you can redistribute it and/or modify *
11+
* it under the terms of the GNU General Public License as published by *
12+
* the Free Software Foundation; either version 2 of the License, or *
13+
* (at your option) any later version. *
14+
* *
15+
***************************************************************************/
16+
17+
#ifndef QGS_GEOMETRY_SNAPPER_H
18+
#define QGS_GEOMETRY_SNAPPER_H
19+
20+
#include <QMutex>
21+
#include <QFuture>
22+
#include <QStringList>
23+
#include "qgsspatialindex.h"
24+
#include "qgsabstractgeometry.h"
25+
#include "qgspointv2.h"
26+
27+
class QgsVectorLayer;
28+
29+
/**
30+
* \class QgsGeometrySnapper
31+
* \ingroup analysis
32+
* QgsGeometrySnapper allows a geometry to be snapped to the geometries within a
33+
* different refence layer. Vertices in the geometries will be modified to
34+
* match the reference layer features within a specified snap tolerance.
35+
* \note added in QGIS 3.0
36+
*/
37+
class ANALYSIS_EXPORT QgsGeometrySnapper : public QObject
38+
{
39+
Q_OBJECT
40+
41+
public:
42+
43+
/**
44+
* Constructor for QgsGeometrySnapper. A reference layer which contains geometries to snap to must be
45+
* set. The snap tolerance is specified in the layer units for the
46+
* reference layer, and it is assumed that all geometries snapped using this object will have the
47+
* same CRS as the reference layer (ie, no reprojection is performed).
48+
*/
49+
QgsGeometrySnapper( QgsVectorLayer* referenceLayer, double snapTolerance );
50+
51+
/**
52+
* Snaps a geometry to the reference layer and returns the result. The geometry must be in the same
53+
* CRS as the reference layer, and must have the same type as the reference layer geometry.
54+
*/
55+
QgsGeometry snapGeometry( const QgsGeometry& geometry ) const;
56+
57+
/**
58+
* Snaps a set of features to the reference layer and returns the result. This operation is
59+
* multithreaded for performance. The featureSnapped() signal will be emitted each time a feature
60+
* is processed. This method is not safe to call from multiple threads concurrently.
61+
*/
62+
QgsFeatureList snapFeatures( const QgsFeatureList& features );
63+
64+
signals:
65+
66+
//! Emitted each time a feature has been processed when calling snapFeatures()
67+
void featureSnapped();
68+
69+
private:
70+
struct ProcessFeatureWrapper
71+
{
72+
QgsGeometrySnapper* instance;
73+
explicit ProcessFeatureWrapper( QgsGeometrySnapper* _instance ) : instance( _instance ) {}
74+
void operator()( QgsFeature& feature ) { return instance->processFeature( feature ); }
75+
};
76+
77+
enum PointFlag { SnappedToRefNode, SnappedToRefSegment, Unsnapped };
78+
79+
QgsVectorLayer* mReferenceLayer;
80+
QgsFeatureList mInputFeatures;
81+
82+
double mSnapTolerance;
83+
84+
QgsSpatialIndex mIndex;
85+
mutable QMutex mIndexMutex;
86+
mutable QMutex mReferenceLayerMutex;
87+
88+
void processFeature( QgsFeature& feature );
89+
90+
int polyLineSize( const QgsAbstractGeometry* geom, int iPart, int iRing ) const;
91+
};
92+
93+
///@cond PRIVATE
94+
class QgsSnapIndex
95+
{
96+
public:
97+
struct CoordIdx
98+
{
99+
CoordIdx( const QgsAbstractGeometry* _geom, QgsVertexId _vidx )
100+
: geom( _geom )
101+
, vidx( _vidx )
102+
{}
103+
QgsPointV2 point() const { return geom->vertexAt( vidx ); }
104+
105+
const QgsAbstractGeometry* geom;
106+
QgsVertexId vidx;
107+
};
108+
109+
enum SnapType { SnapPoint, SnapSegment };
110+
111+
class SnapItem
112+
{
113+
public:
114+
virtual ~SnapItem() {}
115+
SnapType type;
116+
virtual QgsPointV2 getSnapPoint( const QgsPointV2& p ) const = 0;
117+
118+
protected:
119+
explicit SnapItem( SnapType _type ) : type( _type ) {}
120+
};
121+
122+
class PointSnapItem : public QgsSnapIndex::SnapItem
123+
{
124+
public:
125+
explicit PointSnapItem( const CoordIdx* _idx );
126+
QgsPointV2 getSnapPoint( const QgsPointV2 &/*p*/ ) const override;
127+
const CoordIdx* idx;
128+
};
129+
130+
class SegmentSnapItem : public QgsSnapIndex::SnapItem
131+
{
132+
public:
133+
SegmentSnapItem( const CoordIdx* _idxFrom, const CoordIdx* _idxTo );
134+
QgsPointV2 getSnapPoint( const QgsPointV2 &p ) const override;
135+
bool getIntersection( const QgsPointV2& p1, const QgsPointV2& p2, QgsPointV2& inter ) const;
136+
bool getProjection( const QgsPointV2 &p, QgsPointV2 &pProj );
137+
const CoordIdx* idxFrom;
138+
const CoordIdx* idxTo;
139+
};
140+
141+
QgsSnapIndex( const QgsPointV2& origin, double cellSize );
142+
~QgsSnapIndex();
143+
void addGeometry( const QgsAbstractGeometry *geom );
144+
QgsPointV2 getClosestSnapToPoint( const QgsPointV2& p, const QgsPointV2& q );
145+
SnapItem *getSnapItem( const QgsPointV2& pos, double tol, PointSnapItem **pSnapPoint = nullptr, SegmentSnapItem **pSnapSegment = nullptr ) const;
146+
147+
private:
148+
typedef QList<SnapItem*> Cell;
149+
typedef QPair<QgsPointV2, QgsPointV2> Segment;
150+
151+
class GridRow
152+
{
153+
public:
154+
GridRow() : mColStartIdx( 0 ) {}
155+
~GridRow();
156+
const Cell *getCell( int col ) const;
157+
Cell& getCreateCell( int col );
158+
QList<SnapItem*> getSnapItems( int colStart, int colEnd ) const;
159+
160+
private:
161+
QList<QgsSnapIndex::Cell> mCells;
162+
int mColStartIdx;
163+
};
164+
165+
QgsPointV2 mOrigin;
166+
double mCellSize;
167+
168+
QList<CoordIdx*> mCoordIdxs;
169+
QList<GridRow> mGridRows;
170+
int mRowsStartIdx;
171+
172+
void addPoint( const CoordIdx* idx );
173+
void addSegment( const CoordIdx* idxFrom, const CoordIdx* idxTo );
174+
const Cell* getCell( int col, int row ) const;
175+
Cell &getCreateCell( int col, int row );
176+
177+
QgsSnapIndex( const QgsSnapIndex& rh );
178+
QgsSnapIndex& operator=( const QgsSnapIndex& rh );
179+
};
180+
181+
///@endcond
182+
183+
#endif // QGS_GEOMETRY_SNAPPER_H

0 commit comments

Comments
 (0)
Please sign in to comment.