QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgsgeometrysnapper.cpp
Go to the documentation of this file.
1/***************************************************************************
2 * qgsgeometrysnapper.cpp *
3 * ------------------- *
4 * copyright : (C) 2014 by Sandro Mani / Sourcepole AG *
5 * email : [email protected] *
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#include "qgsfeatureiterator.h"
18#include "qgsgeometry.h"
19#include "qgsvectorlayer.h"
20#include "qgsgeometrysnapper.h"
22#include "qgsgeometryutils.h"
23#include "qgssurface.h"
24#include "qgsmultisurface.h"
25#include "qgscurve.h"
26
27#include <QtConcurrentMap>
28#include <geos_c.h>
29
31
32QgsSnapIndex::PointSnapItem::PointSnapItem( const QgsSnapIndex::CoordIdx *_idx, bool isEndPoint )
33 : SnapItem( isEndPoint ? QgsSnapIndex::SnapEndPoint : QgsSnapIndex::SnapPoint )
34 , idx( _idx )
35{}
36
37QgsPoint QgsSnapIndex::PointSnapItem::getSnapPoint( const QgsPoint &/*p*/ ) const
38{
39 return idx->point();
40}
41
42QgsSnapIndex::SegmentSnapItem::SegmentSnapItem( const QgsSnapIndex::CoordIdx *_idxFrom, const QgsSnapIndex::CoordIdx *_idxTo )
43 : SnapItem( QgsSnapIndex::SnapSegment )
44 , idxFrom( _idxFrom )
45 , idxTo( _idxTo )
46{}
47
48QgsPoint QgsSnapIndex::SegmentSnapItem::getSnapPoint( const QgsPoint &p ) const
49{
50 return QgsGeometryUtils::projectPointOnSegment( p, idxFrom->point(), idxTo->point() );
51}
52
53bool QgsSnapIndex::SegmentSnapItem::getIntersection( const QgsPoint &p1, const QgsPoint &p2, QgsPoint &inter ) const
54{
55 const QgsPoint &q1 = idxFrom->point(), & q2 = idxTo->point();
56 QgsVector v( p2.x() - p1.x(), p2.y() - p1.y() );
57 QgsVector w( q2.x() - q1.x(), q2.y() - q1.y() );
58 const double vl = v.length();
59 const double wl = w.length();
60
61 if ( qgsDoubleNear( vl, 0, 0.000000000001 ) || qgsDoubleNear( wl, 0, 0.000000000001 ) )
62 {
63 return false;
64 }
65 v = v / vl;
66 w = w / wl;
67
68 const double d = v.y() * w.x() - v.x() * w.y();
69
70 if ( d == 0 )
71 return false;
72
73 const double dx = q1.x() - p1.x();
74 const double dy = q1.y() - p1.y();
75 const double k = ( dy * w.x() - dx * w.y() ) / d;
76
77 inter = QgsPoint( p1.x() + v.x() * k, p1.y() + v.y() * k );
78
79 const double lambdav = QgsVector( inter.x() - p1.x(), inter.y() - p1.y() ) * v;
80 if ( lambdav < 0. + 1E-8 || lambdav > vl - 1E-8 )
81 return false;
82
83 const double lambdaw = QgsVector( inter.x() - q1.x(), inter.y() - q1.y() ) * w;
84 return !( lambdaw < 0. + 1E-8 || lambdaw >= wl - 1E-8 );
85}
86
87bool QgsSnapIndex::SegmentSnapItem::getProjection( const QgsPoint &p, QgsPoint &pProj ) const
88{
89 const QgsPoint &s1 = idxFrom->point();
90 const QgsPoint &s2 = idxTo->point();
91 const double nx = s2.y() - s1.y();
92 const double ny = -( s2.x() - s1.x() );
93 const double t = ( p.x() * ny - p.y() * nx - s1.x() * ny + s1.y() * nx ) / ( ( s2.x() - s1.x() ) * ny - ( s2.y() - s1.y() ) * nx );
94 if ( t < 0. || t > 1. )
95 {
96 return false;
97 }
98 pProj = QgsPoint( s1.x() + ( s2.x() - s1.x() ) * t, s1.y() + ( s2.y() - s1.y() ) * t );
99 return true;
100}
101
102bool QgsSnapIndex::SegmentSnapItem::withinSquaredDistance( const QgsPoint &p, const double squaredDistance )
103{
104 double minDistX, minDistY;
105 return QgsGeometryUtilsBase::sqrDistToLine( p.x(), p.y(), idxFrom->point().x(), idxFrom->point().y(), idxTo->point().x(), idxTo->point().y(), minDistX, minDistY, 4 * std::numeric_limits<double>::epsilon() ) <= squaredDistance;
106}
107
109
110QgsSnapIndex::QgsSnapIndex()
111{
112 mSTRTree = GEOSSTRtree_create_r( QgsGeos::getGEOSHandler(), ( size_t )10 );
113}
114
115QgsSnapIndex::~QgsSnapIndex()
116{
117 qDeleteAll( mCoordIdxs );
118 qDeleteAll( mSnapItems );
119
120 GEOSSTRtree_destroy_r( QgsGeos::getGEOSHandler(), mSTRTree );
121}
122
123void QgsSnapIndex::addPoint( const CoordIdx *idx, bool isEndPoint )
124{
125 const QgsPoint p = idx->point();
126
127 GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
128 geos::unique_ptr point( GEOSGeom_createPointFromXY_r( geosctxt, p.x(), p.y() ) );
129
130 PointSnapItem *item = new PointSnapItem( idx, isEndPoint );
131 GEOSSTRtree_insert_r( geosctxt, mSTRTree, point.get(), item );
132 mSnapItems << item;
133}
134
135void QgsSnapIndex::addSegment( const CoordIdx *idxFrom, const CoordIdx *idxTo )
136{
137 const QgsPoint pointFrom = idxFrom->point();
138 const QgsPoint pointTo = idxTo->point();
139
140 GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
141
142 GEOSCoordSequence *coord = GEOSCoordSeq_create_r( geosctxt, 2, 2 );
143 GEOSCoordSeq_setXY_r( geosctxt, coord, 0, pointFrom.x(), pointFrom.y() );
144 GEOSCoordSeq_setXY_r( geosctxt, coord, 1, pointTo.x(), pointTo.y() );
145 geos::unique_ptr segment( GEOSGeom_createLineString_r( geosctxt, coord ) );
146
147 SegmentSnapItem *item = new SegmentSnapItem( idxFrom, idxTo );
148 GEOSSTRtree_insert_r( geosctxt, mSTRTree, segment.get(), item );
149 mSnapItems << item;
150}
151
152void QgsSnapIndex::addGeometry( const QgsAbstractGeometry *geom )
153{
154 for ( int iPart = 0, nParts = geom->partCount(); iPart < nParts; ++iPart )
155 {
156 for ( int iRing = 0, nRings = geom->ringCount( iPart ); iRing < nRings; ++iRing )
157 {
158 int nVerts = geom->vertexCount( iPart, iRing );
159
160 if ( qgsgeometry_cast< const QgsSurface * >( geom ) )
161 nVerts--;
162 else if ( const QgsCurve *curve = qgsgeometry_cast< const QgsCurve * >( geom ) )
163 {
164 if ( curve->isClosed() )
165 nVerts--;
166 }
167
168 for ( int iVert = 0; iVert < nVerts; ++iVert )
169 {
170 CoordIdx *idx = new CoordIdx( geom, QgsVertexId( iPart, iRing, iVert ) );
171 CoordIdx *idx1 = new CoordIdx( geom, QgsVertexId( iPart, iRing, iVert + 1 ) );
172 mCoordIdxs.append( idx );
173 mCoordIdxs.append( idx1 );
174 addPoint( idx, iVert == 0 || iVert == nVerts - 1 );
175 if ( iVert < nVerts - 1 )
176 addSegment( idx, idx1 );
177 }
178 }
179 }
180}
181
183{
184 QList< QgsSnapIndex::SnapItem * > *list;
185};
186
187void _GEOSQueryCallback( void *item, void *userdata )
188{
189 reinterpret_cast<_GEOSQueryCallbackData *>( userdata )->list->append( static_cast<QgsSnapIndex::SnapItem *>( item ) );
190}
191
192QgsPoint QgsSnapIndex::getClosestSnapToPoint( const QgsPoint &startPoint, const QgsPoint &midPoint )
193{
194 GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
195
196 // Look for intersections on segment from the target point to the point opposite to the point reference point
197 // p2 = p1 + 2 * (q - p1)
198 const QgsPoint endPoint( 2 * midPoint.x() - startPoint.x(), 2 * midPoint.y() - startPoint.y() );
199
200 QgsPoint minPoint = startPoint;
201 double minDistance = std::numeric_limits<double>::max();
202
203 GEOSCoordSequence *coord = GEOSCoordSeq_create_r( geosctxt, 2, 2 );
204 GEOSCoordSeq_setXY_r( geosctxt, coord, 0, startPoint.x(), startPoint.y() );
205 GEOSCoordSeq_setXY_r( geosctxt, coord, 1, endPoint.x(), endPoint.y() );
206 geos::unique_ptr searchDiagonal( GEOSGeom_createLineString_r( geosctxt, coord ) );
207
208 QList<SnapItem *> items;
209 struct _GEOSQueryCallbackData callbackData;
210 callbackData.list = &items;
211 GEOSSTRtree_query_r( geosctxt, mSTRTree, searchDiagonal.get(), _GEOSQueryCallback, &callbackData );
212 for ( const SnapItem *item : std::as_const( items ) )
213 {
214 if ( item->type == SnapSegment )
215 {
216 QgsPoint inter;
217 if ( static_cast<const SegmentSnapItem *>( item )->getIntersection( startPoint, endPoint, inter ) )
218 {
219 const double dist = QgsGeometryUtils::sqrDistance2D( midPoint, inter );
220 if ( dist < minDistance )
221 {
222 minDistance = dist;
223 minPoint = inter;
224 }
225 }
226 }
227 }
228
229 return minPoint;
230}
231
232QgsSnapIndex::SnapItem *QgsSnapIndex::getSnapItem( const QgsPoint &pos, const double tolerance, QgsSnapIndex::PointSnapItem **pSnapPoint, QgsSnapIndex::SegmentSnapItem **pSnapSegment, bool endPointOnly ) const
233{
234 GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
235
236 GEOSCoordSequence *coord = GEOSCoordSeq_create_r( geosctxt, 2, 2 );
237 GEOSCoordSeq_setXY_r( geosctxt, coord, 0, pos.x() - tolerance, pos.y() - tolerance );
238 GEOSCoordSeq_setXY_r( geosctxt, coord, 1, pos.x() + tolerance, pos.y() + tolerance );
239
240 geos::unique_ptr searchDiagonal( GEOSGeom_createLineString_r( geosctxt, coord ) );
241
242 QList<SnapItem *> items;
243 struct _GEOSQueryCallbackData callbackData;
244 callbackData.list = &items;
245 GEOSSTRtree_query_r( geosctxt, mSTRTree, searchDiagonal.get(), _GEOSQueryCallback, &callbackData );
246
247 double minDistSegment = std::numeric_limits<double>::max();
248 double minDistPoint = std::numeric_limits<double>::max();
249 QgsSnapIndex::SegmentSnapItem *snapSegment = nullptr;
250 QgsSnapIndex::PointSnapItem *snapPoint = nullptr;
251
252 const double squaredTolerance = tolerance * tolerance;
253 const auto constItems = items;
254 for ( QgsSnapIndex::SnapItem *item : constItems )
255 {
256 if ( ( ! endPointOnly && item->type == SnapPoint ) || item->type == SnapEndPoint )
257 {
258 const double dist = QgsGeometryUtils::sqrDistance2D( item->getSnapPoint( pos ), pos );
259 if ( dist < minDistPoint )
260 {
261 minDistPoint = dist;
262 snapPoint = static_cast<PointSnapItem *>( item );
263 }
264 }
265 else if ( item->type == SnapSegment && !endPointOnly )
266 {
267 if ( !static_cast<SegmentSnapItem *>( item )->withinSquaredDistance( pos, squaredTolerance ) )
268 continue;
269
270 QgsPoint pProj;
271 if ( !static_cast<SegmentSnapItem *>( item )->getProjection( pos, pProj ) )
272 continue;
273
274 const double dist = QgsGeometryUtils::sqrDistance2D( pProj, pos );
275 if ( dist < minDistSegment )
276 {
277 minDistSegment = dist;
278 snapSegment = static_cast<SegmentSnapItem *>( item );
279 }
280 }
281 }
282 snapPoint = minDistPoint < squaredTolerance ? snapPoint : nullptr;
283 snapSegment = minDistSegment < squaredTolerance ? snapSegment : nullptr;
284 if ( pSnapPoint ) *pSnapPoint = snapPoint;
285 if ( pSnapSegment ) *pSnapSegment = snapSegment;
286 return minDistPoint < minDistSegment ? static_cast<QgsSnapIndex::SnapItem *>( snapPoint ) : static_cast<QgsSnapIndex::SnapItem *>( snapSegment );
287}
288
290
291
292//
293// QgsGeometrySnapper
294//
295
297 : mReferenceSource( referenceSource )
298{
299 // Build spatial index
300 mIndex = QgsSpatialIndex( *mReferenceSource );
301}
302
303QgsFeatureList QgsGeometrySnapper::snapFeatures( const QgsFeatureList &features, double snapTolerance, SnapMode mode )
304{
305 QgsFeatureList list = features;
306 QtConcurrent::blockingMap( list, ProcessFeatureWrapper( this, snapTolerance, mode ) );
307 return list;
308}
309
310void QgsGeometrySnapper::processFeature( QgsFeature &feature, double snapTolerance, SnapMode mode )
311{
312 if ( !feature.geometry().isNull() )
313 feature.setGeometry( snapGeometry( feature.geometry(), snapTolerance, mode ) );
314 emit featureSnapped();
315}
316
317QgsGeometry QgsGeometrySnapper::snapGeometry( const QgsGeometry &geometry, double snapTolerance, SnapMode mode ) const
318{
319 // Get potential reference features and construct snap index
320 QList<QgsGeometry> refGeometries;
321 mIndexMutex.lock();
322 QgsRectangle searchBounds = geometry.boundingBox();
323 searchBounds.grow( snapTolerance );
324 const QgsFeatureIds refFeatureIds = qgis::listToSet( mIndex.intersects( searchBounds ) );
325 mIndexMutex.unlock();
326
327 if ( refFeatureIds.isEmpty() )
328 return QgsGeometry( geometry );
329
330 refGeometries.reserve( refFeatureIds.size() );
331 QgsFeatureIds missingFeatureIds;
332 const QgsFeatureIds cachedIds = qgis::listToSet( mCachedReferenceGeometries.keys() );
333 for ( const QgsFeatureId id : refFeatureIds )
334 {
335 if ( cachedIds.contains( id ) )
336 {
337 refGeometries.append( mCachedReferenceGeometries[id] );
338 }
339 else
340 {
341 missingFeatureIds << id;
342 }
343 }
344
345 if ( missingFeatureIds.size() > 0 )
346 {
347
348 mReferenceLayerMutex.lock();
349 const QgsFeatureRequest refFeatureRequest = QgsFeatureRequest().setFilterFids( missingFeatureIds ).setNoAttributes();
350 QgsFeatureIterator refFeatureIt = mReferenceSource->getFeatures( refFeatureRequest );
351 QgsFeature refFeature;
352 while ( refFeatureIt.nextFeature( refFeature ) )
353 {
354 refGeometries.append( refFeature.geometry() );
355 }
356 mReferenceLayerMutex.unlock();
357 }
358
359 return snapGeometry( geometry, snapTolerance, refGeometries, mode );
360}
361
362QgsGeometry QgsGeometrySnapper::snapGeometry( const QgsGeometry &geometry, double snapTolerance, const QList<QgsGeometry> &referenceGeometries, QgsGeometrySnapper::SnapMode mode )
363{
365 ( mode == EndPointPreferClosest || mode == EndPointPreferNodes || mode == EndPointToEndPoint ) )
366 return geometry;
367
368 const QgsPoint center = qgsgeometry_cast< const QgsPoint * >( geometry.constGet() ) ? *static_cast< const QgsPoint * >( geometry.constGet() ) :
369 QgsPoint( geometry.constGet()->boundingBox().center() );
370
371 QgsSnapIndex refSnapIndex;
372 for ( const QgsGeometry &geom : referenceGeometries )
373 {
374 refSnapIndex.addGeometry( geom.constGet() );
375 }
376
377 // Snap geometries
378 QgsAbstractGeometry *subjGeom = geometry.constGet()->clone();
379 QList < QList< QList<PointFlag> > > subjPointFlags;
380
381 // Pass 1: snap vertices of subject geometry to reference vertices
382 for ( int iPart = 0, nParts = subjGeom->partCount(); iPart < nParts; ++iPart )
383 {
384 subjPointFlags.append( QList< QList<PointFlag> >() );
385
386 for ( int iRing = 0, nRings = subjGeom->ringCount( iPart ); iRing < nRings; ++iRing )
387 {
388 subjPointFlags[iPart].append( QList<PointFlag>() );
389
390 for ( int iVert = 0, nVerts = polyLineSize( subjGeom, iPart, iRing ); iVert < nVerts; ++iVert )
391 {
392 if ( ( mode == EndPointPreferClosest || mode == EndPointPreferNodes || mode == EndPointToEndPoint ) &&
393 QgsWkbTypes::geometryType( subjGeom->wkbType() ) == Qgis::GeometryType::Line && ( iVert > 0 && iVert < nVerts - 1 ) )
394 {
395 //endpoint mode and not at an endpoint, skip
396 subjPointFlags[iPart][iRing].append( Unsnapped );
397 continue;
398 }
399
400 QgsSnapIndex::PointSnapItem *snapPoint = nullptr;
401 QgsSnapIndex::SegmentSnapItem *snapSegment = nullptr;
402 const QgsVertexId vidx( iPart, iRing, iVert );
403 const QgsPoint p = subjGeom->vertexAt( vidx );
404 if ( !refSnapIndex.getSnapItem( p, snapTolerance, &snapPoint, &snapSegment, mode == EndPointToEndPoint ) )
405 {
406 subjPointFlags[iPart][iRing].append( Unsnapped );
407 }
408 else
409 {
410 switch ( mode )
411 {
412 case PreferNodes:
416 {
417 // Prefer snapping to point
418 if ( snapPoint )
419 {
420 subjGeom->moveVertex( vidx, snapPoint->getSnapPoint( p ) );
421 subjPointFlags[iPart][iRing].append( SnappedToRefNode );
422 }
423 else if ( snapSegment )
424 {
425 subjGeom->moveVertex( vidx, snapSegment->getSnapPoint( p ) );
426 subjPointFlags[iPart][iRing].append( SnappedToRefSegment );
427 }
428 break;
429 }
430
431 case PreferClosest:
434 {
435 QgsPoint nodeSnap, segmentSnap;
436 double distanceNode = std::numeric_limits<double>::max();
437 double distanceSegment = std::numeric_limits<double>::max();
438 if ( snapPoint )
439 {
440 nodeSnap = snapPoint->getSnapPoint( p );
441 distanceNode = nodeSnap.distanceSquared( p );
442 }
443 if ( snapSegment )
444 {
445 segmentSnap = snapSegment->getSnapPoint( p );
446 distanceSegment = segmentSnap.distanceSquared( p );
447 }
448 if ( snapPoint && distanceNode < distanceSegment )
449 {
450 subjGeom->moveVertex( vidx, nodeSnap );
451 subjPointFlags[iPart][iRing].append( SnappedToRefNode );
452 }
453 else if ( snapSegment )
454 {
455 subjGeom->moveVertex( vidx, segmentSnap );
456 subjPointFlags[iPart][iRing].append( SnappedToRefSegment );
457 }
458 break;
459 }
460 }
461 }
462 }
463 }
464 }
465
466 // no extra vertices to add for point geometry
467 if ( qgsgeometry_cast< const QgsPoint * >( subjGeom ) )
468 return QgsGeometry( subjGeom );
469
470 // nor for no extra vertices modes and end point only snapping
472 {
473 QgsGeometry result( subjGeom );
474 result.removeDuplicateNodes();
475 return result;
476 }
477
478 std::unique_ptr< QgsSnapIndex > subjSnapIndex( new QgsSnapIndex() );
479 subjSnapIndex->addGeometry( subjGeom );
480
481 std::unique_ptr< QgsAbstractGeometry > origSubjGeom( subjGeom->clone() );
482 std::unique_ptr< QgsSnapIndex > origSubjSnapIndex( new QgsSnapIndex() );
483 origSubjSnapIndex->addGeometry( origSubjGeom.get() );
484
485 // Pass 2: add missing vertices to subject geometry
486 for ( const QgsGeometry &refGeom : referenceGeometries )
487 {
488 for ( int iPart = 0, nParts = refGeom.constGet()->partCount(); iPart < nParts; ++iPart )
489 {
490 for ( int iRing = 0, nRings = refGeom.constGet()->ringCount( iPart ); iRing < nRings; ++iRing )
491 {
492 for ( int iVert = 0, nVerts = polyLineSize( refGeom.constGet(), iPart, iRing ); iVert < nVerts; ++iVert )
493 {
494 QgsSnapIndex::PointSnapItem *snapPoint = nullptr;
495 QgsSnapIndex::SegmentSnapItem *snapSegment = nullptr;
496 const QgsPoint point = refGeom.constGet()->vertexAt( QgsVertexId( iPart, iRing, iVert ) );
497 if ( subjSnapIndex->getSnapItem( point, snapTolerance, &snapPoint, &snapSegment ) )
498 {
499 // Snap to segment, unless a subject point was already snapped to the reference point
500 if ( snapPoint )
501 {
502 const QgsPoint snappedPoint = snapPoint->getSnapPoint( point );
503 if ( QgsGeometryUtils::sqrDistance2D( snappedPoint, point ) < 1E-16 )
504 continue;
505 }
506
507 if ( snapSegment )
508 {
509 // Look if there is a closer reference segment, if so, ignore this point
510 const QgsPoint pProj = snapSegment->getSnapPoint( point );
511 const QgsPoint closest = refSnapIndex.getClosestSnapToPoint( point, pProj );
512 if ( QgsGeometryUtils::sqrDistance2D( pProj, point ) > QgsGeometryUtils::sqrDistance2D( pProj, closest ) )
513 {
514 continue;
515 }
516
517 // If we are too far away from the original geometry, do nothing
518 if ( !origSubjSnapIndex->getSnapItem( point, snapTolerance ) )
519 {
520 continue;
521 }
522
523 const QgsSnapIndex::CoordIdx *idx = snapSegment->idxFrom;
524 subjGeom->insertVertex( QgsVertexId( idx->vidx.part, idx->vidx.ring, idx->vidx.vertex + 1 ), point );
525 subjPointFlags[idx->vidx.part][idx->vidx.ring].insert( idx->vidx.vertex + 1, SnappedToRefNode );
526 subjSnapIndex.reset( new QgsSnapIndex() );
527 subjSnapIndex->addGeometry( subjGeom );
528 }
529 }
530 }
531 }
532 }
533 }
534 subjSnapIndex.reset();
535 origSubjSnapIndex.reset();
536 origSubjGeom.reset();
537
538 // Pass 3: remove superfluous vertices: all vertices which are snapped to a segment and not preceded or succeeded by an unsnapped vertex
539 for ( int iPart = 0, nParts = subjGeom->partCount(); iPart < nParts; ++iPart )
540 {
541 for ( int iRing = 0, nRings = subjGeom->ringCount( iPart ); iRing < nRings; ++iRing )
542 {
543 const bool ringIsClosed = subjGeom->vertexAt( QgsVertexId( iPart, iRing, 0 ) ) == subjGeom->vertexAt( QgsVertexId( iPart, iRing, subjGeom->vertexCount( iPart, iRing ) - 1 ) );
544 for ( int iVert = 0, nVerts = polyLineSize( subjGeom, iPart, iRing ); iVert < nVerts; ++iVert )
545 {
546 const int iPrev = ( iVert - 1 + nVerts ) % nVerts;
547 const int iNext = ( iVert + 1 ) % nVerts;
548 const QgsPoint pMid = subjGeom->vertexAt( QgsVertexId( iPart, iRing, iVert ) );
549 const QgsPoint pPrev = subjGeom->vertexAt( QgsVertexId( iPart, iRing, iPrev ) );
550 const QgsPoint pNext = subjGeom->vertexAt( QgsVertexId( iPart, iRing, iNext ) );
551
552 if ( subjPointFlags[iPart][iRing][iVert] == SnappedToRefSegment &&
553 subjPointFlags[iPart][iRing][iPrev] != Unsnapped &&
554 subjPointFlags[iPart][iRing][iNext] != Unsnapped &&
555 QgsGeometryUtils::sqrDistance2D( QgsGeometryUtils::projectPointOnSegment( pMid, pPrev, pNext ), pMid ) < 1E-12 )
556 {
557 if ( ( ringIsClosed && nVerts > 3 ) || ( !ringIsClosed && nVerts > 2 ) )
558 {
559 subjGeom->deleteVertex( QgsVertexId( iPart, iRing, iVert ) );
560 subjPointFlags[iPart][iRing].removeAt( iVert );
561 iVert -= 1;
562 nVerts -= 1;
563 }
564 else
565 {
566 // Don't delete vertices if this would result in a degenerate geometry
567 break;
568 }
569 }
570 }
571 }
572 }
573
574 QgsGeometry result( subjGeom );
575 result.removeDuplicateNodes();
576 return result;
577}
578
579int QgsGeometrySnapper::polyLineSize( const QgsAbstractGeometry *geom, int iPart, int iRing )
580{
581 const int nVerts = geom->vertexCount( iPart, iRing );
582
583 if ( qgsgeometry_cast< const QgsSurface * >( geom ) || qgsgeometry_cast< const QgsMultiSurface * >( geom ) )
584 {
585 const QgsPoint front = geom->vertexAt( QgsVertexId( iPart, iRing, 0 ) );
586 const QgsPoint back = geom->vertexAt( QgsVertexId( iPart, iRing, nVerts - 1 ) );
587 if ( front == back )
588 return nVerts - 1;
589 }
590
591 return nVerts;
592}
593
594
595
596
597
598//
599// QgsInternalGeometrySnapper
600//
601
603 : mSnapTolerance( snapTolerance )
604 , mMode( mode )
605{}
606
608{
609 if ( !feature.hasGeometry() )
610 return QgsGeometry();
611
612 QgsFeature feat = feature;
613 QgsGeometry geometry = feat.geometry();
614 if ( !mFirstFeature )
615 {
616 // snap against processed geometries
617 // Get potential reference features and construct snap index
618 QgsRectangle searchBounds = geometry.boundingBox();
619 searchBounds.grow( mSnapTolerance );
620 const QgsFeatureIds refFeatureIds = qgis::listToSet( mProcessedIndex.intersects( searchBounds ) );
621 if ( !refFeatureIds.isEmpty() )
622 {
623 QList< QgsGeometry > refGeometries;
624 const auto constRefFeatureIds = refFeatureIds;
625 for ( const QgsFeatureId id : constRefFeatureIds )
626 {
627 refGeometries << mProcessedGeometries.value( id );
628 }
629
630 geometry = QgsGeometrySnapper::snapGeometry( geometry, mSnapTolerance, refGeometries, mMode );
631 }
632 }
633 mProcessedGeometries.insert( feat.id(), geometry );
634 mProcessedIndex.addFeature( feat );
635 mFirstFeature = false;
636 return geometry;
637}
638
@ Polygon
Polygons.
Abstract base class for all geometries.
virtual int ringCount(int part=0) const =0
Returns the number of rings of which this geometry is built.
virtual bool moveVertex(QgsVertexId position, const QgsPoint &newPos)=0
Moves a vertex within the geometry.
virtual int vertexCount(int part=0, int ring=0) const =0
Returns the number of vertices of which this geometry is built.
virtual QgsRectangle boundingBox() const
Returns the minimal bounding box for the geometry.
virtual QgsPoint vertexAt(QgsVertexId id) const =0
Returns the point corresponding to a specified vertex id.
Qgis::WkbType wkbType() const
Returns the WKB type of the geometry.
virtual bool insertVertex(QgsVertexId position, const QgsPoint &vertex)=0
Inserts a vertex into the geometry.
virtual int partCount() const =0
Returns count of parts contained in the geometry.
virtual bool deleteVertex(QgsVertexId position)=0
Deletes a vertex within the geometry.
virtual QgsAbstractGeometry * clone() const =0
Clones the geometry by performing a deep copy.
Abstract base class for curved geometry type.
Definition: qgscurve.h:35
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 & setFilterFids(const QgsFeatureIds &fids)
Sets the feature IDs that should be fetched.
QgsFeatureRequest & setNoAttributes()
Set that no attributes will be fetched.
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.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
QgsGeometry geometry
Definition: qgsfeature.h:67
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Definition: qgsfeature.cpp:230
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
Definition: qgsfeature.cpp:167
Q_GADGET QgsFeatureId id
Definition: qgsfeature.h:64
void featureSnapped()
Emitted each time a feature has been processed when calling snapFeatures()
QgsFeatureList snapFeatures(const QgsFeatureList &features, double snapTolerance, SnapMode mode=PreferNodes)
Snaps a set of features to the reference layer and returns the result.
QgsGeometry snapGeometry(const QgsGeometry &geometry, double snapTolerance, SnapMode mode=PreferNodes) const
Snaps a geometry to the reference layer and returns the result.
SnapMode
Snapping modes.
@ EndPointPreferClosest
Only snap start/end points of lines (point features will also be snapped, polygon features will not b...
@ PreferClosestNoExtraVertices
Snap to closest point, regardless of it is a node or a segment. No new nodes will be inserted.
@ EndPointPreferNodes
Only snap start/end points of lines (point features will also be snapped, polygon features will not b...
@ PreferNodes
Prefer to snap to nodes, even when a segment may be closer than a node. New nodes will be inserted to...
@ PreferClosest
Snap to closest point, regardless of it is a node or a segment. New nodes will be inserted to make ge...
@ EndPointToEndPoint
Only snap the start/end points of lines to other start/end points of lines.
@ PreferNodesNoExtraVertices
Prefer to snap to nodes, even when a segment may be closer than a node. No new nodes will be inserted...
QgsGeometrySnapper(QgsFeatureSource *referenceSource)
Constructor for QgsGeometrySnapper.
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.
static QgsPoint projectPointOnSegment(const QgsPoint &p, const QgsPoint &s1, const QgsPoint &s2)
Project the point on a segment.
static Q_DECL_DEPRECATED double sqrDistance2D(double x1, double y1, double x2, double y2)
Returns the squared 2D distance between (x1, y1) and (x2, y2).
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:162
Q_GADGET bool isNull
Definition: qgsgeometry.h:164
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
bool removeDuplicateNodes(double epsilon=4 *std::numeric_limits< double >::epsilon(), bool useZValues=false)
Removes duplicate nodes from the geometry, wherever removing the nodes does not result in a degenerat...
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
Qgis::WkbType wkbType() const
Returns type of the geometry as a WKB type (point / linestring / polygon etc.)
static GEOSContextHandle_t getGEOSHandler()
Definition: qgsgeos.cpp:3576
QgsInternalGeometrySnapper(double snapTolerance, QgsGeometrySnapper::SnapMode mode=QgsGeometrySnapper::PreferNodes)
Constructor for QgsInternalGeometrySnapper.
QgsGeometry snapFeature(const QgsFeature &feature)
Snaps a single feature's geometry against all feature geometries already processed by calls to snapFe...
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:49
Q_GADGET double x
Definition: qgspoint.h:52
QgsPoint vertexAt(QgsVertexId) const override
Returns the point corresponding to a specified vertex id.
Definition: qgspoint.cpp:522
double distanceSquared(double x, double y) const
Returns the Cartesian 2D squared distance between this point a specified x, y coordinate.
Definition: qgspoint.h:415
double y
Definition: qgspoint.h:53
A rectangle specified with double values.
Definition: qgsrectangle.h:42
QgsPointXY center() const
Returns the center point of the rectangle.
Definition: qgsrectangle.h:262
void grow(double delta)
Grows the rectangle in place by the specified amount.
Definition: qgsrectangle.h:307
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.
A class to represent a vector.
Definition: qgsvector.h:30
static Qgis::GeometryType geometryType(Qgis::WkbType type)
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
Definition: qgswkbtypes.h:862
std::unique_ptr< GEOSGeometry, GeosDeleter > unique_ptr
Scoped GEOS pointer.
Definition: qgsgeos.h:73
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:5207
QList< QgsFeature > QgsFeatureList
Definition: qgsfeature.h:917
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:37
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
Definition: qgsfeatureid.h:28
void _GEOSQueryCallback(void *item, void *userdata)
QLineF segment(int index, QRectF rect, double radius)
Utility class for identifying a unique vertex within a geometry.
Definition: qgsvertexid.h:30
QList< const QgsPointCloudLayerProfileResults::PointResult * > * list