Skip to content

Commit 9bcd21f

Browse files
authoredSep 14, 2018
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
Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
/***************************************************************************
2+
qgsgeometrysnappersinglesource.cpp
3+
---------------------
4+
Date : May 2018
5+
Copyright : (C) 2018 by Martin Dobias
6+
Email : wonder dot sk at gmail dot com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#include "qgsgeometrysnappersinglesource.h"
17+
18+
#include "qgsfeatureiterator.h"
19+
#include "qgsfeaturesink.h"
20+
#include "qgsfeaturesource.h"
21+
#include "qgsfeedback.h"
22+
#include "qgsgeometrycollection.h"
23+
#include "qgsgeometryutils.h"
24+
#include "qgslinestring.h"
25+
#include "qgspolygon.h"
26+
#include "qgsspatialindex.h"
27+
28+
//! record about vertex coordinates and index of anchor to which it is snapped
29+
struct AnchorPoint
30+
{
31+
//! coordinates of the point
32+
double x, y;
33+
34+
/**
35+
* Anchor information:
36+
* 0+ - index of anchor to which this point should be snapped
37+
* -1 - initial value (undefined)
38+
* -2 - this point is an anchor, i.e. do not snap this point (snap others to this point)
39+
*/
40+
int anchor;
41+
};
42+
43+
44+
//! record about anchor being along a segment
45+
struct AnchorAlongSegment
46+
{
47+
int anchor; //!< Index of the anchor point
48+
double along; //!< Distance of the anchor point along the segment
49+
};
50+
51+
52+
static void buildSnapIndex( QgsFeatureIterator &fi, QgsSpatialIndex &index, QVector<AnchorPoint> &pnts, QgsFeedback *feedback, int &count, int totalCount )
53+
{
54+
QgsFeature f;
55+
int pntId = 0;
56+
57+
while ( fi.nextFeature( f ) )
58+
{
59+
if ( feedback->isCanceled() )
60+
break;
61+
62+
QgsGeometry g = f.geometry();
63+
64+
for ( auto it = g.vertices_begin(); it != g.vertices_end(); ++it )
65+
{
66+
QgsPoint pt = *it;
67+
QgsRectangle rect( pt.x(), pt.y(), pt.x(), pt.y() );
68+
69+
QList<QgsFeatureId> ids = index.intersects( rect );
70+
if ( ids.isEmpty() )
71+
{
72+
// add to tree and to structure
73+
index.insertFeature( pntId, pt.boundingBox() );
74+
75+
AnchorPoint xp;
76+
xp.x = pt.x();
77+
xp.y = pt.y();
78+
xp.anchor = -1;
79+
pnts.append( xp );
80+
pntId++;
81+
}
82+
}
83+
84+
++count;
85+
feedback->setProgress( 100. * count / totalCount );
86+
}
87+
}
88+
89+
90+
static void assignAnchors( QgsSpatialIndex &index, QVector<AnchorPoint> &pnts, double thresh )
91+
{
92+
double thresh2 = thresh * thresh;
93+
int nanchors = 0, ntosnap = 0;
94+
for ( int point = 0; point < pnts.count(); ++point )
95+
{
96+
if ( pnts[point].anchor >= 0 )
97+
continue;
98+
99+
pnts[point].anchor = -2; // make it anchor
100+
nanchors++;
101+
102+
// Find points in threshold
103+
double x = pnts[point].x, y = pnts[point].y;
104+
QgsRectangle rect( x - thresh, y - thresh, x + thresh, y + thresh );
105+
106+
const QList<QgsFeatureId> ids = index.intersects( rect );
107+
for ( QgsFeatureId pointb : ids )
108+
{
109+
if ( pointb == point )
110+
continue;
111+
112+
double dx = pnts[pointb].x - pnts[point].x;
113+
double dy = pnts[pointb].y - pnts[point].y;
114+
double dist2 = dx * dx + dy * dy;
115+
if ( dist2 > thresh2 )
116+
continue; // outside threshold
117+
118+
if ( pnts[pointb].anchor == -1 )
119+
{
120+
// doesn't have an anchor yet
121+
pnts[pointb].anchor = point;
122+
ntosnap++;
123+
}
124+
else if ( pnts[pointb].anchor >= 0 )
125+
{
126+
// check distance to previously assigned anchor
127+
double dx2 = pnts[pnts[pointb].anchor].x - pnts[pointb].x;
128+
double dy2 = pnts[pnts[pointb].anchor].y - pnts[pointb].y;
129+
double dist2_a = dx2 * dx2 + dy2 * dy2;
130+
if ( dist2 < dist2_a )
131+
pnts[pointb].anchor = point; // replace old anchor
132+
}
133+
}
134+
}
135+
}
136+
137+
138+
static bool snapPoint( QgsPoint *pt, QgsSpatialIndex &index, QVector<AnchorPoint> &pnts )
139+
{
140+
// Find point ( should always find one point )
141+
QList<QgsFeatureId> fids = index.intersects( QgsRectangle( pt->x(), pt->y(), pt->x(), pt->y() ) );
142+
Q_ASSERT( fids.count() == 1 );
143+
144+
int spoint = fids[0];
145+
int anchor = pnts[spoint].anchor;
146+
147+
if ( anchor >= 0 )
148+
{
149+
// to be snapped
150+
pt->setX( pnts[anchor].x );
151+
pt->setY( pnts[anchor].y );
152+
return true;
153+
}
154+
155+
return false;
156+
}
157+
158+
159+
static bool snapLineString( QgsLineString *linestring, QgsSpatialIndex &index, QVector<AnchorPoint> &pnts, double thresh )
160+
{
161+
QVector<QgsPoint> newPoints;
162+
QVector<int> anchors; // indexes of anchors for vertices
163+
double thresh2 = thresh * thresh;
164+
double minDistX, minDistY; // coordinates of the closest point on the segment line
165+
bool changed = false;
166+
167+
// snap vertices
168+
for ( int v = 0; v < linestring->numPoints(); v++ )
169+
{
170+
double x = linestring->xAt( v );
171+
double y = linestring->yAt( v );
172+
QgsRectangle rect( x, y, x, y );
173+
174+
// Find point ( should always find one point )
175+
QList<QgsFeatureId> fids = index.intersects( rect );
176+
Q_ASSERT( fids.count() == 1 );
177+
178+
int spoint = fids.first();
179+
int anchor = pnts[spoint].anchor;
180+
if ( anchor >= 0 )
181+
{
182+
// to be snapped
183+
linestring->setXAt( v, pnts[anchor].x );
184+
linestring->setYAt( v, pnts[anchor].y );
185+
anchors.append( anchor ); // point on new location
186+
changed = true;
187+
}
188+
else
189+
{
190+
anchors.append( spoint ); // old point
191+
}
192+
}
193+
194+
// Snap all segments to anchors in threshold
195+
for ( int v = 0; v < linestring->numPoints() - 1; v++ )
196+
{
197+
double x1 = linestring->xAt( v );
198+
double x2 = linestring->xAt( v + 1 );
199+
double y1 = linestring->yAt( v );
200+
double y2 = linestring->yAt( v + 1 );
201+
202+
newPoints << linestring->pointN( v );
203+
204+
// Box
205+
double xmin = x1, xmax = x2, ymin = y1, ymax = y2;
206+
if ( xmin > xmax )
207+
std::swap( xmin, xmax );
208+
if ( ymin > ymax )
209+
std::swap( ymin, ymax );
210+
211+
QgsRectangle rect( xmin - thresh, ymin - thresh, xmax + thresh, ymax + thresh );
212+
213+
// Find points
214+
const QList<QgsFeatureId> fids = index.intersects( rect );
215+
216+
QVector<AnchorAlongSegment> newVerticesAlongSegment;
217+
218+
// Snap to anchor in threshold different from end points
219+
for ( QgsFeatureId fid : fids )
220+
{
221+
int spoint = fid;
222+
223+
if ( spoint == anchors[v] || spoint == anchors[v + 1] )
224+
continue; // end point
225+
if ( pnts[spoint].anchor >= 0 )
226+
continue; // point is not anchor
227+
228+
// Check the distance
229+
double dist2 = QgsGeometryUtils::sqrDistToLine( pnts[spoint].x, pnts[spoint].y, x1, y1, x2, y2, minDistX, minDistY, 0 );
230+
// skip points that are behind segment's endpoints or extremely close to them
231+
double dx1 = minDistX - x1, dx2 = minDistX - x2;
232+
double dy1 = minDistY - y1, dy2 = minDistY - y2;
233+
bool isOnSegment = !qgsDoubleNear( dx1 * dx1 + dy1 * dy1, 0 ) && !qgsDoubleNear( dx2 * dx2 + dy2 * dy2, 0 );
234+
if ( isOnSegment && dist2 <= thresh2 )
235+
{
236+
// an anchor is in the threshold
237+
AnchorAlongSegment item;
238+
item.anchor = spoint;
239+
item.along = QgsPointXY( x1, y1 ).distance( minDistX, minDistY );
240+
newVerticesAlongSegment << item;
241+
}
242+
}
243+
244+
if ( !newVerticesAlongSegment.isEmpty() )
245+
{
246+
// sort by distance along the segment
247+
std::sort( newVerticesAlongSegment.begin(), newVerticesAlongSegment.end(), []( const AnchorAlongSegment & p1, const AnchorAlongSegment & p2 )
248+
{
249+
return ( p1.along < p2.along ? -1 : ( p1.along > p2.along ) );
250+
} );
251+
252+
// insert new vertices
253+
for ( int i = 0; i < newVerticesAlongSegment.count(); i++ )
254+
{
255+
int anchor = newVerticesAlongSegment[i].anchor;
256+
newPoints << QgsPoint( pnts[anchor].x, pnts[anchor].y, 0 );
257+
}
258+
changed = true;
259+
}
260+
}
261+
262+
// append end point
263+
newPoints << linestring->pointN( linestring->numPoints() - 1 );
264+
265+
// replace linestring's points
266+
if ( changed )
267+
linestring->setPoints( newPoints );
268+
269+
return changed;
270+
}
271+
272+
273+
static bool snapGeometry( QgsAbstractGeometry *g, QgsSpatialIndex &index, QVector<AnchorPoint> &pnts, double thresh )
274+
{
275+
bool changed = false;
276+
if ( QgsLineString *linestring = qgsgeometry_cast<QgsLineString *>( g ) )
277+
{
278+
changed |= snapLineString( linestring, index, pnts, thresh );
279+
}
280+
else if ( QgsPolygon *polygon = qgsgeometry_cast<QgsPolygon *>( g ) )
281+
{
282+
if ( QgsLineString *exteriorRing = qgsgeometry_cast<QgsLineString *>( polygon->exteriorRing() ) )
283+
changed |= snapLineString( exteriorRing, index, pnts, thresh );
284+
for ( int i = 0; i < polygon->numInteriorRings(); ++i )
285+
{
286+
if ( QgsLineString *interiorRing = qgsgeometry_cast<QgsLineString *>( polygon->interiorRing( i ) ) )
287+
changed |= snapLineString( interiorRing, index, pnts, thresh );
288+
}
289+
}
290+
else if ( QgsGeometryCollection *collection = qgsgeometry_cast<QgsGeometryCollection *>( g ) )
291+
{
292+
for ( int i = 0; i < collection->numGeometries(); ++i )
293+
changed |= snapGeometry( collection->geometryN( i ), index, pnts, thresh );
294+
}
295+
else if ( QgsPoint *pt = qgsgeometry_cast<QgsPoint *>( g ) )
296+
{
297+
changed |= snapPoint( pt, index, pnts );
298+
}
299+
300+
return changed;
301+
}
302+
303+
304+
int QgsGeometrySnapperSingleSource::run( const QgsFeatureSource &source, QgsFeatureSink &sink, double thresh, QgsFeedback *feedback )
305+
{
306+
// the logic here comes from GRASS implementation of Vect_snap_lines_list()
307+
308+
int count = 0;
309+
int totalCount = source.featureCount() * 2;
310+
311+
// step 1: record all point locations in a spatial index + extra data structure to keep
312+
// reference to which other point they have been snapped to (in the next phase).
313+
314+
QgsSpatialIndex index;
315+
QVector<AnchorPoint> pnts;
316+
QgsFeatureRequest request;
317+
request.setSubsetOfAttributes( QgsAttributeList() );
318+
QgsFeatureIterator fi = source.getFeatures( request );
319+
buildSnapIndex( fi, index, pnts, feedback, count, totalCount );
320+
321+
if ( feedback->isCanceled() )
322+
return 0;
323+
324+
// step 2: go through all registered points and if not yet marked mark it as anchor and
325+
// assign this anchor to all not yet marked points in threshold
326+
327+
assignAnchors( index, pnts, thresh );
328+
329+
// step 3: alignment of vertices and segments to the anchors
330+
// Go through all lines and:
331+
// 1) for all vertices: if not anchor snap it to its anchor
332+
// 2) for all segments: snap it to all anchors in threshold (except anchors of vertices of course)
333+
334+
int modified = 0;
335+
QgsFeature f;
336+
fi = source.getFeatures();
337+
while ( fi.nextFeature( f ) )
338+
{
339+
if ( feedback->isCanceled() )
340+
break;
341+
342+
QgsGeometry geom = f.geometry();
343+
if ( snapGeometry( geom.get(), index, pnts, thresh ) )
344+
{
345+
f.setGeometry( geom );
346+
++modified;
347+
}
348+
349+
sink.addFeature( f, QgsFeatureSink::FastInsert );
350+
351+
++count;
352+
feedback->setProgress( 100. * count / totalCount );
353+
}
354+
355+
return modified;
356+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/***************************************************************************
2+
qgsgeometrysnappersinglesource.h
3+
---------------------
4+
Date : May 2018
5+
Copyright : (C) 2018 by Martin Dobias
6+
Email : wonder dot sk at gmail dot com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#ifndef QGSGEOMETRYSNAPPERSINGLESOURCE_H
17+
#define QGSGEOMETRYSNAPPERSINGLESOURCE_H
18+
19+
#include "qgis_analysis.h"
20+
21+
class QgsFeatureSink;
22+
class QgsFeatureSource;
23+
class QgsFeedback;
24+
25+
/**
26+
* \ingroup analysis
27+
*
28+
* Makes sure that any two vertices of the vector layer are at least at distance given by the threshold value.
29+
* The algorithm moves nearby vertices to one location and adds vertices to segments that are passing around other
30+
* vertices within the threshold. It does not remove any vertices. Also, it does not modify geometries unless
31+
* needed (it does not snap coordinates to a grid).
32+
*
33+
* This algorithm comes handy when doing vector overlay operations such as intersection, union or difference
34+
* to prevent possible topological errors caused by numerical errors if coordinates are very close to each other.
35+
*
36+
* After running the algorithm some previously valid geometries may become invalid and therefore it may be useful
37+
* to run Fix geometries algorithm afterwards.
38+
*
39+
* \note Originally ported from GRASS implementation of Vect_snap_lines_list()
40+
*
41+
* \since QGIS 3.4
42+
*/
43+
class ANALYSIS_EXPORT QgsGeometrySnapperSingleSource
44+
{
45+
public:
46+
47+
/**
48+
* Run the algorithm on given source and output results to the sink, using threshold value in the source's map units.
49+
* Returns number of modified geometries.
50+
*/
51+
static int run( const QgsFeatureSource &source, QgsFeatureSink &sink, double thresh, QgsFeedback *feedback );
52+
};
53+
54+
#endif // QGSGEOMETRYSNAPPERSINGLESOURCE_H

0 commit comments

Comments
 (0)
Please sign in to comment.