1 /***************************************************************************
2   qgspointlocator.cpp
3   --------------------------------------
4   Date                 : November 2014
5   Copyright            : (C) 2014 by Martin Dobias
6   Email                : wonder dot sk at gmail dot com
7  ***************************************************************************
8  *                                                                         *
9  *   This program is free software; you can redistribute it and/or modify  *
10  *   it under the terms of the GNU General Public License as published by  *
11  *   the Free Software Foundation; either version 2 of the License, or     *
12  *   (at your option) any later version.                                   *
13  *                                                                         *
14  ***************************************************************************/
15 
16 #include "qgspointlocator.h"
17 
18 #include "qgsfeatureiterator.h"
19 #include "qgsgeometry.h"
20 #include "qgsvectorlayer.h"
21 #include "qgswkbptr.h"
22 #include "qgis.h"
23 #include "qgslogger.h"
24 #include "qgsrenderer.h"
25 #include "qgsapplication.h"
26 #include "qgsvectorlayerfeatureiterator.h"
27 #include "qgsexpressioncontextutils.h"
28 #include "qgslinestring.h"
29 #include "qgscurvepolygon.h"
30 #include "qgsrendercontext.h"
31 #include "qgspointlocatorinittask.h"
32 #include <spatialindex/SpatialIndex.h>
33 
34 #include <QLinkedListIterator>
35 #include <QtConcurrent>
36 
37 using namespace SpatialIndex;
38 
39 
40 
point2point(const QgsPointXY & point)41 static SpatialIndex::Point point2point( const QgsPointXY &point )
42 {
43   double plow[2] = { point.x(), point.y() };
44   return Point( plow, 2 );
45 }
46 
47 
rect2region(const QgsRectangle & rect)48 static SpatialIndex::Region rect2region( const QgsRectangle &rect )
49 {
50   double pLow[2] = { rect.xMinimum(), rect.yMinimum() };
51   double pHigh[2] = { rect.xMaximum(), rect.yMaximum() };
52   return SpatialIndex::Region( pLow, pHigh, 2 );
53 }
54 
55 
56 // Ahh.... another magic number. Taken from QgsVectorLayer::snapToGeometry() call to closestSegmentWithContext().
57 // The default epsilon used for sqrDistToSegment (1e-8) is too high when working with lat/lon coordinates
58 // I still do not fully understand why the sqrDistToSegment() code uses epsilon and if the square distance
59 // is lower than epsilon it will have a special logic...
60 static const double POINT_LOC_EPSILON = 1e-12;
61 
62 ////////////////////////////////////////////////////////////////////////////
63 
64 
65 /**
66  * \ingroup core
67  * \brief Helper class for bulk loading of R-trees.
68  * \note not available in Python bindings
69 */
70 class QgsPointLocator_Stream : public IDataStream
71 {
72   public:
QgsPointLocator_Stream(const QLinkedList<RTree::Data * > & dataList)73     explicit QgsPointLocator_Stream( const QLinkedList<RTree::Data *> &dataList )
74       : mDataList( dataList )
75       , mIt( mDataList )
76     { }
77 
getNext()78     IData *getNext() override { return mIt.next(); }
hasNext()79     bool hasNext() override { return mIt.hasNext(); }
80 
size()81     uint32_t size() override { Q_ASSERT( false && "not available" ); return 0; }
rewind()82     void rewind() override { Q_ASSERT( false && "not available" ); }
83 
84   private:
85     QLinkedList<RTree::Data *> mDataList;
86     QLinkedListIterator<RTree::Data *> mIt;
87 };
88 
89 
90 ////////////////////////////////////////////////////////////////////////////
91 
92 
93 /**
94  * \ingroup core
95  * \brief Helper class used when traversing the index looking for vertices - builds a list of matches.
96  * \note not available in Python bindings
97 */
98 class QgsPointLocator_VisitorNearestVertex : public IVisitor
99 {
100   public:
QgsPointLocator_VisitorNearestVertex(QgsPointLocator * pl,QgsPointLocator::Match & m,const QgsPointXY & srcPoint,QgsPointLocator::MatchFilter * filter=nullptr)101     QgsPointLocator_VisitorNearestVertex( QgsPointLocator *pl, QgsPointLocator::Match &m, const QgsPointXY &srcPoint, QgsPointLocator::MatchFilter *filter = nullptr )
102       : mLocator( pl )
103       , mBest( m )
104       , mSrcPoint( srcPoint )
105       , mFilter( filter )
106     {}
107 
visitNode(const INode & n)108     void visitNode( const INode &n ) override { Q_UNUSED( n ) }
visitData(std::vector<const IData * > & v)109     void visitData( std::vector<const IData *> &v ) override { Q_UNUSED( v ) }
110 
visitData(const IData & d)111     void visitData( const IData &d ) override
112     {
113       const QgsFeatureId id = d.getIdentifier();
114       QgsGeometry *geom = mLocator->mGeoms.value( id );
115       int vertexIndex, beforeVertex, afterVertex;
116       double sqrDist;
117 
118       const QgsPointXY pt = geom->closestVertex( mSrcPoint, vertexIndex, beforeVertex, afterVertex, sqrDist );
119       if ( sqrDist < 0 )
120         return;  // probably empty geometry
121 
122       const QgsPointLocator::Match m( QgsPointLocator::Vertex, mLocator->mLayer, id, std::sqrt( sqrDist ), pt, vertexIndex );
123       // in range queries the filter may reject some matches
124       if ( mFilter && !mFilter->acceptMatch( m ) )
125         return;
126 
127       if ( !mBest.isValid() || m.distance() < mBest.distance() )
128         mBest = m;
129     }
130 
131   private:
132     QgsPointLocator *mLocator = nullptr;
133     QgsPointLocator::Match &mBest;
134     QgsPointXY mSrcPoint;
135     QgsPointLocator::MatchFilter *mFilter = nullptr;
136 };
137 
138 
139 
140 /**
141  * \ingroup core
142  * \brief Helper class used when traversing the index looking for centroid - builds a list of matches.
143  * \note not available in Python bindings
144  * \since QGIS 3.12
145 */
146 class QgsPointLocator_VisitorNearestCentroid : public IVisitor
147 {
148   public:
149 
150     /**
151      * \ingroup core
152      * \brief Helper class used when traversing the index looking for centroid - builds a list of matches.
153      * \note not available in Python bindings
154      * \since QGIS 3.12
155     */
QgsPointLocator_VisitorNearestCentroid(QgsPointLocator * pl,QgsPointLocator::Match & m,const QgsPointXY & srcPoint,QgsPointLocator::MatchFilter * filter=nullptr)156     QgsPointLocator_VisitorNearestCentroid( QgsPointLocator *pl, QgsPointLocator::Match &m, const QgsPointXY &srcPoint, QgsPointLocator::MatchFilter *filter = nullptr )
157       : mLocator( pl )
158       , mBest( m )
159       , mSrcPoint( srcPoint )
160       , mFilter( filter )
161     {}
162 
visitNode(const INode & n)163     void visitNode( const INode &n ) override { Q_UNUSED( n ) }
visitData(std::vector<const IData * > & v)164     void visitData( std::vector<const IData *> &v ) override { Q_UNUSED( v ) }
165 
visitData(const IData & d)166     void visitData( const IData &d ) override
167     {
168       const QgsFeatureId id = d.getIdentifier();
169       QgsGeometry *geom = mLocator->mGeoms.value( id );
170 
171       const QgsPointXY pt = geom->centroid().asPoint();
172 
173       const QgsPointLocator::Match m( QgsPointLocator::Centroid, mLocator->mLayer, id, std::sqrt( mSrcPoint.sqrDist( pt ) ), pt, -1 );
174       // in range queries the filter may reject some matches
175       if ( mFilter && !mFilter->acceptMatch( m ) )
176         return;
177 
178       if ( !mBest.isValid() || m.distance() < mBest.distance() )
179         mBest = m;
180 
181     }
182 
183   private:
184     QgsPointLocator *mLocator = nullptr;
185     QgsPointLocator::Match &mBest;
186     QgsPointXY mSrcPoint;
187     QgsPointLocator::MatchFilter *mFilter = nullptr;
188 };
189 
190 ////////////////////////////////////////////////////////////////////////////
191 
192 /**
193  * \ingroup core
194  * \brief Helper class used when traversing the index looking for middle segment - builds a list of matches.
195  * \note not available in Python bindings
196  * \since QGIS 3.12
197 */
198 class QgsPointLocator_VisitorNearestMiddleOfSegment: public IVisitor
199 {
200   public:
201 
202     /**
203      * \ingroup core
204      * \brief Helper class used when traversing the index looking for middle segment - builds a list of matches.
205      * \note not available in Python bindings
206      * \since QGIS 3.12
207     */
QgsPointLocator_VisitorNearestMiddleOfSegment(QgsPointLocator * pl,QgsPointLocator::Match & m,const QgsPointXY & srcPoint,QgsPointLocator::MatchFilter * filter=nullptr)208     QgsPointLocator_VisitorNearestMiddleOfSegment( QgsPointLocator *pl, QgsPointLocator::Match &m, const QgsPointXY &srcPoint, QgsPointLocator::MatchFilter *filter = nullptr )
209       : mLocator( pl )
210       , mBest( m )
211       , mSrcPoint( srcPoint )
212       , mFilter( filter )
213     {}
214 
visitNode(const INode & n)215     void visitNode( const INode &n ) override { Q_UNUSED( n ) }
visitData(std::vector<const IData * > & v)216     void visitData( std::vector<const IData *> &v ) override { Q_UNUSED( v ) }
217 
visitData(const IData & d)218     void visitData( const IData &d ) override
219     {
220       const QgsFeatureId id = d.getIdentifier();
221       QgsGeometry *geom = mLocator->mGeoms.value( id );
222       QgsPointXY pt;
223       int afterVertex;
224       const double sqrDist = geom->closestSegmentWithContext( mSrcPoint, pt, afterVertex, nullptr, POINT_LOC_EPSILON );
225       if ( sqrDist < 0 )
226         return;
227 
228       QgsPointXY edgePoints[2];
229       edgePoints[0] = geom->vertexAt( afterVertex - 1 );
230       edgePoints[1] = geom->vertexAt( afterVertex );
231       pt = QgsPointXY( ( edgePoints[0].x() + edgePoints[1].x() ) / 2.0, ( edgePoints[0].y() + edgePoints[1].y() ) / 2.0 );
232 
233       const QgsPointLocator::Match m( QgsPointLocator::MiddleOfSegment, mLocator->mLayer, id, std::sqrt( mSrcPoint.sqrDist( pt ) ), pt, afterVertex - 1 );
234       // in range queries the filter may reject some matches
235       if ( mFilter && !mFilter->acceptMatch( m ) )
236         return;
237 
238       if ( !mBest.isValid() || m.distance() < mBest.distance() )
239         mBest = m;
240 
241     }
242 
243   private:
244     QgsPointLocator *mLocator = nullptr;
245     QgsPointLocator::Match &mBest;
246     QgsPointXY mSrcPoint;
247     QgsPointLocator::MatchFilter *mFilter = nullptr;
248 };
249 
250 ////////////////////////////////////////////////////////////////////////////
251 
252 /**
253  * \ingroup core
254  * \brief Helper class used when traversing the index looking for line endpoints (start or end vertex) - builds a list of matches.
255  * \note not available in Python bindings
256  * \since QGIS 3.20
257 */
258 class QgsPointLocator_VisitorNearestLineEndpoint : public IVisitor
259 {
260   public:
261 
262     /**
263      * \ingroup core
264      * \brief Helper class used when traversing the index looking for line endpoints (start or end vertex) - builds a list of matches.
265     */
QgsPointLocator_VisitorNearestLineEndpoint(QgsPointLocator * pl,QgsPointLocator::Match & m,const QgsPointXY & srcPoint,QgsPointLocator::MatchFilter * filter=nullptr)266     QgsPointLocator_VisitorNearestLineEndpoint( QgsPointLocator *pl, QgsPointLocator::Match &m, const QgsPointXY &srcPoint, QgsPointLocator::MatchFilter *filter = nullptr )
267       : mLocator( pl )
268       , mBest( m )
269       , mSrcPoint( srcPoint )
270       , mFilter( filter )
271     {}
272 
visitNode(const INode & n)273     void visitNode( const INode &n ) override { Q_UNUSED( n ) }
visitData(std::vector<const IData * > & v)274     void visitData( std::vector<const IData *> &v ) override { Q_UNUSED( v ) }
275 
visitData(const IData & d)276     void visitData( const IData &d ) override
277     {
278       const QgsFeatureId id = d.getIdentifier();
279       const QgsGeometry *geom = mLocator->mGeoms.value( id );
280 
281       QgsPointXY bestPoint;
282       int bestVertexNumber = -1;
283       auto replaceIfBetter = [this, &bestPoint, &bestVertexNumber]( const QgsPoint & candidate, int vertexNumber )
284       {
285         if ( bestPoint.isEmpty() || candidate.distanceSquared( mSrcPoint.x(), mSrcPoint.y() ) < bestPoint.sqrDist( mSrcPoint ) )
286         {
287           bestPoint = QgsPointXY( candidate );
288           bestVertexNumber = vertexNumber;
289         }
290       };
291 
292       switch ( QgsWkbTypes::geometryType( geom->wkbType() ) )
293       {
294         case QgsWkbTypes::PointGeometry:
295         case QgsWkbTypes::UnknownGeometry:
296         case QgsWkbTypes::NullGeometry:
297           return;
298 
299         case QgsWkbTypes::LineGeometry:
300         {
301           int partStartVertexNum = 0;
302           for ( auto partIt = geom->const_parts_begin(); partIt != geom->const_parts_end(); ++partIt )
303           {
304             if ( const QgsCurve *curve = qgsgeometry_cast< const QgsCurve * >( *partIt ) )
305             {
306               replaceIfBetter( curve->startPoint(), partStartVertexNum );
307               replaceIfBetter( curve->endPoint(), partStartVertexNum + curve->numPoints() - 1 );
308               partStartVertexNum += curve->numPoints();
309             }
310           }
311           break;
312         }
313 
314         case QgsWkbTypes::PolygonGeometry:
315         {
316           int partStartVertexNum = 0;
317           for ( auto partIt = geom->const_parts_begin(); partIt != geom->const_parts_end(); ++partIt )
318           {
319             if ( const QgsCurvePolygon *polygon = qgsgeometry_cast< const QgsCurvePolygon * >( *partIt ) )
320             {
321               if ( polygon->exteriorRing() )
322               {
323                 replaceIfBetter( polygon->exteriorRing()->startPoint(), partStartVertexNum );
324                 partStartVertexNum += polygon->exteriorRing()->numPoints();
325               }
326               for ( int i = 0; i < polygon->numInteriorRings(); ++i )
327               {
328                 const QgsCurve *ring = polygon->interiorRing( i );
329                 replaceIfBetter( ring->startPoint(), partStartVertexNum );
330                 partStartVertexNum += ring->numPoints();
331               }
332             }
333           }
334           break;
335         }
336       }
337 
338       const QgsPointLocator::Match m( QgsPointLocator::LineEndpoint, mLocator->mLayer, id, std::sqrt( mSrcPoint.sqrDist( bestPoint ) ), bestPoint, bestVertexNumber );
339       // in range queries the filter may reject some matches
340       if ( mFilter && !mFilter->acceptMatch( m ) )
341         return;
342 
343       if ( !mBest.isValid() || m.distance() < mBest.distance() )
344         mBest = m;
345     }
346 
347   private:
348     QgsPointLocator *mLocator = nullptr;
349     QgsPointLocator::Match &mBest;
350     QgsPointXY mSrcPoint;
351     QgsPointLocator::MatchFilter *mFilter = nullptr;
352 };
353 
354 
355 ////////////////////////////////////////////////////////////////////////////
356 
357 
358 /**
359  * \ingroup core
360  * \brief Helper class used when traversing the index looking for edges - builds a list of matches.
361  * \note not available in Python bindings
362 */
363 class QgsPointLocator_VisitorNearestEdge : public IVisitor
364 {
365   public:
QgsPointLocator_VisitorNearestEdge(QgsPointLocator * pl,QgsPointLocator::Match & m,const QgsPointXY & srcPoint,QgsPointLocator::MatchFilter * filter=nullptr)366     QgsPointLocator_VisitorNearestEdge( QgsPointLocator *pl, QgsPointLocator::Match &m, const QgsPointXY &srcPoint, QgsPointLocator::MatchFilter *filter = nullptr )
367       : mLocator( pl )
368       , mBest( m )
369       , mSrcPoint( srcPoint )
370       , mFilter( filter )
371     {}
372 
visitNode(const INode & n)373     void visitNode( const INode &n ) override { Q_UNUSED( n ) }
visitData(std::vector<const IData * > & v)374     void visitData( std::vector<const IData *> &v ) override { Q_UNUSED( v ) }
375 
visitData(const IData & d)376     void visitData( const IData &d ) override
377     {
378       const QgsFeatureId id = d.getIdentifier();
379       QgsGeometry *geom = mLocator->mGeoms.value( id );
380       QgsPointXY pt;
381       int afterVertex;
382       const double sqrDist = geom->closestSegmentWithContext( mSrcPoint, pt, afterVertex, nullptr, POINT_LOC_EPSILON );
383       if ( sqrDist < 0 )
384         return;
385 
386       QgsPointXY edgePoints[2];
387       edgePoints[0] = geom->vertexAt( afterVertex - 1 );
388       edgePoints[1] = geom->vertexAt( afterVertex );
389       const QgsPointLocator::Match m( QgsPointLocator::Edge, mLocator->mLayer, id, std::sqrt( sqrDist ), pt, afterVertex - 1, edgePoints );
390       // in range queries the filter may reject some matches
391       if ( mFilter && !mFilter->acceptMatch( m ) )
392         return;
393 
394       if ( !mBest.isValid() || m.distance() < mBest.distance() )
395         mBest = m;
396     }
397 
398   private:
399     QgsPointLocator *mLocator = nullptr;
400     QgsPointLocator::Match &mBest;
401     QgsPointXY mSrcPoint;
402     QgsPointLocator::MatchFilter *mFilter = nullptr;
403 };
404 
405 
406 ////////////////////////////////////////////////////////////////////////////
407 
408 /**
409  * \ingroup core
410  * \brief Helper class used when traversing the index with areas - builds a list of matches.
411  * \note not available in Python bindings
412 */
413 class QgsPointLocator_VisitorArea : public IVisitor
414 {
415   public:
416     //! constructor
QgsPointLocator_VisitorArea(QgsPointLocator * pl,const QgsPointXY & origPt,QgsPointLocator::MatchList & list)417     QgsPointLocator_VisitorArea( QgsPointLocator *pl, const QgsPointXY &origPt, QgsPointLocator::MatchList &list )
418       : mLocator( pl )
419       , mList( list )
420       , mGeomPt( QgsGeometry::fromPointXY( origPt ) )
421     {}
422 
visitNode(const INode & n)423     void visitNode( const INode &n ) override { Q_UNUSED( n ) }
visitData(std::vector<const IData * > & v)424     void visitData( std::vector<const IData *> &v ) override { Q_UNUSED( v ) }
425 
visitData(const IData & d)426     void visitData( const IData &d ) override
427     {
428       const QgsFeatureId id = d.getIdentifier();
429       QgsGeometry *g = mLocator->mGeoms.value( id );
430       if ( g->intersects( mGeomPt ) )
431         mList << QgsPointLocator::Match( QgsPointLocator::Area, mLocator->mLayer, id, 0, mGeomPt.asPoint() );
432     }
433   private:
434     QgsPointLocator *mLocator = nullptr;
435     QgsPointLocator::MatchList &mList;
436     QgsGeometry mGeomPt;
437 };
438 
439 
440 ////////////////////////////////////////////////////////////////////////////
441 
442 // code adapted from
443 // http://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm
444 struct _CohenSutherland
445 {
_CohenSutherland_CohenSutherland446   explicit _CohenSutherland( const QgsRectangle &rect ) : mRect( rect ) {}
447 
448   typedef int OutCode;
449 
450   static const int INSIDE = 0; // 0000
451   static const int LEFT = 1;   // 0001
452   static const int RIGHT = 2;  // 0010
453   static const int BOTTOM = 4; // 0100
454   static const int TOP = 8;    // 1000
455 
456   QgsRectangle mRect;
457 
computeOutCode_CohenSutherland458   OutCode computeOutCode( double x, double y )
459   {
460     OutCode code = INSIDE;  // initialized as being inside of clip window
461     if ( x < mRect.xMinimum() )         // to the left of clip window
462       code |= LEFT;
463     else if ( x > mRect.xMaximum() )    // to the right of clip window
464       code |= RIGHT;
465     if ( y < mRect.yMinimum() )         // below the clip window
466       code |= BOTTOM;
467     else if ( y > mRect.yMaximum() )    // above the clip window
468       code |= TOP;
469     return code;
470   }
471 
isSegmentInRect_CohenSutherland472   bool isSegmentInRect( double x0, double y0, double x1, double y1 )
473   {
474     // compute outcodes for P0, P1, and whatever point lies outside the clip rectangle
475     OutCode outcode0 = computeOutCode( x0, y0 );
476     OutCode outcode1 = computeOutCode( x1, y1 );
477     bool accept = false;
478 
479     while ( true )
480     {
481       if ( !( outcode0 | outcode1 ) )
482       {
483         // Bitwise OR is 0. Trivially accept and get out of loop
484         accept = true;
485         break;
486       }
487       else if ( outcode0 & outcode1 )
488       {
489         // Bitwise AND is not 0. Trivially reject and get out of loop
490         break;
491       }
492       else
493       {
494         // failed both tests, so calculate the line segment to clip
495         // from an outside point to an intersection with clip edge
496         double x, y;
497 
498         // At least one endpoint is outside the clip rectangle; pick it.
499         const OutCode outcodeOut = outcode0 ? outcode0 : outcode1;
500 
501         // Now find the intersection point;
502         // use formulas y = y0 + slope * (x - x0), x = x0 + (1 / slope) * (y - y0)
503         if ( outcodeOut & TOP )
504         {
505           // point is above the clip rectangle
506           x = x0 + ( x1 - x0 ) * ( mRect.yMaximum() - y0 ) / ( y1 - y0 );
507           y = mRect.yMaximum();
508         }
509         else if ( outcodeOut & BOTTOM )
510         {
511           // point is below the clip rectangle
512           x = x0 + ( x1 - x0 ) * ( mRect.yMinimum() - y0 ) / ( y1 - y0 );
513           y = mRect.yMinimum();
514         }
515         else if ( outcodeOut & RIGHT )
516         {
517           // point is to the right of clip rectangle
518           y = y0 + ( y1 - y0 ) * ( mRect.xMaximum() - x0 ) / ( x1 - x0 );
519           x = mRect.xMaximum();
520         }
521         else if ( outcodeOut & LEFT )
522         {
523           // point is to the left of clip rectangle
524           y = y0 + ( y1 - y0 ) * ( mRect.xMinimum() - x0 ) / ( x1 - x0 );
525           x = mRect.xMinimum();
526         }
527         else
528           break;
529 
530         // Now we move outside point to intersection point to clip
531         // and get ready for next pass.
532         if ( outcodeOut == outcode0 )
533         {
534           x0 = x;
535           y0 = y;
536           outcode0 = computeOutCode( x0, y0 );
537         }
538         else
539         {
540           x1 = x;
541           y1 = y;
542           outcode1 = computeOutCode( x1, y1 );
543         }
544       }
545     }
546     return accept;
547   }
548 };
549 
550 
_geometrySegmentsInRect(QgsGeometry * geom,const QgsRectangle & rect,QgsVectorLayer * vl,QgsFeatureId fid)551 static QgsPointLocator::MatchList _geometrySegmentsInRect( QgsGeometry *geom, const QgsRectangle &rect, QgsVectorLayer *vl, QgsFeatureId fid )
552 {
553   // this code is stupidly based on QgsGeometry::closestSegmentWithContext
554   // we need iterator for segments...
555 
556   QgsPointLocator::MatchList lst;
557 
558   // geom is converted to a MultiCurve
559   QgsGeometry straightGeom = geom->convertToType( QgsWkbTypes::LineGeometry, true );
560   // and convert to straight segemnt / converts curve to linestring
561   straightGeom.convertToStraightSegment();
562 
563   // so, you must have multilinestring
564   //
565   // Special case: Intersections cannot be done on an empty linestring like
566   // QgsGeometry(QgsLineString()) or QgsGeometry::fromWkt("LINESTRING EMPTY")
567   if ( straightGeom.isEmpty() || ( ( straightGeom.type() != QgsWkbTypes::LineGeometry ) && ( !straightGeom.isMultipart() ) ) )
568     return lst;
569 
570   _CohenSutherland cs( rect );
571 
572   int pointIndex = 0;
573   for ( auto part = straightGeom.const_parts_begin(); part != straightGeom.const_parts_end(); ++part )
574   {
575     // Checking for invalid linestrings
576     // A linestring should/(must?) have at least two points.
577     QgsCurve *curve = qgsgeometry_cast<QgsCurve *>( *part );
578     Q_ASSERT( !curve->hasCurvedSegments() );
579     if ( curve->numPoints() < 2 )
580       continue;
581 
582     QgsAbstractGeometry::vertex_iterator it = ( *part )->vertices_begin();
583     QgsPointXY prevPoint( *it );
584     it++;
585     while ( it != ( *part )->vertices_end() )
586     {
587       const QgsPointXY thisPoint( *it );
588       if ( cs.isSegmentInRect( prevPoint.x(), prevPoint.y(), thisPoint.x(), thisPoint.y() ) )
589       {
590         QgsPointXY edgePoints[2];
591         edgePoints[0] = prevPoint;
592         edgePoints[1] = thisPoint;
593         lst << QgsPointLocator::Match( QgsPointLocator::Edge, vl, fid, 0, QgsPointXY(), pointIndex - 1, edgePoints );
594       }
595       prevPoint = QgsPointXY( *it );
596       it++;
597       pointIndex += 1;
598 
599     }
600   }
601   return lst;
602 }
603 
604 /**
605  * \ingroup core
606  * \brief Helper class used when traversing the index looking for edges - builds a list of matches.
607  * \note not available in Python bindings
608 */
609 class QgsPointLocator_VisitorEdgesInRect : public IVisitor
610 {
611   public:
QgsPointLocator_VisitorEdgesInRect(QgsPointLocator * pl,QgsPointLocator::MatchList & lst,const QgsRectangle & srcRect,QgsPointLocator::MatchFilter * filter=nullptr)612     QgsPointLocator_VisitorEdgesInRect( QgsPointLocator *pl, QgsPointLocator::MatchList &lst, const QgsRectangle &srcRect, QgsPointLocator::MatchFilter *filter = nullptr )
613       : mLocator( pl )
614       , mList( lst )
615       , mSrcRect( srcRect )
616       , mFilter( filter )
617     {}
618 
visitNode(const INode & n)619     void visitNode( const INode &n ) override { Q_UNUSED( n ) }
visitData(std::vector<const IData * > & v)620     void visitData( std::vector<const IData *> &v ) override { Q_UNUSED( v ) }
621 
visitData(const IData & d)622     void visitData( const IData &d ) override
623     {
624       const QgsFeatureId id = d.getIdentifier();
625       QgsGeometry *geom = mLocator->mGeoms.value( id );
626 
627       const auto segmentsInRect {_geometrySegmentsInRect( geom, mSrcRect, mLocator->mLayer, id )};
628       for ( const QgsPointLocator::Match &m : segmentsInRect )
629       {
630         // in range queries the filter may reject some matches
631         if ( mFilter && !mFilter->acceptMatch( m ) )
632           continue;
633 
634         mList << m;
635       }
636     }
637 
638   private:
639     QgsPointLocator *mLocator = nullptr;
640     QgsPointLocator::MatchList &mList;
641     QgsRectangle mSrcRect;
642     QgsPointLocator::MatchFilter *mFilter = nullptr;
643 };
644 
645 ////////////////////////////////////////////////////////////////////////////
646 
647 /**
648  * \ingroup core
649  * \brief Helper class used when traversing the index looking for vertices - builds a list of matches.
650  * \note not available in Python bindings
651  * \since QGIS 3.6
652 */
653 class QgsPointLocator_VisitorVerticesInRect : public IVisitor
654 {
655   public:
656     //! Constructs the visitor
QgsPointLocator_VisitorVerticesInRect(QgsPointLocator * pl,QgsPointLocator::MatchList & lst,const QgsRectangle & srcRect,QgsPointLocator::MatchFilter * filter=nullptr)657     QgsPointLocator_VisitorVerticesInRect( QgsPointLocator *pl, QgsPointLocator::MatchList &lst, const QgsRectangle &srcRect, QgsPointLocator::MatchFilter *filter = nullptr )
658       : mLocator( pl )
659       , mList( lst )
660       , mSrcRect( srcRect )
661       , mFilter( filter )
662     {}
663 
visitNode(const INode & n)664     void visitNode( const INode &n ) override { Q_UNUSED( n ) }
visitData(std::vector<const IData * > & v)665     void visitData( std::vector<const IData *> &v ) override { Q_UNUSED( v ) }
666 
visitData(const IData & d)667     void visitData( const IData &d ) override
668     {
669       const QgsFeatureId id = d.getIdentifier();
670       const QgsGeometry *geom = mLocator->mGeoms.value( id );
671 
672       for ( QgsAbstractGeometry::vertex_iterator it = geom->vertices_begin(); it != geom->vertices_end(); ++it )
673       {
674         if ( mSrcRect.contains( *it ) )
675         {
676           const QgsPointLocator::Match m( QgsPointLocator::Vertex, mLocator->mLayer, id, 0, *it, geom->vertexNrFromVertexId( it.vertexId() ) );
677 
678           // in range queries the filter may reject some matches
679           if ( mFilter && !mFilter->acceptMatch( m ) )
680             continue;
681 
682           mList << m;
683         }
684       }
685     }
686 
687   private:
688     QgsPointLocator *mLocator = nullptr;
689     QgsPointLocator::MatchList &mList;
690     QgsRectangle mSrcRect;
691     QgsPointLocator::MatchFilter *mFilter = nullptr;
692 };
693 
694 /**
695  * \ingroup core
696  * \brief Helper class used when traversing the index looking for centroid - builds a list of matches.
697  * \note not available in Python bindings
698  * \since QGIS 3.10
699 */
700 class QgsPointLocator_VisitorCentroidsInRect : public IVisitor
701 {
702   public:
703     //! Constructs the visitor
QgsPointLocator_VisitorCentroidsInRect(QgsPointLocator * pl,QgsPointLocator::MatchList & lst,const QgsRectangle & srcRect,QgsPointLocator::MatchFilter * filter=nullptr)704     QgsPointLocator_VisitorCentroidsInRect( QgsPointLocator *pl, QgsPointLocator::MatchList &lst, const QgsRectangle &srcRect, QgsPointLocator::MatchFilter *filter = nullptr )
705       : mLocator( pl )
706       , mList( lst )
707       , mSrcRect( srcRect )
708       , mFilter( filter )
709     {}
710 
visitNode(const INode & n)711     void visitNode( const INode &n ) override { Q_UNUSED( n ); }
visitData(std::vector<const IData * > & v)712     void visitData( std::vector<const IData *> &v ) override { Q_UNUSED( v ); }
713 
visitData(const IData & d)714     void visitData( const IData &d ) override
715     {
716       const QgsFeatureId id = d.getIdentifier();
717       const QgsGeometry *geom = mLocator->mGeoms.value( id );
718       const QgsPointXY centroid = geom->centroid().asPoint();
719       if ( mSrcRect.contains( centroid ) )
720       {
721         const QgsPointLocator::Match m( QgsPointLocator::Centroid, mLocator->mLayer, id, 0, centroid, -1 );
722 
723         // in range queries the filter may reject some matches
724         if ( !( mFilter && !mFilter->acceptMatch( m ) ) )
725           mList << m;
726       }
727     }
728 
729   private:
730     QgsPointLocator *mLocator = nullptr;
731     QgsPointLocator::MatchList &mList;
732     QgsRectangle mSrcRect;
733     QgsPointLocator::MatchFilter *mFilter = nullptr;
734 };
735 
736 /**
737  * \ingroup core
738  * \brief Helper class used when traversing the index looking for middle segment - builds a list of matches.
739  * \note not available in Python bindings
740  * \since QGIS 3.10
741 */
742 class QgsPointLocator_VisitorMiddlesInRect : public IVisitor
743 {
744   public:
745     //! Constructs the visitor
QgsPointLocator_VisitorMiddlesInRect(QgsPointLocator * pl,QgsPointLocator::MatchList & lst,const QgsRectangle & srcRect,QgsPointLocator::MatchFilter * filter=nullptr)746     QgsPointLocator_VisitorMiddlesInRect( QgsPointLocator *pl, QgsPointLocator::MatchList &lst, const QgsRectangle &srcRect, QgsPointLocator::MatchFilter *filter = nullptr )
747       : mLocator( pl )
748       , mList( lst )
749       , mSrcRect( srcRect )
750       , mFilter( filter )
751     {}
752 
visitNode(const INode & n)753     void visitNode( const INode &n ) override { Q_UNUSED( n ); }
visitData(std::vector<const IData * > & v)754     void visitData( std::vector<const IData *> &v ) override { Q_UNUSED( v ); }
755 
visitData(const IData & d)756     void visitData( const IData &d ) override
757     {
758       const QgsFeatureId id = d.getIdentifier();
759       const QgsGeometry *geom = mLocator->mGeoms.value( id );
760 
761       for ( QgsAbstractGeometry::const_part_iterator itPart = geom->const_parts_begin() ; itPart != geom->const_parts_end() ; ++itPart )
762       {
763         QgsAbstractGeometry::vertex_iterator it = ( *itPart )->vertices_begin();
764         QgsAbstractGeometry::vertex_iterator itPrevious = ( *itPart )->vertices_begin();
765         it++;
766         for ( ; it != geom->vertices_end(); ++it, ++itPrevious )
767         {
768           const QgsPointXY pt( ( ( *itPrevious ).x() + ( *it ).x() ) / 2.0, ( ( *itPrevious ).y() + ( *it ).y() ) / 2.0 );
769           if ( mSrcRect.contains( pt ) )
770           {
771             const QgsPointLocator::Match m( QgsPointLocator::MiddleOfSegment, mLocator->mLayer, id, 0, pt, geom->vertexNrFromVertexId( it.vertexId() ) );
772 
773             // in range queries the filter may reject some matches
774             if ( mFilter && !mFilter->acceptMatch( m ) )
775               continue;
776 
777             mList << m;
778           }
779         }
780       }
781     }
782 
783   private:
784     QgsPointLocator *mLocator = nullptr;
785     QgsPointLocator::MatchList &mList;
786     QgsRectangle mSrcRect;
787     QgsPointLocator::MatchFilter *mFilter = nullptr;
788 };
789 
790 ////////////////////////////////////////////////////////////////////////////
791 #include <QStack>
792 
793 /**
794  * \ingroup core
795  * \brief Helper class to dump the R-index nodes and their content
796  * \note not available in Python bindings
797 */
798 class QgsPointLocator_DumpTree : public SpatialIndex::IQueryStrategy
799 {
800   private:
801     QStack<id_type> ids;
802 
803   public:
804 
getNextEntry(const IEntry & entry,id_type & nextEntry,bool & hasNext)805     void getNextEntry( const IEntry &entry, id_type &nextEntry, bool &hasNext ) override
806     {
807       const INode *n = dynamic_cast<const INode *>( &entry );
808       if ( !n )
809         return;
810 
811       QgsDebugMsgLevel( QStringLiteral( "NODE: %1" ).arg( n->getIdentifier() ), 4 );
812       if ( n->getLevel() > 0 )
813       {
814         // inner nodes
815         for ( uint32_t cChild = 0; cChild < n->getChildrenCount(); cChild++ )
816         {
817           QgsDebugMsgLevel( QStringLiteral( "- CH: %1" ).arg( n->getChildIdentifier( cChild ) ), 4 );
818           ids.push( n->getChildIdentifier( cChild ) );
819         }
820       }
821       else
822       {
823         // leaves
824         for ( uint32_t cChild = 0; cChild < n->getChildrenCount(); cChild++ )
825         {
826           QgsDebugMsgLevel( QStringLiteral( "- L: %1" ).arg( n->getChildIdentifier( cChild ) ), 4 );
827         }
828       }
829 
830       if ( ! ids.empty() )
831       {
832         nextEntry = ids.back();
833         ids.pop();
834         hasNext = true;
835       }
836       else
837         hasNext = false;
838     }
839 };
840 
841 ////////////////////////////////////////////////////////////////////////////
842 
843 
QgsPointLocator(QgsVectorLayer * layer,const QgsCoordinateReferenceSystem & destCRS,const QgsCoordinateTransformContext & transformContext,const QgsRectangle * extent)844 QgsPointLocator::QgsPointLocator( QgsVectorLayer *layer, const QgsCoordinateReferenceSystem &destCRS, const QgsCoordinateTransformContext &transformContext, const QgsRectangle *extent )
845   : mLayer( layer )
846 {
847   if ( destCRS.isValid() )
848   {
849     mTransform = QgsCoordinateTransform( layer->crs(), destCRS, transformContext );
850   }
851 
852   setExtent( extent );
853 
854   mStorage.reset( StorageManager::createNewMemoryStorageManager() );
855 
856   connect( mLayer, &QgsVectorLayer::featureAdded, this, &QgsPointLocator::onFeatureAdded );
857   connect( mLayer, &QgsVectorLayer::featureDeleted, this, &QgsPointLocator::onFeatureDeleted );
858   connect( mLayer, &QgsVectorLayer::geometryChanged, this, &QgsPointLocator::onGeometryChanged );
859   connect( mLayer, &QgsVectorLayer::attributeValueChanged, this, &QgsPointLocator::onAttributeValueChanged );
860   connect( mLayer, &QgsVectorLayer::dataChanged, this, &QgsPointLocator::destroyIndex );
861 }
862 
863 
~QgsPointLocator()864 QgsPointLocator::~QgsPointLocator()
865 {
866   // don't delete a locator if there is an indexing task running on it
867   mIsDestroying = true;
868   if ( mIsIndexing )
869     waitForIndexingFinished();
870 
871   destroyIndex();
872 }
873 
destinationCrs() const874 QgsCoordinateReferenceSystem QgsPointLocator::destinationCrs() const
875 {
876   return mTransform.isValid() ? mTransform.destinationCrs() : QgsCoordinateReferenceSystem();
877 }
878 
setExtent(const QgsRectangle * extent)879 void QgsPointLocator::setExtent( const QgsRectangle *extent )
880 {
881   if ( mIsIndexing )
882     // already indexing, return!
883     return;
884 
885   mExtent.reset( extent ? new QgsRectangle( *extent ) : nullptr );
886 
887   destroyIndex();
888 }
889 
setRenderContext(const QgsRenderContext * context)890 void QgsPointLocator::setRenderContext( const QgsRenderContext *context )
891 {
892   if ( mIsIndexing )
893     // already indexing, return!
894     return;
895 
896   disconnect( mLayer, &QgsVectorLayer::styleChanged, this, &QgsPointLocator::destroyIndex );
897 
898   destroyIndex();
899   mContext.reset( nullptr );
900 
901   if ( context )
902   {
903     mContext = std::unique_ptr<QgsRenderContext>( new QgsRenderContext( *context ) );
904     connect( mLayer, &QgsVectorLayer::styleChanged, this, &QgsPointLocator::destroyIndex );
905   }
906 
907 }
908 
onInitTaskFinished()909 void QgsPointLocator::onInitTaskFinished()
910 {
911   // Check that we don't call this method twice, when calling waitForFinished
912   // for instance (because of taskCompleted signal)
913   if ( !mIsIndexing )
914     return;
915 
916   if ( mIsDestroying )
917     return;
918 
919   mIsIndexing = false;
920   mRenderer.reset();
921   mSource.reset();
922 
923   // treat added and deleted feature while indexing
924   for ( const QgsFeatureId fid : mAddedFeatures )
925     onFeatureAdded( fid );
926   mAddedFeatures.clear();
927 
928   for ( const QgsFeatureId fid : mDeletedFeatures )
929     onFeatureDeleted( fid );
930   mDeletedFeatures.clear();
931 
932   emit initFinished( mInitTask->isBuildOK() );
933 }
934 
init(int maxFeaturesToIndex,bool relaxed)935 bool QgsPointLocator::init( int maxFeaturesToIndex, bool relaxed )
936 {
937   const QgsWkbTypes::GeometryType geomType = mLayer->geometryType();
938   if ( geomType == QgsWkbTypes::NullGeometry // nothing to index
939        || hasIndex()
940        || mIsIndexing ) // already indexing, return!
941     return true;
942 
943   if ( !mLayer->dataProvider()
944        || !mLayer->dataProvider()->isValid() )
945     return false;
946 
947   mSource.reset( new QgsVectorLayerFeatureSource( mLayer ) );
948 
949   if ( mContext )
950   {
951     mRenderer.reset( mLayer->renderer() ? mLayer->renderer()->clone() : nullptr );
952     mContext->expressionContext() << QgsExpressionContextUtils::layerScope( mLayer );
953   }
954 
955   mIsIndexing = true;
956 
957   if ( relaxed )
958   {
959     mInitTask = new QgsPointLocatorInitTask( this );
960     connect( mInitTask, &QgsPointLocatorInitTask::taskTerminated, this, &QgsPointLocator::onInitTaskFinished );
961     connect( mInitTask, &QgsPointLocatorInitTask::taskCompleted, this, &QgsPointLocator::onInitTaskFinished );
962     QgsApplication::taskManager()->addTask( mInitTask );
963     return true;
964   }
965   else
966   {
967     const bool ok = rebuildIndex( maxFeaturesToIndex );
968     mIsIndexing = false;
969     emit initFinished( ok );
970     return ok;
971   }
972 }
973 
waitForIndexingFinished()974 void QgsPointLocator::waitForIndexingFinished()
975 {
976   mInitTask->waitForFinished();
977 
978   if ( !mIsDestroying )
979     onInitTaskFinished();
980 }
981 
hasIndex() const982 bool QgsPointLocator::hasIndex() const
983 {
984   return mIsIndexing || mRTree || mIsEmptyLayer;
985 }
986 
prepare(bool relaxed)987 bool QgsPointLocator::prepare( bool relaxed )
988 {
989   if ( mIsIndexing )
990   {
991     if ( relaxed )
992       return false;
993     else
994       waitForIndexingFinished();
995   }
996 
997   if ( !mRTree )
998   {
999     init( -1, relaxed );
1000     if ( ( relaxed && mIsIndexing ) || !mRTree ) // relaxed mode and currently indexing or still invalid?
1001       return false;
1002   }
1003 
1004   return true;
1005 }
1006 
rebuildIndex(int maxFeaturesToIndex)1007 bool QgsPointLocator::rebuildIndex( int maxFeaturesToIndex )
1008 {
1009   QElapsedTimer t;
1010   t.start();
1011 
1012   QgsDebugMsgLevel( QStringLiteral( "RebuildIndex start : %1" ).arg( mSource->id() ), 2 );
1013 
1014   destroyIndex();
1015 
1016   QLinkedList<RTree::Data *> dataList;
1017   QgsFeature f;
1018 
1019   QgsFeatureRequest request;
1020   request.setNoAttributes();
1021 
1022   if ( mExtent )
1023   {
1024     QgsRectangle rect = *mExtent;
1025     if ( mTransform.isValid() )
1026     {
1027       try
1028       {
1029         rect = mTransform.transformBoundingBox( rect, Qgis::TransformDirection::Reverse );
1030       }
1031       catch ( const QgsException &e )
1032       {
1033         Q_UNUSED( e )
1034         // See https://github.com/qgis/QGIS/issues/20749
1035         QgsDebugMsg( QStringLiteral( "could not transform bounding box to map, skipping the snap filter (%1)" ).arg( e.what() ) );
1036       }
1037     }
1038     request.setFilterRect( rect );
1039   }
1040 
1041   bool filter = false;
1042   QgsRenderContext *ctx = nullptr;
1043   if ( mContext )
1044   {
1045     ctx = mContext.get();
1046     if ( mRenderer )
1047     {
1048       // setup scale for scale dependent visibility (rule based)
1049       mRenderer->startRender( *ctx, mSource->fields() );
1050       filter = mRenderer->capabilities() & QgsFeatureRenderer::Filter;
1051       request.setSubsetOfAttributes( mRenderer->usedAttributes( *ctx ), mSource->fields() );
1052     }
1053   }
1054 
1055   QgsFeatureIterator fi = mSource->getFeatures( request );
1056   int indexedCount = 0;
1057 
1058   while ( fi.nextFeature( f ) )
1059   {
1060     if ( !f.hasGeometry() )
1061       continue;
1062 
1063     if ( filter && ctx && mRenderer )
1064     {
1065       ctx->expressionContext().setFeature( f );
1066       if ( !mRenderer->willRenderFeature( f, *ctx ) )
1067       {
1068         continue;
1069       }
1070     }
1071 
1072     if ( mTransform.isValid() )
1073     {
1074       try
1075       {
1076         QgsGeometry transformedGeometry = f.geometry();
1077         transformedGeometry.transform( mTransform );
1078         f.setGeometry( transformedGeometry );
1079       }
1080       catch ( const QgsException &e )
1081       {
1082         Q_UNUSED( e )
1083         // See https://github.com/qgis/QGIS/issues/20749
1084         QgsDebugMsg( QStringLiteral( "could not transform geometry to map, skipping the snap for it (%1)" ).arg( e.what() ) );
1085         continue;
1086       }
1087     }
1088 
1089     const QgsRectangle bbox = f.geometry().boundingBox();
1090     if ( bbox.isFinite() )
1091     {
1092       SpatialIndex::Region r( rect2region( bbox ) );
1093       dataList << new RTree::Data( 0, nullptr, r, f.id() );
1094 
1095       if ( mGeoms.contains( f.id() ) )
1096         delete mGeoms.take( f.id() );
1097       mGeoms[f.id()] = new QgsGeometry( f.geometry() );
1098       ++indexedCount;
1099     }
1100 
1101     if ( maxFeaturesToIndex != -1 && indexedCount > maxFeaturesToIndex )
1102     {
1103       qDeleteAll( dataList );
1104       destroyIndex();
1105       return false;
1106     }
1107   }
1108 
1109   // R-Tree parameters
1110   const double fillFactor = 0.7;
1111   const unsigned long indexCapacity = 10;
1112   const unsigned long leafCapacity = 10;
1113   const unsigned long dimension = 2;
1114   const RTree::RTreeVariant variant = RTree::RV_RSTAR;
1115   SpatialIndex::id_type indexId;
1116 
1117   if ( dataList.isEmpty() )
1118   {
1119     mIsEmptyLayer = true;
1120     return true; // no features
1121   }
1122 
1123   QgsPointLocator_Stream stream( dataList );
1124   mRTree.reset( RTree::createAndBulkLoadNewRTree( RTree::BLM_STR, stream, *mStorage, fillFactor, indexCapacity,
1125                 leafCapacity, dimension, variant, indexId ) );
1126 
1127   if ( ctx && mRenderer )
1128   {
1129     mRenderer->stopRender( *ctx );
1130   }
1131 
1132   QgsDebugMsgLevel( QStringLiteral( "RebuildIndex end : %1 ms (%2)" ).arg( t.elapsed() ).arg( mSource->id() ), 2 );
1133 
1134   return true;
1135 }
1136 
1137 
destroyIndex()1138 void QgsPointLocator::destroyIndex()
1139 {
1140   mRTree.reset();
1141 
1142   mIsEmptyLayer = false;
1143 
1144   qDeleteAll( mGeoms );
1145 
1146   mGeoms.clear();
1147 }
1148 
onFeatureAdded(QgsFeatureId fid)1149 void QgsPointLocator::onFeatureAdded( QgsFeatureId fid )
1150 {
1151   if ( mIsIndexing )
1152   {
1153     // will modify index once current indexing is finished
1154     mAddedFeatures << fid;
1155     return;
1156   }
1157 
1158   if ( !mRTree )
1159   {
1160     if ( mIsEmptyLayer )
1161     {
1162       // layer is not empty any more, let's build the index
1163       mIsEmptyLayer = false;
1164       init();
1165     }
1166     return; // nothing to do if we are not initialized yet
1167   }
1168 
1169   QgsFeature f;
1170   if ( mLayer->getFeatures( mContext ? QgsFeatureRequest( fid ) : QgsFeatureRequest( fid ).setNoAttributes() ).nextFeature( f ) )
1171   {
1172     if ( !f.hasGeometry() )
1173       return;
1174 
1175     if ( mContext )
1176     {
1177       std::unique_ptr< QgsFeatureRenderer > renderer( mLayer->renderer() ? mLayer->renderer()->clone() : nullptr );
1178       QgsRenderContext *ctx = nullptr;
1179 
1180       mContext->expressionContext() << QgsExpressionContextUtils::layerScope( mLayer );
1181       ctx = mContext.get();
1182       if ( renderer && ctx )
1183       {
1184         bool pass = false;
1185         renderer->startRender( *ctx, mLayer->fields() );
1186 
1187         ctx->expressionContext().setFeature( f );
1188         if ( !renderer->willRenderFeature( f, *ctx ) )
1189         {
1190           pass = true;
1191         }
1192 
1193         renderer->stopRender( *ctx );
1194         if ( pass )
1195           return;
1196       }
1197     }
1198 
1199     if ( mTransform.isValid() )
1200     {
1201       try
1202       {
1203         QgsGeometry transformedGeom = f.geometry();
1204         transformedGeom.transform( mTransform );
1205         f.setGeometry( transformedGeom );
1206       }
1207       catch ( const QgsException &e )
1208       {
1209         Q_UNUSED( e )
1210         // See https://github.com/qgis/QGIS/issues/20749
1211         QgsDebugMsg( QStringLiteral( "could not transform geometry to map, skipping the snap for it (%1)" ).arg( e.what() ) );
1212         return;
1213       }
1214     }
1215 
1216     const QgsRectangle bbox = f.geometry().boundingBox();
1217     if ( bbox.isFinite() )
1218     {
1219       const SpatialIndex::Region r( rect2region( bbox ) );
1220       mRTree->insertData( 0, nullptr, r, f.id() );
1221 
1222       if ( mGeoms.contains( f.id() ) )
1223         delete mGeoms.take( f.id() );
1224       mGeoms[fid] = new QgsGeometry( f.geometry() );
1225     }
1226   }
1227 }
1228 
onFeatureDeleted(QgsFeatureId fid)1229 void QgsPointLocator::onFeatureDeleted( QgsFeatureId fid )
1230 {
1231   if ( mIsIndexing )
1232   {
1233     if ( mAddedFeatures.contains( fid ) )
1234     {
1235       mAddedFeatures.remove( fid );
1236     }
1237     else
1238     {
1239       // will modify index once current indexing is finished
1240       mDeletedFeatures << fid;
1241     }
1242     return;
1243   }
1244 
1245   if ( !mRTree )
1246     return; // nothing to do if we are not initialized yet
1247 
1248   if ( mGeoms.contains( fid ) )
1249   {
1250     mRTree->deleteData( rect2region( mGeoms[fid]->boundingBox() ), fid );
1251     delete mGeoms.take( fid );
1252   }
1253 
1254 }
1255 
onGeometryChanged(QgsFeatureId fid,const QgsGeometry & geom)1256 void QgsPointLocator::onGeometryChanged( QgsFeatureId fid, const QgsGeometry &geom )
1257 {
1258   Q_UNUSED( geom )
1259   onFeatureDeleted( fid );
1260   onFeatureAdded( fid );
1261 }
1262 
onAttributeValueChanged(QgsFeatureId fid,int idx,const QVariant & value)1263 void QgsPointLocator::onAttributeValueChanged( QgsFeatureId fid, int idx, const QVariant &value )
1264 {
1265   Q_UNUSED( idx )
1266   Q_UNUSED( value )
1267   if ( mContext )
1268   {
1269     onFeatureDeleted( fid );
1270     onFeatureAdded( fid );
1271   }
1272 }
1273 
1274 
nearestVertex(const QgsPointXY & point,double tolerance,MatchFilter * filter,bool relaxed)1275 QgsPointLocator::Match QgsPointLocator::nearestVertex( const QgsPointXY &point, double tolerance, MatchFilter *filter, bool relaxed )
1276 {
1277   if ( !prepare( relaxed ) )
1278     return Match();
1279 
1280   Match m;
1281   QgsPointLocator_VisitorNearestVertex visitor( this, m, point, filter );
1282   const QgsRectangle rect( point.x() - tolerance, point.y() - tolerance, point.x() + tolerance, point.y() + tolerance );
1283   mRTree->intersectsWithQuery( rect2region( rect ), visitor );
1284   if ( m.isValid() && m.distance() > tolerance )
1285     return Match(); // make sure that only match strictly within the tolerance is returned
1286   return m;
1287 }
1288 
nearestCentroid(const QgsPointXY & point,double tolerance,MatchFilter * filter,bool relaxed)1289 QgsPointLocator::Match QgsPointLocator::nearestCentroid( const QgsPointXY &point, double tolerance, MatchFilter *filter, bool relaxed )
1290 {
1291   if ( !prepare( relaxed ) )
1292     return Match();
1293 
1294   Match m;
1295   QgsPointLocator_VisitorNearestCentroid visitor( this, m, point, filter );
1296 
1297   const QgsRectangle rect( point.x() - tolerance, point.y() - tolerance, point.x() + tolerance, point.y() + tolerance );
1298   mRTree->intersectsWithQuery( rect2region( rect ), visitor );
1299   if ( m.isValid() && m.distance() > tolerance )
1300     return Match(); // make sure that only match strictly within the tolerance is returned
1301   return m;
1302 }
1303 
nearestMiddleOfSegment(const QgsPointXY & point,double tolerance,MatchFilter * filter,bool relaxed)1304 QgsPointLocator::Match QgsPointLocator::nearestMiddleOfSegment( const QgsPointXY &point, double tolerance, MatchFilter *filter, bool relaxed )
1305 {
1306   if ( !prepare( relaxed ) )
1307     return Match();
1308 
1309   Match m;
1310   QgsPointLocator_VisitorNearestMiddleOfSegment visitor( this, m, point, filter );
1311 
1312   const QgsRectangle rect( point.x() - tolerance, point.y() - tolerance, point.x() + tolerance, point.y() + tolerance );
1313   mRTree->intersectsWithQuery( rect2region( rect ), visitor );
1314   if ( m.isValid() && m.distance() > tolerance )
1315     return Match(); // make sure that only match strictly within the tolerance is returned
1316   return m;
1317 }
1318 
nearestLineEndpoints(const QgsPointXY & point,double tolerance,QgsPointLocator::MatchFilter * filter,bool relaxed)1319 QgsPointLocator::Match QgsPointLocator::nearestLineEndpoints( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter, bool relaxed )
1320 {
1321   if ( !prepare( relaxed ) )
1322     return Match();
1323 
1324   Match m;
1325   QgsPointLocator_VisitorNearestLineEndpoint visitor( this, m, point, filter );
1326 
1327   const QgsRectangle rect( point.x() - tolerance, point.y() - tolerance, point.x() + tolerance, point.y() + tolerance );
1328   mRTree->intersectsWithQuery( rect2region( rect ), visitor );
1329   if ( m.isValid() && m.distance() > tolerance )
1330     return Match(); // make sure that only match strictly within the tolerance is returned
1331   return m;
1332 }
1333 
nearestEdge(const QgsPointXY & point,double tolerance,MatchFilter * filter,bool relaxed)1334 QgsPointLocator::Match QgsPointLocator::nearestEdge( const QgsPointXY &point, double tolerance, MatchFilter *filter, bool relaxed )
1335 {
1336   if ( !prepare( relaxed ) )
1337     return Match();
1338 
1339   const QgsWkbTypes::GeometryType geomType = mLayer->geometryType();
1340   if ( geomType == QgsWkbTypes::PointGeometry )
1341     return Match();
1342 
1343   Match m;
1344   QgsPointLocator_VisitorNearestEdge visitor( this, m, point, filter );
1345   const QgsRectangle rect( point.x() - tolerance, point.y() - tolerance, point.x() + tolerance, point.y() + tolerance );
1346   mRTree->intersectsWithQuery( rect2region( rect ), visitor );
1347   if ( m.isValid() && m.distance() > tolerance )
1348     return Match(); // make sure that only match strictly within the tolerance is returned
1349   return m;
1350 }
1351 
nearestArea(const QgsPointXY & point,double tolerance,MatchFilter * filter,bool relaxed)1352 QgsPointLocator::Match QgsPointLocator::nearestArea( const QgsPointXY &point, double tolerance, MatchFilter *filter, bool relaxed )
1353 {
1354   if ( !prepare( relaxed ) )
1355     return Match();
1356 
1357   const MatchList mlist = pointInPolygon( point );
1358   if ( !mlist.isEmpty() && mlist.at( 0 ).isValid() )
1359   {
1360     return mlist.at( 0 );
1361   }
1362 
1363   if ( tolerance == 0 )
1364   {
1365     return Match();
1366   }
1367 
1368   // discard point and line layers to keep only polygons
1369   const QgsWkbTypes::GeometryType geomType = mLayer->geometryType();
1370   if ( geomType == QgsWkbTypes::PointGeometry || geomType == QgsWkbTypes::LineGeometry )
1371     return Match();
1372 
1373   // use edges for adding tolerance
1374   const Match m = nearestEdge( point, tolerance, filter );
1375   if ( m.isValid() )
1376     return Match( Area, m.layer(), m.featureId(), m.distance(), m.point() );
1377   else
1378     return Match();
1379 }
1380 
1381 
edgesInRect(const QgsRectangle & rect,QgsPointLocator::MatchFilter * filter,bool relaxed)1382 QgsPointLocator::MatchList QgsPointLocator::edgesInRect( const QgsRectangle &rect, QgsPointLocator::MatchFilter *filter, bool relaxed )
1383 {
1384   if ( !prepare( relaxed ) )
1385     return MatchList();
1386 
1387   const QgsWkbTypes::GeometryType geomType = mLayer->geometryType();
1388   if ( geomType == QgsWkbTypes::PointGeometry )
1389     return MatchList();
1390 
1391   MatchList lst;
1392   QgsPointLocator_VisitorEdgesInRect visitor( this, lst, rect, filter );
1393   mRTree->intersectsWithQuery( rect2region( rect ), visitor );
1394 
1395   return lst;
1396 }
1397 
edgesInRect(const QgsPointXY & point,double tolerance,QgsPointLocator::MatchFilter * filter,bool relaxed)1398 QgsPointLocator::MatchList QgsPointLocator::edgesInRect( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter, bool relaxed )
1399 {
1400   const QgsRectangle rect( point.x() - tolerance, point.y() - tolerance, point.x() + tolerance, point.y() + tolerance );
1401   return edgesInRect( rect, filter, relaxed );
1402 }
1403 
verticesInRect(const QgsRectangle & rect,QgsPointLocator::MatchFilter * filter,bool relaxed)1404 QgsPointLocator::MatchList QgsPointLocator::verticesInRect( const QgsRectangle &rect, QgsPointLocator::MatchFilter *filter, bool relaxed )
1405 {
1406   if ( !prepare( relaxed ) )
1407     return MatchList();
1408 
1409   MatchList lst;
1410   QgsPointLocator_VisitorVerticesInRect visitor( this, lst, rect, filter );
1411   mRTree->intersectsWithQuery( rect2region( rect ), visitor );
1412 
1413   return lst;
1414 }
1415 
verticesInRect(const QgsPointXY & point,double tolerance,QgsPointLocator::MatchFilter * filter,bool relaxed)1416 QgsPointLocator::MatchList QgsPointLocator::verticesInRect( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter, bool relaxed )
1417 {
1418   const QgsRectangle rect( point.x() - tolerance, point.y() - tolerance, point.x() + tolerance, point.y() + tolerance );
1419   return verticesInRect( rect, filter, relaxed );
1420 }
1421 
pointInPolygon(const QgsPointXY & point,bool relaxed)1422 QgsPointLocator::MatchList QgsPointLocator::pointInPolygon( const QgsPointXY &point, bool relaxed )
1423 {
1424   if ( !prepare( relaxed ) )
1425     return MatchList();
1426 
1427   const QgsWkbTypes::GeometryType geomType = mLayer->geometryType();
1428   if ( geomType == QgsWkbTypes::PointGeometry || geomType == QgsWkbTypes::LineGeometry )
1429     return MatchList();
1430 
1431   MatchList lst;
1432   QgsPointLocator_VisitorArea visitor( this, point, lst );
1433   mRTree->intersectsWithQuery( point2point( point ), visitor );
1434   return lst;
1435 }
1436