QGIS API Documentation 3.41.0-Master (45a0abf3bec)
Loading...
Searching...
No Matches
qgsgeometrysnappersinglesource.cpp
Go to the documentation of this file.
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
17
18#include "qgsfeatureiterator.h"
19#include "qgsfeaturesink.h"
20#include "qgsfeaturesource.h"
21#include "qgsfeedback.h"
23#include "qgsgeometryutils.h"
24#include "qgslinestring.h"
25#include "qgspolygon.h"
26#include "qgsspatialindex.h"
27
30{
32 double x, y;
33
40 int anchor;
41};
42
43
46{
47 int anchor;
48 double along;
49};
50
51
52static void buildSnapIndex( QgsFeatureIterator &fi, QgsSpatialIndex &index, QVector<AnchorPoint> &pnts, QgsFeedback *feedback, long long &count, long long totalCount )
53{
54 QgsFeature f;
55 int pntId = 0;
56
57 while ( fi.nextFeature( f ) )
58 {
59 if ( feedback->isCanceled() )
60 break;
61
62 const QgsGeometry g = f.geometry();
63
64 for ( auto it = g.vertices_begin(); it != g.vertices_end(); ++it )
65 {
66 const QgsPoint pt = *it;
67 const QgsRectangle rect( pt.x(), pt.y(), pt.x(), pt.y() );
68
69 const QList<QgsFeatureId> ids = index.intersects( rect );
70 if ( ids.isEmpty() )
71 {
72 // add to tree and to structure
73 index.addFeature( 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
90static void assignAnchors( QgsSpatialIndex &index, QVector<AnchorPoint> &pnts, double thresh )
91{
92 const double thresh2 = thresh * thresh;
93 for ( int point = 0; point < pnts.count(); ++point )
94 {
95 if ( pnts[point].anchor >= 0 )
96 continue;
97
98 pnts[point].anchor = -2; // make it anchor
99
100 // Find points in threshold
101 double x = pnts[point].x, y = pnts[point].y;
102 const QgsRectangle rect( x - thresh, y - thresh, x + thresh, y + thresh );
103
104 const QList<QgsFeatureId> ids = index.intersects( rect );
105 for ( const QgsFeatureId pointb : ids )
106 {
107 if ( pointb == point )
108 continue;
109
110 const double dx = pnts[pointb].x - pnts[point].x;
111 const double dy = pnts[pointb].y - pnts[point].y;
112 const double dist2 = dx * dx + dy * dy;
113 if ( dist2 > thresh2 )
114 continue; // outside threshold
115
116 if ( pnts[pointb].anchor == -1 )
117 {
118 // doesn't have an anchor yet
119 pnts[pointb].anchor = point;
120 }
121 else if ( pnts[pointb].anchor >= 0 )
122 {
123 // check distance to previously assigned anchor
124 const double dx2 = pnts[pnts[pointb].anchor].x - pnts[pointb].x;
125 const double dy2 = pnts[pnts[pointb].anchor].y - pnts[pointb].y;
126 const double dist2_a = dx2 * dx2 + dy2 * dy2;
127 if ( dist2 < dist2_a )
128 pnts[pointb].anchor = point; // replace old anchor
129 }
130 }
131 }
132}
133
134
135static bool snapPoint( QgsPoint *pt, const QgsSpatialIndex &index, const QVector<AnchorPoint> &pnts )
136{
137 // Find point ( should always find one point )
138 QList<QgsFeatureId> fids = index.intersects( QgsRectangle( pt->x(), pt->y(), pt->x(), pt->y() ) );
139 Q_ASSERT( fids.count() == 1 );
140
141 const int spoint = fids[0];
142 const int anchor = pnts[spoint].anchor;
143
144 if ( anchor >= 0 )
145 {
146 // to be snapped
147 pt->setX( pnts[anchor].x );
148 pt->setY( pnts[anchor].y );
149 return true;
150 }
151
152 return false;
153}
154
155
156static bool snapLineString( QgsLineString *linestring, const QgsSpatialIndex &index, const QVector<AnchorPoint> &pnts, double thresh )
157{
158 const int lineStringSize = linestring->numPoints();
159 QVector<QgsPoint> newPoints;
160 std::vector<int> anchors; // indexes of anchors for vertices
161 anchors.reserve( lineStringSize );
162 const double thresh2 = thresh * thresh;
163 double minDistX, minDistY; // coordinates of the closest point on the segment line
164 bool changed = false;
165
166 const AnchorPoint *pntsData = pnts.constData();
167
168 // snap vertices
169 for ( int v = 0; v < lineStringSize; v++ )
170 {
171 const double x = linestring->xAt( v );
172 const double y = linestring->yAt( v );
173 const QgsRectangle rect( x, y, x, y );
174
175 // Find point ( should always find one point )
176 QList<QgsFeatureId> fids = index.intersects( rect );
177 Q_ASSERT( fids.count() == 1 );
178
179 const int spoint = fids.first();
180 const int anchor = pntsData[spoint].anchor;
181 if ( anchor >= 0 )
182 {
183 // to be snapped
184 linestring->setXAt( v, pntsData[anchor].x );
185 linestring->setYAt( v, pntsData[anchor].y );
186 anchors.push_back( anchor ); // point on new location
187 changed = true;
188 }
189 else
190 {
191 anchors.push_back( spoint ); // old point
192 }
193 }
194
195 // Snap all segments to anchors in threshold
196 for ( int v = 0; v < linestring->numPoints() - 1; v++ )
197 {
198 const double x1 = linestring->xAt( v );
199 const double x2 = linestring->xAt( v + 1 );
200 const double y1 = linestring->yAt( v );
201 const double y2 = linestring->yAt( v + 1 );
202
203 newPoints << linestring->pointN( v );
204
205 // Box
206 double xmin = x1, xmax = x2, ymin = y1, ymax = y2;
207 if ( xmin > xmax )
208 std::swap( xmin, xmax );
209 if ( ymin > ymax )
210 std::swap( ymin, ymax );
211
212 const QgsRectangle rect( xmin - thresh, ymin - thresh, xmax + thresh, ymax + thresh );
213
214 // Find points
215 const QList<QgsFeatureId> fids = index.intersects( rect );
216
217 std::vector<AnchorAlongSegment> newVerticesAlongSegment;
218
219 // Snap to anchor in threshold different from end points
220 for ( const QgsFeatureId fid : fids )
221 {
222 const int spoint = fid;
223
224 if ( spoint == anchors[v] || spoint == anchors[v + 1] )
225 continue; // end point
226 if ( pnts[spoint].anchor >= 0 )
227 continue; // point is not anchor
228
229 // Check the distance
230 const double dist2 = QgsGeometryUtilsBase::sqrDistToLine( pnts[spoint].x, pnts[spoint].y, x1, y1, x2, y2, minDistX, minDistY, 0 );
231 // skip points that are behind segment's endpoints or extremely close to them
232 double dx1 = minDistX - x1, dx2 = minDistX - x2;
233 double dy1 = minDistY - y1, dy2 = minDistY - y2;
234 const bool isOnSegment = !qgsDoubleNear( dx1 * dx1 + dy1 * dy1, 0 ) && !qgsDoubleNear( dx2 * dx2 + dy2 * dy2, 0 );
235 if ( isOnSegment && dist2 <= thresh2 )
236 {
237 // an anchor is in the threshold
239 item.anchor = spoint;
240 item.along = QgsPointXY( x1, y1 ).distance( minDistX, minDistY );
241 newVerticesAlongSegment.push_back( item );
242 }
243 }
244
245 if ( !newVerticesAlongSegment.empty() )
246 {
247 // sort by distance along the segment
248 std::sort( newVerticesAlongSegment.begin(), newVerticesAlongSegment.end(), []( AnchorAlongSegment p1, AnchorAlongSegment p2 )
249 {
250 return p1.along < p2.along;
251 } );
252
253 // insert new vertices
254 for ( const AnchorAlongSegment &vertex : newVerticesAlongSegment )
255 {
256 const int anchor = vertex.anchor;
257 newPoints << QgsPoint( pntsData[anchor].x, pntsData[anchor].y, 0 );
258 }
259 changed = true;
260 }
261 }
262
263 // append end point
264 newPoints << linestring->pointN( linestring->numPoints() - 1 );
265
266 // replace linestring's points
267 if ( changed )
268 linestring->setPoints( newPoints );
269
270 return changed;
271}
272
273
274static bool snapGeometry( QgsAbstractGeometry *g, const QgsSpatialIndex &index, const QVector<AnchorPoint> &pnts, double thresh )
275{
276 bool changed = false;
277 if ( QgsLineString *linestring = qgsgeometry_cast<QgsLineString *>( g ) )
278 {
279 changed = snapLineString( linestring, index, pnts, thresh ) | changed;
280 }
281 else if ( QgsPolygon *polygon = qgsgeometry_cast<QgsPolygon *>( g ) )
282 {
283 if ( QgsLineString *exteriorRing = qgsgeometry_cast<QgsLineString *>( polygon->exteriorRing() ) )
284 changed = snapLineString( exteriorRing, index, pnts, thresh ) | changed;
285 for ( int i = 0; i < polygon->numInteriorRings(); ++i )
286 {
287 if ( QgsLineString *interiorRing = qgsgeometry_cast<QgsLineString *>( polygon->interiorRing( i ) ) )
288 changed = snapLineString( interiorRing, index, pnts, thresh ) | changed;
289 }
290 }
291 else if ( QgsGeometryCollection *collection = qgsgeometry_cast<QgsGeometryCollection *>( g ) )
292 {
293 for ( int i = 0; i < collection->numGeometries(); ++i )
294 changed = snapGeometry( collection->geometryN( i ), index, pnts, thresh ) | changed;
295 }
296 else if ( QgsPoint *pt = qgsgeometry_cast<QgsPoint *>( g ) )
297 {
298 changed = snapPoint( pt, index, pnts ) | changed;
299 }
300
301 return changed;
302}
303
304
305int QgsGeometrySnapperSingleSource::run( const QgsFeatureSource &source, QgsFeatureSink &sink, double thresh, QgsFeedback *feedback )
306{
307 // the logic here comes from GRASS implementation of Vect_snap_lines_list()
308
309 long long count = 0;
310 const long long totalCount = source.featureCount() * 2;
311
312 // step 1: record all point locations in a spatial index + extra data structure to keep
313 // reference to which other point they have been snapped to (in the next phase).
314
315 QgsSpatialIndex index;
316 QVector<AnchorPoint> pnts;
317 QgsFeatureRequest request;
318 request.setNoAttributes();
319 QgsFeatureIterator fi = source.getFeatures( request );
320 buildSnapIndex( fi, index, pnts, feedback, count, totalCount );
321
322 if ( feedback->isCanceled() )
323 return 0;
324
325 // step 2: go through all registered points and if not yet marked mark it as anchor and
326 // assign this anchor to all not yet marked points in threshold
327
328 assignAnchors( index, pnts, thresh );
329
330 // step 3: alignment of vertices and segments to the anchors
331 // Go through all lines and:
332 // 1) for all vertices: if not anchor snap it to its anchor
333 // 2) for all segments: snap it to all anchors in threshold (except anchors of vertices of course)
334
335 int modified = 0;
336 QgsFeature f;
337 fi = source.getFeatures();
338 while ( fi.nextFeature( f ) )
339 {
340 if ( feedback->isCanceled() )
341 break;
342
343 QgsGeometry geom = f.geometry();
344 if ( snapGeometry( geom.get(), index, pnts, thresh ) )
345 {
346 f.setGeometry( geom );
347 ++modified;
348 }
349
351
352 ++count;
353 feedback->setProgress( 100. * count / totalCount );
354 }
355
356 return modified;
357}
Abstract base class for all geometries.
virtual QgsRectangle boundingBox() const
Returns the minimal bounding box for the geometry.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
This class wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setNoAttributes()
Set that no attributes will be fetched.
An interface for objects which accept features via addFeature(s) methods.
virtual bool addFeature(QgsFeature &feature, QgsFeatureSink::Flags flags=QgsFeatureSink::Flags())
Adds a single feature to the sink.
@ FastInsert
Use faster inserts, at the cost of updating the passed features to reflect changes made at the provid...
An interface for objects which provide features via a getFeatures method.
virtual QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const =0
Returns an iterator for the features in the source.
virtual long long featureCount() const =0
Returns the number of features contained in the source, or -1 if the feature count is unknown.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:58
QgsGeometry geometry
Definition qgsfeature.h:69
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition qgsfeedback.h:44
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:53
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:61
static int run(const QgsFeatureSource &source, QgsFeatureSink &sink, double thresh, QgsFeedback *feedback)
Run the algorithm on given source and output results to the sink, using threshold value in the source...
static double sqrDistToLine(double ptX, double ptY, double x1, double y1, double x2, double y2, double &minDistX, double &minDistY, double epsilon)
Returns the squared distance between a point and a line.
A geometry is the spatial representation of a feature.
QgsAbstractGeometry * get()
Returns a modifiable (non-const) reference to the underlying abstract geometry primitive.
QgsAbstractGeometry::vertex_iterator vertices_begin() const
Returns STL-style iterator pointing to the first vertex of the geometry.
QgsAbstractGeometry::vertex_iterator vertices_end() const
Returns STL-style iterator pointing to the imaginary vertex after the last vertex of the geometry.
Line string geometry type, with support for z-dimension and m-values.
int numPoints() const override
Returns the number of points in the curve.
QgsPoint pointN(int i) const
Returns the specified point from inside the line string.
void setPoints(size_t size, const double *x, const double *y, const double *z=nullptr, const double *m=nullptr)
Resets the line string to match the specified point data.
double yAt(int index) const override
Returns the y-coordinate of the specified node in the line string.
void setYAt(int index, double y)
Sets the y-coordinate of the specified node in the line string.
void setXAt(int index, double x)
Sets the x-coordinate of the specified node in the line string.
double xAt(int index) const override
Returns the x-coordinate of the specified node in the line string.
A class to represent a 2D point.
Definition qgspointxy.h:60
double distance(double x, double y) const
Returns the distance between this point and a specified x, y coordinate.
Definition qgspointxy.h:206
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:49
void setY(double y)
Sets the point's y-coordinate.
Definition qgspoint.h:343
void setX(double x)
Sets the point's x-coordinate.
Definition qgspoint.h:332
double x
Definition qgspoint.h:52
double y
Definition qgspoint.h:53
Polygon geometry type.
Definition qgspolygon.h:33
A rectangle specified with double values.
A spatial index for QgsFeature objects.
QList< QgsFeatureId > intersects(const QgsRectangle &rectangle) const
Returns a list of features with a bounding box which intersects the specified rectangle.
bool addFeature(QgsFeature &feature, QgsFeatureSink::Flags flags=QgsFeatureSink::Flags()) override
Adds a feature to the index.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:5958
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
record about anchor being along a segment
int anchor
Index of the anchor point.
double along
Distance of the anchor point along the segment.
record about vertex coordinates and index of anchor to which it is snapped
double x
coordinates of the point
int anchor
Anchor information: 0+ - index of anchor to which this point should be snapped -1 - initial value (un...