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