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