31#include <QtConcurrentMap>
33#include "moc_qgsgeometrysnapper.cpp"
37QgsSnapIndex::PointSnapItem::PointSnapItem(
const QgsSnapIndex::CoordIdx *_idx,
bool isEndPoint )
38 : SnapItem( isEndPoint ? QgsSnapIndex::SnapEndPoint : QgsSnapIndex::SnapPoint )
47QgsSnapIndex::SegmentSnapItem::SegmentSnapItem(
const QgsSnapIndex::CoordIdx *_idxFrom,
const QgsSnapIndex::CoordIdx *_idxTo )
48 : SnapItem( QgsSnapIndex::SnapSegment )
53QgsPoint QgsSnapIndex::SegmentSnapItem::getSnapPoint(
const QgsPoint &p )
const
60 const QgsPoint &q1 = idxFrom->point(), &q2 = idxTo->point();
63 const double vl = v.length();
64 const double wl = w.length();
73 const double d = v.y() * w.x() - v.x() * w.y();
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;
82 inter =
QgsPoint( p1.
x() + v.x() * k, p1.
y() + v.y() * k );
84 const double lambdav =
QgsVector( inter.
x() - p1.
x(), inter.
y() - p1.
y() ) * v;
85 if ( lambdav < 0. + 1E-8 || lambdav > vl - 1E-8 )
88 const double lambdaw =
QgsVector( inter.
x() - q1.
x(), inter.
y() - q1.
y() ) * w;
89 return !( lambdaw < 0. + 1E-8 || lambdaw >= wl - 1E-8 );
92bool QgsSnapIndex::SegmentSnapItem::getProjection(
const QgsPoint &p,
QgsPoint &pProj )
const
94 const QgsPoint &s1 = idxFrom->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. )
103 pProj =
QgsPoint( s1.
x() + ( s2.
x() - s1.
x() ) * t, s1.
y() + ( s2.
y() - s1.
y() ) * t );
107bool QgsSnapIndex::SegmentSnapItem::withinSquaredDistance(
const QgsPoint &p,
const double squaredDistance )
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() ) <= squaredDistance;
115QgsSnapIndex::QgsSnapIndex()
120QgsSnapIndex::~QgsSnapIndex()
122 qDeleteAll( mCoordIdxs );
123 qDeleteAll( mSnapItems );
128void QgsSnapIndex::addPoint(
const CoordIdx *idx,
bool isEndPoint )
135 PointSnapItem *item =
new PointSnapItem( idx, isEndPoint );
136 GEOSSTRtree_insert_r( geosctxt, mSTRTree, point.get(), item );
140void QgsSnapIndex::addSegment(
const CoordIdx *idxFrom,
const CoordIdx *idxTo )
142 const QgsPoint pointFrom = idxFrom->point();
143 const QgsPoint pointTo = idxTo->point();
147 GEOSCoordSequence *coord = GEOSCoordSeq_create_r( geosctxt, 2, 2 );
148 GEOSCoordSeq_setXY_r( geosctxt, coord, 0, pointFrom.
x(), pointFrom.
y() );
149 GEOSCoordSeq_setXY_r( geosctxt, coord, 1, pointTo.
x(), pointTo.
y() );
152 SegmentSnapItem *item =
new SegmentSnapItem( idxFrom, idxTo );
153 GEOSSTRtree_insert_r( geosctxt, mSTRTree,
segment.get(), item );
159 for (
int iPart = 0, nParts = geom->
partCount(); iPart < nParts; ++iPart )
161 for (
int iRing = 0, nRings = geom->
ringCount( iPart ); iRing < nRings; ++iRing )
169 if ( curve->isClosed() )
173 for (
int iVert = 0; iVert < nVerts; ++iVert )
175 CoordIdx *idx =
new CoordIdx( geom,
QgsVertexId( iPart, iRing, iVert ) );
176 CoordIdx *idx1 =
new CoordIdx( geom,
QgsVertexId( iPart, iRing, iVert + 1 ) );
177 mCoordIdxs.append( idx );
178 mCoordIdxs.append( idx1 );
179 addPoint( idx, iVert == 0 || iVert == nVerts - 1 );
180 if ( iVert < nVerts - 1 )
181 addSegment( idx, idx1 );
189 QList<QgsSnapIndex::SnapItem *> *
list;
194 reinterpret_cast<_GEOSQueryCallbackData *
>( userdata )->list->append(
static_cast<QgsSnapIndex::SnapItem *
>( item ) );
203 const QgsPoint endPoint( 2 * midPoint.
x() - startPoint.
x(), 2 * midPoint.
y() - startPoint.
y() );
206 double minDistance = std::numeric_limits<double>::max();
208 GEOSCoordSequence *coord = GEOSCoordSeq_create_r( geosctxt, 2, 2 );
209 GEOSCoordSeq_setXY_r( geosctxt, coord, 0, startPoint.
x(), startPoint.
y() );
210 GEOSCoordSeq_setXY_r( geosctxt, coord, 1, endPoint.x(), endPoint.y() );
211 geos::unique_ptr searchDiagonal( GEOSGeom_createLineString_r( geosctxt, coord ) );
213 QList<SnapItem *> items;
215 callbackData.
list = &items;
216 GEOSSTRtree_query_r( geosctxt, mSTRTree, searchDiagonal.get(),
_GEOSQueryCallback, &callbackData );
217 for (
const SnapItem *item : std::as_const( items ) )
219 if ( item->type == SnapSegment )
222 if (
static_cast<const SegmentSnapItem *
>( item )->getIntersection( startPoint, endPoint, inter ) )
225 if ( dist < minDistance )
237QgsSnapIndex::SnapItem *QgsSnapIndex::getSnapItem(
const QgsPoint &pos,
const double tolerance, QgsSnapIndex::PointSnapItem **pSnapPoint, QgsSnapIndex::SegmentSnapItem **pSnapSegment,
bool endPointOnly )
const
241 GEOSCoordSequence *coord = GEOSCoordSeq_create_r( geosctxt, 2, 2 );
242 GEOSCoordSeq_setXY_r( geosctxt, coord, 0, pos.
x() - tolerance, pos.
y() - tolerance );
243 GEOSCoordSeq_setXY_r( geosctxt, coord, 1, pos.
x() + tolerance, pos.
y() + tolerance );
245 geos::unique_ptr searchDiagonal( GEOSGeom_createLineString_r( geosctxt, coord ) );
247 QList<SnapItem *> items;
249 callbackData.
list = &items;
250 GEOSSTRtree_query_r( geosctxt, mSTRTree, searchDiagonal.get(),
_GEOSQueryCallback, &callbackData );
252 double minDistSegment = std::numeric_limits<double>::max();
253 double minDistPoint = std::numeric_limits<double>::max();
254 QgsSnapIndex::SegmentSnapItem *snapSegment =
nullptr;
255 QgsSnapIndex::PointSnapItem *snapPoint =
nullptr;
257 const double squaredTolerance = tolerance * tolerance;
258 const auto constItems = items;
259 for ( QgsSnapIndex::SnapItem *item : constItems )
261 if ( ( !endPointOnly && item->type == SnapPoint ) || item->type == SnapEndPoint )
264 if ( dist < minDistPoint )
267 snapPoint =
static_cast<PointSnapItem *
>( item );
270 else if ( item->type == SnapSegment && !endPointOnly )
272 if ( !
static_cast<SegmentSnapItem *
>( item )->withinSquaredDistance( pos, squaredTolerance ) )
276 if ( !
static_cast<SegmentSnapItem *
>( item )->getProjection( pos, pProj ) )
280 if ( dist < minDistSegment )
282 minDistSegment = dist;
283 snapSegment =
static_cast<SegmentSnapItem *
>( item );
287 snapPoint = minDistPoint < squaredTolerance ? snapPoint :
nullptr;
288 snapSegment = minDistSegment < squaredTolerance ? snapSegment :
nullptr;
290 *pSnapPoint = snapPoint;
292 *pSnapSegment = snapSegment;
293 return minDistPoint < minDistSegment ? static_cast<QgsSnapIndex::SnapItem *>( snapPoint ) : static_cast<QgsSnapIndex::SnapItem *>( snapSegment );
304 : mReferenceSource( referenceSource )
313 QtConcurrent::blockingMap( list, ProcessFeatureWrapper(
this, snapTolerance, mode ) );
317void QgsGeometrySnapper::processFeature(
QgsFeature &feature,
double snapTolerance, SnapMode mode )
327 QList<QgsGeometry> refGeometries;
330 searchBounds.
grow( snapTolerance );
331 const QgsFeatureIds refFeatureIds = qgis::listToSet( mIndex.intersects( searchBounds ) );
332 mIndexMutex.unlock();
334 if ( refFeatureIds.isEmpty() )
337 refGeometries.reserve( refFeatureIds.size() );
339 const QgsFeatureIds cachedIds = qgis::listToSet( mCachedReferenceGeometries.keys() );
342 if ( cachedIds.contains(
id ) )
344 refGeometries.append( mCachedReferenceGeometries[
id] );
348 missingFeatureIds << id;
352 if ( missingFeatureIds.size() > 0 )
354 mReferenceLayerMutex.lock();
360 refGeometries.append( refFeature.
geometry() );
362 mReferenceLayerMutex.unlock();
365 return snapGeometry( geometry, snapTolerance, refGeometries, mode );
375 QgsSnapIndex refSnapIndex;
376 for (
const QgsGeometry &geom : referenceGeometries )
378 refSnapIndex.addGeometry( geom.constGet() );
383 QList<QList<QList<PointFlag>>> subjPointFlags;
386 for (
int iPart = 0, nParts = subjGeom->
partCount(); iPart < nParts; ++iPart )
388 subjPointFlags.append( QList<QList<PointFlag>>() );
390 for (
int iRing = 0, nRings = subjGeom->
ringCount( iPart ); iRing < nRings; ++iRing )
392 subjPointFlags[iPart].append( QList<PointFlag>() );
394 for (
int iVert = 0, nVerts = polyLineSize( subjGeom, iPart, iRing ); iVert < nVerts; ++iVert )
399 subjPointFlags[iPart][iRing].append( Unsnapped );
403 QgsSnapIndex::PointSnapItem *snapPoint =
nullptr;
404 QgsSnapIndex::SegmentSnapItem *snapSegment =
nullptr;
407 if ( !refSnapIndex.getSnapItem( p, snapTolerance, &snapPoint, &snapSegment, mode ==
EndPointToEndPoint ) )
409 subjPointFlags[iPart][iRing].append( Unsnapped );
423 subjGeom->
moveVertex( vidx, snapPoint->getSnapPoint( p ) );
424 subjPointFlags[iPart][iRing].append( SnappedToRefNode );
426 else if ( snapSegment )
428 subjGeom->
moveVertex( vidx, snapSegment->getSnapPoint( p ) );
429 subjPointFlags[iPart][iRing].append( SnappedToRefSegment );
439 double distanceNode = std::numeric_limits<double>::max();
440 double distanceSegment = std::numeric_limits<double>::max();
443 nodeSnap = snapPoint->getSnapPoint( p );
448 segmentSnap = snapSegment->getSnapPoint( p );
451 if ( snapPoint && distanceNode < distanceSegment )
454 subjPointFlags[iPart][iRing].append( SnappedToRefNode );
456 else if ( snapSegment )
459 subjPointFlags[iPart][iRing].append( SnappedToRefSegment );
481 auto subjSnapIndex = std::make_unique<QgsSnapIndex>();
482 subjSnapIndex->addGeometry( subjGeom );
484 std::unique_ptr<QgsAbstractGeometry> origSubjGeom( subjGeom->
clone() );
485 auto origSubjSnapIndex = std::make_unique<QgsSnapIndex>();
486 origSubjSnapIndex->addGeometry( origSubjGeom.get() );
489 for (
const QgsGeometry &refGeom : referenceGeometries )
491 for (
int iPart = 0, nParts = refGeom.constGet()->partCount(); iPart < nParts; ++iPart )
493 for (
int iRing = 0, nRings = refGeom.constGet()->ringCount( iPart ); iRing < nRings; ++iRing )
495 for (
int iVert = 0, nVerts = polyLineSize( refGeom.constGet(), iPart, iRing ); iVert < nVerts; ++iVert )
497 QgsSnapIndex::PointSnapItem *snapPoint =
nullptr;
498 QgsSnapIndex::SegmentSnapItem *snapSegment =
nullptr;
500 if ( subjSnapIndex->getSnapItem( point, snapTolerance, &snapPoint, &snapSegment ) )
505 const QgsPoint snappedPoint = snapPoint->getSnapPoint( point );
513 const QgsPoint pProj = snapSegment->getSnapPoint( point );
514 const QgsPoint closest = refSnapIndex.getClosestSnapToPoint( point, pProj );
521 if ( !origSubjSnapIndex->getSnapItem( point, snapTolerance ) )
526 const QgsSnapIndex::CoordIdx *idx = snapSegment->idxFrom;
528 subjPointFlags[idx->vidx.part][idx->vidx.ring].insert( idx->vidx.vertex + 1, SnappedToRefNode );
529 subjSnapIndex = std::make_unique<QgsSnapIndex>();
530 subjSnapIndex->addGeometry( subjGeom );
537 subjSnapIndex.reset();
538 origSubjSnapIndex.reset();
539 origSubjGeom.reset();
542 for (
int iPart = 0, nParts = subjGeom->
partCount(); iPart < nParts; ++iPart )
544 for (
int iRing = 0, nRings = subjGeom->
ringCount( iPart ); iRing < nRings; ++iRing )
547 for (
int iVert = 0, nVerts = polyLineSize( subjGeom, iPart, iRing ); iVert < nVerts; ++iVert )
549 const int iPrev = ( iVert - 1 + nVerts ) % nVerts;
550 const int iNext = ( iVert + 1 ) % nVerts;
557 if ( ( ringIsClosed && nVerts > 3 ) || ( !ringIsClosed && nVerts > 2 ) )
560 subjPointFlags[iPart][iRing].removeAt( iVert );
579int QgsGeometrySnapper::polyLineSize(
const QgsAbstractGeometry *geom,
int iPart,
int iRing )
581 const int nVerts = geom->
vertexCount( iPart, iRing );
600 : mSnapTolerance( snapTolerance )
611 if ( !mFirstFeature )
616 searchBounds.
grow( mSnapTolerance );
617 const QgsFeatureIds refFeatureIds = qgis::listToSet( mProcessedIndex.intersects( searchBounds ) );
618 if ( !refFeatureIds.isEmpty() )
620 QList<QgsGeometry> refGeometries;
621 const auto constRefFeatureIds = refFeatureIds;
624 refGeometries << mProcessedGeometries.value(
id );
630 mProcessedGeometries.insert( feat.
id(), geometry );
631 mProcessedIndex.addFeature( feat );
632 mFirstFeature =
false;
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.
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...
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.
@ 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.
QgsPoint vertexAt(QgsVertexId) const override
Returns the point corresponding to a specified vertex id.
double distanceSquared(double x, double y) const
Returns the Cartesian 2D squared distance between this point a specified x, y coordinate.
A rectangle specified with double values.
void grow(double delta)
Grows the rectangle in place by the specified amount.
A spatial index for QgsFeature objects.
Represent a 2-dimensional vector.
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.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
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.
QList< const QgsPointCloudLayerProfileResults::PointResult * > * list