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