1 /***************************************************************************
2  *  qgsgeometrysnapper.cpp                                                 *
3  *  -------------------                                                    *
4  *  copyright            : (C) 2014 by Sandro Mani / Sourcepole AG         *
5  *  email                : smani@sourcepole.ch                             *
6  ***************************************************************************/
7 
8 /***************************************************************************
9  *                                                                         *
10  *   This program is free software; you can redistribute it and/or modify  *
11  *   it under the terms of the GNU General Public License as published by  *
12  *   the Free Software Foundation; either version 2 of the License, or     *
13  *   (at your option) any later version.                                   *
14  *                                                                         *
15  ***************************************************************************/
16 
17 #include "qgsfeatureiterator.h"
18 #include "qgsgeometry.h"
19 #include "qgsvectorlayer.h"
20 #include "qgsgeometrysnapper.h"
21 #include "qgsvectordataprovider.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 
32 ///@cond PRIVATE
33 
PointSnapItem(const QgsSnapIndex::CoordIdx * _idx,bool isEndPoint)34 QgsSnapIndex::PointSnapItem::PointSnapItem( const QgsSnapIndex::CoordIdx *_idx, bool isEndPoint )
35   : SnapItem( isEndPoint ? QgsSnapIndex::SnapEndPoint : QgsSnapIndex::SnapPoint )
36   , idx( _idx )
37 {}
38 
getSnapPoint(const QgsPoint &) const39 QgsPoint QgsSnapIndex::PointSnapItem::getSnapPoint( const QgsPoint &/*p*/ ) const
40 {
41   return idx->point();
42 }
43 
SegmentSnapItem(const QgsSnapIndex::CoordIdx * _idxFrom,const QgsSnapIndex::CoordIdx * _idxTo)44 QgsSnapIndex::SegmentSnapItem::SegmentSnapItem( const QgsSnapIndex::CoordIdx *_idxFrom, const QgsSnapIndex::CoordIdx *_idxTo )
45   : SnapItem( QgsSnapIndex::SnapSegment )
46   , idxFrom( _idxFrom )
47   , idxTo( _idxTo )
48 {}
49 
getSnapPoint(const QgsPoint & p) const50 QgsPoint QgsSnapIndex::SegmentSnapItem::getSnapPoint( const QgsPoint &p ) const
51 {
52   return QgsGeometryUtils::projectPointOnSegment( p, idxFrom->point(), idxTo->point() );
53 }
54 
getIntersection(const QgsPoint & p1,const QgsPoint & p2,QgsPoint & inter) const55 bool 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 
getProjection(const QgsPoint & p,QgsPoint & pProj)89 bool QgsSnapIndex::SegmentSnapItem::getProjection( const QgsPoint &p, QgsPoint &pProj )
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 
withinDistance(const QgsPoint & p,const double tolerance)104 bool 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 
111 ///////////////////////////////////////////////////////////////////////////////
112 
QgsSnapIndex()113 QgsSnapIndex::QgsSnapIndex()
114 {
115   mSTRTree = GEOSSTRtree_create_r( QgsGeos::getGEOSHandler(), ( size_t )10 );
116 }
117 
~QgsSnapIndex()118 QgsSnapIndex::~QgsSnapIndex()
119 {
120   qDeleteAll( mCoordIdxs );
121   qDeleteAll( mSnapItems );
122 
123   GEOSSTRtree_destroy_r( QgsGeos::getGEOSHandler(), mSTRTree );
124 }
125 
addPoint(const CoordIdx * idx,bool isEndPoint)126 void QgsSnapIndex::addPoint( const CoordIdx *idx, bool isEndPoint )
127 {
128   const QgsPoint p = idx->point();
129 
130   GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
131 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR>=8
132   geos::unique_ptr point( GEOSGeom_createPointFromXY_r( geosctxt, p.x(), p.y() ) );
133 #else
134   GEOSCoordSequence *seq = GEOSCoordSeq_create_r( geosctxt, 1, 2 );
135   GEOSCoordSeq_setX_r( geosctxt, seq, 0, p.x() );
136   GEOSCoordSeq_setY_r( geosctxt, seq, 0, p.y() );
137   geos::unique_ptr point( GEOSGeom_createPoint_r( geosctxt, seq ) );
138 #endif
139 
140   PointSnapItem *item = new PointSnapItem( idx, isEndPoint );
141   GEOSSTRtree_insert_r( geosctxt, mSTRTree, point.get(), item );
142 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR<9
143   mSTRTreeItems.emplace_back( std::move( point ) );
144 #endif
145   mSnapItems << item;
146 }
147 
addSegment(const CoordIdx * idxFrom,const CoordIdx * idxTo)148 void QgsSnapIndex::addSegment( const CoordIdx *idxFrom, const CoordIdx *idxTo )
149 {
150   const QgsPoint pointFrom = idxFrom->point();
151   const QgsPoint pointTo = idxTo->point();
152 
153   GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
154 
155   GEOSCoordSequence *coord = GEOSCoordSeq_create_r( geosctxt, 2, 2 );
156 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR>=8
157   GEOSCoordSeq_setXY_r( geosctxt, coord, 0, pointFrom.x(), pointFrom.y() );
158   GEOSCoordSeq_setXY_r( geosctxt, coord, 1, pointTo.x(), pointTo.y() );
159 #else
160   GEOSCoordSeq_setX_r( geosctxt, coord, 0, pointFrom.x() );
161   GEOSCoordSeq_setY_r( geosctxt, coord, 0, pointFrom.y() );
162   GEOSCoordSeq_setX_r( geosctxt, coord, 1, pointTo.x() );
163   GEOSCoordSeq_setY_r( geosctxt, coord, 1, pointTo.y() );
164 #endif
165   geos::unique_ptr segment( GEOSGeom_createLineString_r( geosctxt, coord ) );
166 
167   SegmentSnapItem *item = new SegmentSnapItem( idxFrom, idxTo );
168   GEOSSTRtree_insert_r( geosctxt, mSTRTree, segment.get(), item );
169 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR<9
170   mSTRTreeItems.push_back( std::move( segment ) );
171 #endif
172   mSnapItems << item;
173 }
174 
addGeometry(const QgsAbstractGeometry * geom)175 void QgsSnapIndex::addGeometry( const QgsAbstractGeometry *geom )
176 {
177   for ( int iPart = 0, nParts = geom->partCount(); iPart < nParts; ++iPart )
178   {
179     for ( int iRing = 0, nRings = geom->ringCount( iPart ); iRing < nRings; ++iRing )
180     {
181       int nVerts = geom->vertexCount( iPart, iRing );
182 
183       if ( qgsgeometry_cast< const QgsSurface * >( geom ) )
184         nVerts--;
185       else if ( const QgsCurve *curve = qgsgeometry_cast< const QgsCurve * >( geom ) )
186       {
187         if ( curve->isClosed() )
188           nVerts--;
189       }
190 
191       for ( int iVert = 0; iVert < nVerts; ++iVert )
192       {
193         CoordIdx *idx = new CoordIdx( geom, QgsVertexId( iPart, iRing, iVert ) );
194         CoordIdx *idx1 = new CoordIdx( geom, QgsVertexId( iPart, iRing, iVert + 1 ) );
195         mCoordIdxs.append( idx );
196         mCoordIdxs.append( idx1 );
197         addPoint( idx, iVert == 0 || iVert == nVerts - 1 );
198         if ( iVert < nVerts - 1 )
199           addSegment( idx, idx1 );
200       }
201     }
202   }
203 }
204 
205 struct _GEOSQueryCallbackData
206 {
207   QList< QgsSnapIndex::SnapItem * > *list;
208 };
209 
_GEOSQueryCallback(void * item,void * userdata)210 void _GEOSQueryCallback( void *item, void *userdata )
211 {
212   reinterpret_cast<_GEOSQueryCallbackData *>( userdata )->list->append( static_cast<QgsSnapIndex::SnapItem *>( item ) );
213 }
214 
getClosestSnapToPoint(const QgsPoint & startPoint,const QgsPoint & midPoint)215 QgsPoint QgsSnapIndex::getClosestSnapToPoint( const QgsPoint &startPoint, const QgsPoint &midPoint )
216 {
217   GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
218 
219   // Look for intersections on segment from the target point to the point opposite to the point reference point
220   // p2 = p1 + 2 * (q - p1)
221   const QgsPoint endPoint( 2 * midPoint.x() - startPoint.x(), 2 * midPoint.y() - startPoint.y() );
222 
223   QgsPoint minPoint = startPoint;
224   double minDistance = std::numeric_limits<double>::max();
225 
226   GEOSCoordSequence *coord = GEOSCoordSeq_create_r( geosctxt, 2, 2 );
227 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR>=8
228   GEOSCoordSeq_setXY_r( geosctxt, coord, 0, startPoint.x(), startPoint.y() );
229   GEOSCoordSeq_setXY_r( geosctxt, coord, 1, endPoint.x(), endPoint.y() );
230 #else
231   GEOSCoordSeq_setX_r( geosctxt, coord, 0, startPoint.x() );
232   GEOSCoordSeq_setY_r( geosctxt, coord, 0, startPoint.y() );
233   GEOSCoordSeq_setX_r( geosctxt, coord, 1, endPoint.x() );
234   GEOSCoordSeq_setY_r( geosctxt, coord, 1, endPoint.y() );
235 #endif
236   geos::unique_ptr searchDiagonal( GEOSGeom_createLineString_r( geosctxt, coord ) );
237 
238   QList<SnapItem *> items;
239   struct _GEOSQueryCallbackData callbackData;
240   callbackData.list = &items;
241   GEOSSTRtree_query_r( geosctxt, mSTRTree, searchDiagonal.get(), _GEOSQueryCallback, &callbackData );
242   for ( const SnapItem *item : std::as_const( items ) )
243   {
244     if ( item->type == SnapSegment )
245     {
246       QgsPoint inter;
247       if ( static_cast<const SegmentSnapItem *>( item )->getIntersection( startPoint, endPoint, inter ) )
248       {
249         const double dist = QgsGeometryUtils::sqrDistance2D( midPoint, inter );
250         if ( dist < minDistance )
251         {
252           minDistance = dist;
253           minPoint = inter;
254         }
255       }
256     }
257   }
258 
259   return minPoint;
260 }
261 
getSnapItem(const QgsPoint & pos,const double tolerance,QgsSnapIndex::PointSnapItem ** pSnapPoint,QgsSnapIndex::SegmentSnapItem ** pSnapSegment,bool endPointOnly) const262 QgsSnapIndex::SnapItem *QgsSnapIndex::getSnapItem( const QgsPoint &pos, const double tolerance, QgsSnapIndex::PointSnapItem **pSnapPoint, QgsSnapIndex::SegmentSnapItem **pSnapSegment, bool endPointOnly ) const
263 {
264   GEOSContextHandle_t geosctxt = QgsGeos::getGEOSHandler();
265 
266   GEOSCoordSequence *coord = GEOSCoordSeq_create_r( geosctxt, 2, 2 );
267 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR>=8
268   GEOSCoordSeq_setXY_r( geosctxt, coord, 0, pos.x() - tolerance, pos.y() - tolerance );
269   GEOSCoordSeq_setXY_r( geosctxt, coord, 1, pos.x() + tolerance, pos.y() + tolerance );
270 #else
271   GEOSCoordSeq_setX_r( geosctxt, coord, 0, pos.x() - tolerance );
272   GEOSCoordSeq_setY_r( geosctxt, coord, 0, pos.y() - tolerance );
273   GEOSCoordSeq_setX_r( geosctxt, coord, 1, pos.x() + tolerance );
274   GEOSCoordSeq_setY_r( geosctxt, coord, 1, pos.y() + tolerance );
275 #endif
276 
277   geos::unique_ptr searchDiagonal( GEOSGeom_createLineString_r( geosctxt, coord ) );
278 
279   QList<SnapItem *> items;
280   struct _GEOSQueryCallbackData callbackData;
281   callbackData.list = &items;
282   GEOSSTRtree_query_r( geosctxt, mSTRTree, searchDiagonal.get(), _GEOSQueryCallback, &callbackData );
283 
284   double minDistSegment = std::numeric_limits<double>::max();
285   double minDistPoint = std::numeric_limits<double>::max();
286   QgsSnapIndex::SegmentSnapItem *snapSegment = nullptr;
287   QgsSnapIndex::PointSnapItem *snapPoint = nullptr;
288 
289   const auto constItems = items;
290   for ( QgsSnapIndex::SnapItem *item : constItems )
291   {
292     if ( ( ! endPointOnly && item->type == SnapPoint ) || item->type == SnapEndPoint )
293     {
294       const double dist = QgsGeometryUtils::sqrDistance2D( item->getSnapPoint( pos ), pos );
295       if ( dist < minDistPoint )
296       {
297         minDistPoint = dist;
298         snapPoint = static_cast<PointSnapItem *>( item );
299       }
300     }
301     else if ( item->type == SnapSegment && !endPointOnly )
302     {
303       if ( !static_cast<SegmentSnapItem *>( item )->withinDistance( pos, tolerance ) )
304         continue;
305 
306       QgsPoint pProj;
307       if ( !static_cast<SegmentSnapItem *>( item )->getProjection( pos, pProj ) )
308         continue;
309 
310       const double dist = QgsGeometryUtils::sqrDistance2D( pProj, pos );
311       if ( dist < minDistSegment )
312       {
313         minDistSegment = dist;
314         snapSegment = static_cast<SegmentSnapItem *>( item );
315       }
316     }
317   }
318   snapPoint = minDistPoint < tolerance * tolerance ? snapPoint : nullptr;
319   snapSegment = minDistSegment < tolerance * tolerance ? snapSegment : nullptr;
320   if ( pSnapPoint ) *pSnapPoint = snapPoint;
321   if ( pSnapSegment ) *pSnapSegment = snapSegment;
322   return minDistPoint < minDistSegment ? static_cast<QgsSnapIndex::SnapItem *>( snapPoint ) : static_cast<QgsSnapIndex::SnapItem *>( snapSegment );
323 }
324 
325 /// @endcond
326 
327 
328 //
329 // QgsGeometrySnapper
330 //
331 
QgsGeometrySnapper(QgsFeatureSource * referenceSource)332 QgsGeometrySnapper::QgsGeometrySnapper( QgsFeatureSource *referenceSource )
333   : mReferenceSource( referenceSource )
334 {
335   // Build spatial index
336   mIndex = QgsSpatialIndex( *mReferenceSource );
337 }
338 
snapFeatures(const QgsFeatureList & features,double snapTolerance,SnapMode mode)339 QgsFeatureList QgsGeometrySnapper::snapFeatures( const QgsFeatureList &features, double snapTolerance, SnapMode mode )
340 {
341   QgsFeatureList list = features;
342   QtConcurrent::blockingMap( list, ProcessFeatureWrapper( this, snapTolerance, mode ) );
343   return list;
344 }
345 
processFeature(QgsFeature & feature,double snapTolerance,SnapMode mode)346 void QgsGeometrySnapper::processFeature( QgsFeature &feature, double snapTolerance, SnapMode mode )
347 {
348   if ( !feature.geometry().isNull() )
349     feature.setGeometry( snapGeometry( feature.geometry(), snapTolerance, mode ) );
350   emit featureSnapped();
351 }
352 
snapGeometry(const QgsGeometry & geometry,double snapTolerance,SnapMode mode) const353 QgsGeometry QgsGeometrySnapper::snapGeometry( const QgsGeometry &geometry, double snapTolerance, SnapMode mode ) const
354 {
355   // Get potential reference features and construct snap index
356   QList<QgsGeometry> refGeometries;
357   mIndexMutex.lock();
358   QgsRectangle searchBounds = geometry.boundingBox();
359   searchBounds.grow( snapTolerance );
360   const QgsFeatureIds refFeatureIds = qgis::listToSet( mIndex.intersects( searchBounds ) );
361   mIndexMutex.unlock();
362 
363   if ( refFeatureIds.isEmpty() )
364     return QgsGeometry( geometry );
365 
366   refGeometries.reserve( refFeatureIds.size() );
367   QgsFeatureIds missingFeatureIds;
368   const QgsFeatureIds cachedIds = qgis::listToSet( mCachedReferenceGeometries.keys() );
369   for ( const QgsFeatureId id : refFeatureIds )
370   {
371     if ( cachedIds.contains( id ) )
372     {
373       refGeometries.append( mCachedReferenceGeometries[id] );
374     }
375     else
376     {
377       missingFeatureIds << id;
378     }
379   }
380 
381   if ( missingFeatureIds.size() > 0 )
382   {
383 
384     mReferenceLayerMutex.lock();
385     const QgsFeatureRequest refFeatureRequest = QgsFeatureRequest().setFilterFids( missingFeatureIds ).setNoAttributes();
386     QgsFeatureIterator refFeatureIt = mReferenceSource->getFeatures( refFeatureRequest );
387     QgsFeature refFeature;
388     while ( refFeatureIt.nextFeature( refFeature ) )
389     {
390       refGeometries.append( refFeature.geometry() );
391     }
392     mReferenceLayerMutex.unlock();
393   }
394 
395   return snapGeometry( geometry, snapTolerance, refGeometries, mode );
396 }
397 
snapGeometry(const QgsGeometry & geometry,double snapTolerance,const QList<QgsGeometry> & referenceGeometries,QgsGeometrySnapper::SnapMode mode)398 QgsGeometry QgsGeometrySnapper::snapGeometry( const QgsGeometry &geometry, double snapTolerance, const QList<QgsGeometry> &referenceGeometries, QgsGeometrySnapper::SnapMode mode )
399 {
400   if ( QgsWkbTypes::geometryType( geometry.wkbType() ) == QgsWkbTypes::PolygonGeometry &&
401        ( mode == EndPointPreferClosest || mode == EndPointPreferNodes || mode == EndPointToEndPoint ) )
402     return geometry;
403 
404   const QgsPoint center = qgsgeometry_cast< const QgsPoint * >( geometry.constGet() ) ? *static_cast< const QgsPoint * >( geometry.constGet() ) :
405                           QgsPoint( geometry.constGet()->boundingBox().center() );
406 
407   QgsSnapIndex refSnapIndex;
408   for ( const QgsGeometry &geom : referenceGeometries )
409   {
410     refSnapIndex.addGeometry( geom.constGet() );
411   }
412 
413   // Snap geometries
414   QgsAbstractGeometry *subjGeom = geometry.constGet()->clone();
415   QList < QList< QList<PointFlag> > > subjPointFlags;
416 
417   // Pass 1: snap vertices of subject geometry to reference vertices
418   for ( int iPart = 0, nParts = subjGeom->partCount(); iPart < nParts; ++iPart )
419   {
420     subjPointFlags.append( QList< QList<PointFlag> >() );
421 
422     for ( int iRing = 0, nRings = subjGeom->ringCount( iPart ); iRing < nRings; ++iRing )
423     {
424       subjPointFlags[iPart].append( QList<PointFlag>() );
425 
426       for ( int iVert = 0, nVerts = polyLineSize( subjGeom, iPart, iRing ); iVert < nVerts; ++iVert )
427       {
428         if ( ( mode == EndPointPreferClosest || mode == EndPointPreferNodes || mode == EndPointToEndPoint ) &&
429              QgsWkbTypes::geometryType( subjGeom->wkbType() ) == QgsWkbTypes::LineGeometry && ( iVert > 0 && iVert < nVerts - 1 ) )
430         {
431           //endpoint mode and not at an endpoint, skip
432           subjPointFlags[iPart][iRing].append( Unsnapped );
433           continue;
434         }
435 
436         QgsSnapIndex::PointSnapItem *snapPoint = nullptr;
437         QgsSnapIndex::SegmentSnapItem *snapSegment = nullptr;
438         const QgsVertexId vidx( iPart, iRing, iVert );
439         const QgsPoint p = subjGeom->vertexAt( vidx );
440         if ( !refSnapIndex.getSnapItem( p, snapTolerance, &snapPoint, &snapSegment, mode == EndPointToEndPoint ) )
441         {
442           subjPointFlags[iPart][iRing].append( Unsnapped );
443         }
444         else
445         {
446           switch ( mode )
447           {
448             case PreferNodes:
449             case PreferNodesNoExtraVertices:
450             case EndPointPreferNodes:
451             case EndPointToEndPoint:
452             {
453               // Prefer snapping to point
454               if ( snapPoint )
455               {
456                 subjGeom->moveVertex( vidx, snapPoint->getSnapPoint( p ) );
457                 subjPointFlags[iPart][iRing].append( SnappedToRefNode );
458               }
459               else if ( snapSegment )
460               {
461                 subjGeom->moveVertex( vidx, snapSegment->getSnapPoint( p ) );
462                 subjPointFlags[iPart][iRing].append( SnappedToRefSegment );
463               }
464               break;
465             }
466 
467             case PreferClosest:
468             case PreferClosestNoExtraVertices:
469             case EndPointPreferClosest:
470             {
471               QgsPoint nodeSnap, segmentSnap;
472               double distanceNode = std::numeric_limits<double>::max();
473               double distanceSegment = std::numeric_limits<double>::max();
474               if ( snapPoint )
475               {
476                 nodeSnap = snapPoint->getSnapPoint( p );
477                 distanceNode = nodeSnap.distanceSquared( p );
478               }
479               if ( snapSegment )
480               {
481                 segmentSnap = snapSegment->getSnapPoint( p );
482                 distanceSegment = segmentSnap.distanceSquared( p );
483               }
484               if ( snapPoint && distanceNode < distanceSegment )
485               {
486                 subjGeom->moveVertex( vidx, nodeSnap );
487                 subjPointFlags[iPart][iRing].append( SnappedToRefNode );
488               }
489               else if ( snapSegment )
490               {
491                 subjGeom->moveVertex( vidx, segmentSnap );
492                 subjPointFlags[iPart][iRing].append( SnappedToRefSegment );
493               }
494               break;
495             }
496           }
497         }
498       }
499     }
500   }
501 
502   // no extra vertices to add for point geometry
503   if ( qgsgeometry_cast< const QgsPoint * >( subjGeom ) )
504     return QgsGeometry( subjGeom );
505 
506   // nor for no extra vertices modes and end point only snapping
507   if ( mode == PreferClosestNoExtraVertices || mode == PreferNodesNoExtraVertices || mode == EndPointPreferClosest || mode == EndPointPreferNodes || mode == EndPointToEndPoint )
508   {
509     QgsGeometry result( subjGeom );
510     result.removeDuplicateNodes();
511     return result;
512   }
513 
514   std::unique_ptr< QgsSnapIndex > subjSnapIndex( new QgsSnapIndex() );
515   subjSnapIndex->addGeometry( subjGeom );
516 
517   std::unique_ptr< QgsAbstractGeometry > origSubjGeom( subjGeom->clone() );
518   std::unique_ptr< QgsSnapIndex > origSubjSnapIndex( new QgsSnapIndex() );
519   origSubjSnapIndex->addGeometry( origSubjGeom.get() );
520 
521   // Pass 2: add missing vertices to subject geometry
522   for ( const QgsGeometry &refGeom : referenceGeometries )
523   {
524     for ( int iPart = 0, nParts = refGeom.constGet()->partCount(); iPart < nParts; ++iPart )
525     {
526       for ( int iRing = 0, nRings = refGeom.constGet()->ringCount( iPart ); iRing < nRings; ++iRing )
527       {
528         for ( int iVert = 0, nVerts = polyLineSize( refGeom.constGet(), iPart, iRing ); iVert < nVerts; ++iVert )
529         {
530           QgsSnapIndex::PointSnapItem *snapPoint = nullptr;
531           QgsSnapIndex::SegmentSnapItem *snapSegment = nullptr;
532           const QgsPoint point = refGeom.constGet()->vertexAt( QgsVertexId( iPart, iRing, iVert ) );
533           if ( subjSnapIndex->getSnapItem( point, snapTolerance, &snapPoint, &snapSegment ) )
534           {
535             // Snap to segment, unless a subject point was already snapped to the reference point
536             if ( snapPoint )
537             {
538               const QgsPoint snappedPoint = snapPoint->getSnapPoint( point );
539               if ( QgsGeometryUtils::sqrDistance2D( snappedPoint, point ) < 1E-16 )
540                 continue;
541             }
542 
543             if ( snapSegment )
544             {
545               // Look if there is a closer reference segment, if so, ignore this point
546               const QgsPoint pProj = snapSegment->getSnapPoint( point );
547               const QgsPoint closest = refSnapIndex.getClosestSnapToPoint( point, pProj );
548               if ( QgsGeometryUtils::sqrDistance2D( pProj, point ) > QgsGeometryUtils::sqrDistance2D( pProj, closest ) )
549               {
550                 continue;
551               }
552 
553               // If we are too far away from the original geometry, do nothing
554               if ( !origSubjSnapIndex->getSnapItem( point, snapTolerance ) )
555               {
556                 continue;
557               }
558 
559               const QgsSnapIndex::CoordIdx *idx = snapSegment->idxFrom;
560               subjGeom->insertVertex( QgsVertexId( idx->vidx.part, idx->vidx.ring, idx->vidx.vertex + 1 ), point );
561               subjPointFlags[idx->vidx.part][idx->vidx.ring].insert( idx->vidx.vertex + 1, SnappedToRefNode );
562               subjSnapIndex.reset( new QgsSnapIndex() );
563               subjSnapIndex->addGeometry( subjGeom );
564             }
565           }
566         }
567       }
568     }
569   }
570   subjSnapIndex.reset();
571   origSubjSnapIndex.reset();
572   origSubjGeom.reset();
573 
574   // Pass 3: remove superfluous vertices: all vertices which are snapped to a segment and not preceded or succeeded by an unsnapped vertex
575   for ( int iPart = 0, nParts = subjGeom->partCount(); iPart < nParts; ++iPart )
576   {
577     for ( int iRing = 0, nRings = subjGeom->ringCount( iPart ); iRing < nRings; ++iRing )
578     {
579       const bool ringIsClosed = subjGeom->vertexAt( QgsVertexId( iPart, iRing, 0 ) ) == subjGeom->vertexAt( QgsVertexId( iPart, iRing, subjGeom->vertexCount( iPart, iRing ) - 1 ) );
580       for ( int iVert = 0, nVerts = polyLineSize( subjGeom, iPart, iRing ); iVert < nVerts; ++iVert )
581       {
582         const int iPrev = ( iVert - 1 + nVerts ) % nVerts;
583         const int iNext = ( iVert + 1 ) % nVerts;
584         const QgsPoint pMid = subjGeom->vertexAt( QgsVertexId( iPart, iRing, iVert ) );
585         const QgsPoint pPrev = subjGeom->vertexAt( QgsVertexId( iPart, iRing, iPrev ) );
586         const QgsPoint pNext = subjGeom->vertexAt( QgsVertexId( iPart, iRing, iNext ) );
587 
588         if ( subjPointFlags[iPart][iRing][iVert] == SnappedToRefSegment &&
589              subjPointFlags[iPart][iRing][iPrev] != Unsnapped &&
590              subjPointFlags[iPart][iRing][iNext] != Unsnapped &&
591              QgsGeometryUtils::sqrDistance2D( QgsGeometryUtils::projectPointOnSegment( pMid, pPrev, pNext ), pMid ) < 1E-12 )
592         {
593           if ( ( ringIsClosed && nVerts > 3 ) || ( !ringIsClosed && nVerts > 2 ) )
594           {
595             subjGeom->deleteVertex( QgsVertexId( iPart, iRing, iVert ) );
596             subjPointFlags[iPart][iRing].removeAt( iVert );
597             iVert -= 1;
598             nVerts -= 1;
599           }
600           else
601           {
602             // Don't delete vertices if this would result in a degenerate geometry
603             break;
604           }
605         }
606       }
607     }
608   }
609 
610   QgsGeometry result( subjGeom );
611   result.removeDuplicateNodes();
612   return result;
613 }
614 
polyLineSize(const QgsAbstractGeometry * geom,int iPart,int iRing)615 int QgsGeometrySnapper::polyLineSize( const QgsAbstractGeometry *geom, int iPart, int iRing )
616 {
617   const int nVerts = geom->vertexCount( iPart, iRing );
618 
619   if ( qgsgeometry_cast< const QgsSurface * >( geom ) || qgsgeometry_cast< const QgsMultiSurface * >( geom ) )
620   {
621     const QgsPoint front = geom->vertexAt( QgsVertexId( iPart, iRing, 0 ) );
622     const QgsPoint back = geom->vertexAt( QgsVertexId( iPart, iRing, nVerts - 1 ) );
623     if ( front == back )
624       return nVerts - 1;
625   }
626 
627   return nVerts;
628 }
629 
630 
631 
632 
633 
634 //
635 // QgsInternalGeometrySnapper
636 //
637 
QgsInternalGeometrySnapper(double snapTolerance,QgsGeometrySnapper::SnapMode mode)638 QgsInternalGeometrySnapper::QgsInternalGeometrySnapper( double snapTolerance, QgsGeometrySnapper::SnapMode mode )
639   : mSnapTolerance( snapTolerance )
640   , mMode( mode )
641 {}
642 
snapFeature(const QgsFeature & feature)643 QgsGeometry QgsInternalGeometrySnapper::snapFeature( const QgsFeature &feature )
644 {
645   if ( !feature.hasGeometry() )
646     return QgsGeometry();
647 
648   QgsFeature feat = feature;
649   QgsGeometry geometry = feat.geometry();
650   if ( !mFirstFeature )
651   {
652     // snap against processed geometries
653     // Get potential reference features and construct snap index
654     QgsRectangle searchBounds = geometry.boundingBox();
655     searchBounds.grow( mSnapTolerance );
656     const QgsFeatureIds refFeatureIds = qgis::listToSet( mProcessedIndex.intersects( searchBounds ) );
657     if ( !refFeatureIds.isEmpty() )
658     {
659       QList< QgsGeometry > refGeometries;
660       const auto constRefFeatureIds = refFeatureIds;
661       for ( const QgsFeatureId id : constRefFeatureIds )
662       {
663         refGeometries << mProcessedGeometries.value( id );
664       }
665 
666       geometry = QgsGeometrySnapper::snapGeometry( geometry, mSnapTolerance, refGeometries, mMode );
667     }
668   }
669   mProcessedGeometries.insert( feat.id(), geometry );
670   mProcessedIndex.addFeature( feat );
671   mFirstFeature = false;
672   return geometry;
673 }
674 
675