QGIS API Documentation  3.27.0-Master (0e23467727)
qgspointlocator.cpp
Go to the documentation of this file.
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"
28 #include "qgslinestring.h"
29 #include "qgscurvepolygon.h"
30 #include "qgsrendercontext.h"
32 #include <spatialindex/SpatialIndex.h>
33 
34 #include <QLinkedListIterator>
35 #include <QtConcurrent>
36 
37 using namespace SpatialIndex;
38 
39 
40 
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 
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 
63 
64 
70 class QgsPointLocator_Stream : public IDataStream
71 {
72  public:
73  explicit QgsPointLocator_Stream( const QLinkedList<RTree::Data *> &dataList )
74  : mDataList( dataList )
75  , mIt( mDataList )
76  { }
77 
78  IData *getNext() override { return mIt.next(); }
79  bool hasNext() override { return mIt.hasNext(); }
80 
81  uint32_t size() override { Q_ASSERT( false && "not available" ); return 0; }
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 
91 
92 
98 class QgsPointLocator_VisitorNearestVertex : public IVisitor
99 {
100  public:
102  : mLocator( pl )
103  , mBest( m )
104  , mSrcPoint( srcPoint )
105  , mFilter( filter )
106  {}
107 
108  void visitNode( const INode &n ) override { Q_UNUSED( n ) }
109  void visitData( std::vector<const IData *> &v ) override { Q_UNUSED( v ) }
110 
111  void visitData( const IData &d ) override
112  {
113  const QgsFeatureId id = d.getIdentifier();
114  QgsGeometry *geom = mLocator->mGeoms.value( id );
115  if ( !geom )
116  return; // should not happen, but be safe
117  int vertexIndex, beforeVertex, afterVertex;
118  double sqrDist;
119 
120  const QgsPointXY pt = geom->closestVertex( mSrcPoint, vertexIndex, beforeVertex, afterVertex, sqrDist );
121  if ( sqrDist < 0 )
122  return; // probably empty geometry
123 
124  const QgsPointLocator::Match m( QgsPointLocator::Vertex, mLocator->mLayer, id, std::sqrt( sqrDist ), pt, vertexIndex );
125  // in range queries the filter may reject some matches
126  if ( mFilter && !mFilter->acceptMatch( m ) )
127  return;
128 
129  if ( !mBest.isValid() || m.distance() < mBest.distance() )
130  mBest = m;
131  }
132 
133  private:
134  QgsPointLocator *mLocator = nullptr;
135  QgsPointLocator::Match &mBest;
136  QgsPointXY mSrcPoint;
137  QgsPointLocator::MatchFilter *mFilter = nullptr;
138 };
139 
140 
141 
149 {
150  public:
151 
159  : mLocator( pl )
160  , mBest( m )
161  , mSrcPoint( srcPoint )
162  , mFilter( filter )
163  {}
164 
165  void visitNode( const INode &n ) override { Q_UNUSED( n ) }
166  void visitData( std::vector<const IData *> &v ) override { Q_UNUSED( v ) }
167 
168  void visitData( const IData &d ) override
169  {
170  const QgsFeatureId id = d.getIdentifier();
171  QgsGeometry *geom = mLocator->mGeoms.value( id );
172  if ( !geom )
173  return; // should not happen, but be safe
174 
175  const QgsPointXY pt = geom->centroid().asPoint();
176 
177  const QgsPointLocator::Match m( QgsPointLocator::Centroid, mLocator->mLayer, id, std::sqrt( mSrcPoint.sqrDist( pt ) ), pt, -1 );
178  // in range queries the filter may reject some matches
179  if ( mFilter && !mFilter->acceptMatch( m ) )
180  return;
181 
182  if ( !mBest.isValid() || m.distance() < mBest.distance() )
183  mBest = m;
184 
185  }
186 
187  private:
188  QgsPointLocator *mLocator = nullptr;
189  QgsPointLocator::Match &mBest;
190  QgsPointXY mSrcPoint;
191  QgsPointLocator::MatchFilter *mFilter = nullptr;
192 };
193 
195 
203 {
204  public:
205 
213  : mLocator( pl )
214  , mBest( m )
215  , mSrcPoint( srcPoint )
216  , mFilter( filter )
217  {}
218 
219  void visitNode( const INode &n ) override { Q_UNUSED( n ) }
220  void visitData( std::vector<const IData *> &v ) override { Q_UNUSED( v ) }
221 
222  void visitData( const IData &d ) override
223  {
224  const QgsFeatureId id = d.getIdentifier();
225  QgsGeometry *geom = mLocator->mGeoms.value( id );
226  if ( !geom )
227  return; // should not happen, but be safe
228 
229  QgsPointXY pt;
230  int afterVertex;
231  const double sqrDist = geom->closestSegmentWithContext( mSrcPoint, pt, afterVertex, nullptr, POINT_LOC_EPSILON );
232  if ( sqrDist < 0 )
233  return;
234 
235  QgsPointXY edgePoints[2];
236  edgePoints[0] = geom->vertexAt( afterVertex - 1 );
237  edgePoints[1] = geom->vertexAt( afterVertex );
238  pt = QgsPointXY( ( edgePoints[0].x() + edgePoints[1].x() ) / 2.0, ( edgePoints[0].y() + edgePoints[1].y() ) / 2.0 );
239 
240  const QgsPointLocator::Match m( QgsPointLocator::MiddleOfSegment, mLocator->mLayer, id, std::sqrt( mSrcPoint.sqrDist( pt ) ), pt, afterVertex - 1 );
241  // in range queries the filter may reject some matches
242  if ( mFilter && !mFilter->acceptMatch( m ) )
243  return;
244 
245  if ( !mBest.isValid() || m.distance() < mBest.distance() )
246  mBest = m;
247 
248  }
249 
250  private:
251  QgsPointLocator *mLocator = nullptr;
252  QgsPointLocator::Match &mBest;
253  QgsPointXY mSrcPoint;
254  QgsPointLocator::MatchFilter *mFilter = nullptr;
255 };
256 
258 
266 {
267  public:
268 
274  : mLocator( pl )
275  , mBest( m )
276  , mSrcPoint( srcPoint )
277  , mFilter( filter )
278  {}
279 
280  void visitNode( const INode &n ) override { Q_UNUSED( n ) }
281  void visitData( std::vector<const IData *> &v ) override { Q_UNUSED( v ) }
282 
283  void visitData( const IData &d ) override
284  {
285  const QgsFeatureId id = d.getIdentifier();
286  const QgsGeometry *geom = mLocator->mGeoms.value( id );
287  if ( !geom )
288  return; // should not happen, but be safe
289 
290  QgsPointXY bestPoint;
291  int bestVertexNumber = -1;
292  auto replaceIfBetter = [this, &bestPoint, &bestVertexNumber]( const QgsPoint & candidate, int vertexNumber )
293  {
294  if ( bestPoint.isEmpty() || candidate.distanceSquared( mSrcPoint.x(), mSrcPoint.y() ) < bestPoint.sqrDist( mSrcPoint ) )
295  {
296  bestPoint = QgsPointXY( candidate );
297  bestVertexNumber = vertexNumber;
298  }
299  };
300 
301  switch ( QgsWkbTypes::geometryType( geom->wkbType() ) )
302  {
306  return;
307 
309  {
310  int partStartVertexNum = 0;
311  for ( auto partIt = geom->const_parts_begin(); partIt != geom->const_parts_end(); ++partIt )
312  {
313  if ( const QgsCurve *curve = qgsgeometry_cast< const QgsCurve * >( *partIt ) )
314  {
315  replaceIfBetter( curve->startPoint(), partStartVertexNum );
316  replaceIfBetter( curve->endPoint(), partStartVertexNum + curve->numPoints() - 1 );
317  partStartVertexNum += curve->numPoints();
318  }
319  }
320  break;
321  }
322 
324  {
325  int partStartVertexNum = 0;
326  for ( auto partIt = geom->const_parts_begin(); partIt != geom->const_parts_end(); ++partIt )
327  {
328  if ( const QgsCurvePolygon *polygon = qgsgeometry_cast< const QgsCurvePolygon * >( *partIt ) )
329  {
330  if ( polygon->exteriorRing() )
331  {
332  replaceIfBetter( polygon->exteriorRing()->startPoint(), partStartVertexNum );
333  partStartVertexNum += polygon->exteriorRing()->numPoints();
334  }
335  for ( int i = 0; i < polygon->numInteriorRings(); ++i )
336  {
337  const QgsCurve *ring = polygon->interiorRing( i );
338  replaceIfBetter( ring->startPoint(), partStartVertexNum );
339  partStartVertexNum += ring->numPoints();
340  }
341  }
342  }
343  break;
344  }
345  }
346 
347  const QgsPointLocator::Match m( QgsPointLocator::LineEndpoint, mLocator->mLayer, id, std::sqrt( mSrcPoint.sqrDist( bestPoint ) ), bestPoint, bestVertexNumber );
348  // in range queries the filter may reject some matches
349  if ( mFilter && !mFilter->acceptMatch( m ) )
350  return;
351 
352  if ( !mBest.isValid() || m.distance() < mBest.distance() )
353  mBest = m;
354  }
355 
356  private:
357  QgsPointLocator *mLocator = nullptr;
358  QgsPointLocator::Match &mBest;
359  QgsPointXY mSrcPoint;
360  QgsPointLocator::MatchFilter *mFilter = nullptr;
361 };
362 
363 
365 
366 
372 class QgsPointLocator_VisitorNearestEdge : public IVisitor
373 {
374  public:
376  : mLocator( pl )
377  , mBest( m )
378  , mSrcPoint( srcPoint )
379  , mFilter( filter )
380  {}
381 
382  void visitNode( const INode &n ) override { Q_UNUSED( n ) }
383  void visitData( std::vector<const IData *> &v ) override { Q_UNUSED( v ) }
384 
385  void visitData( const IData &d ) override
386  {
387  const QgsFeatureId id = d.getIdentifier();
388  QgsGeometry *geom = mLocator->mGeoms.value( id );
389  if ( !geom )
390  return; // should not happen, but be safe
391 
392  QgsPointXY pt;
393  int afterVertex;
394  const double sqrDist = geom->closestSegmentWithContext( mSrcPoint, pt, afterVertex, nullptr, POINT_LOC_EPSILON );
395  if ( sqrDist < 0 )
396  return;
397 
398  QgsPointXY edgePoints[2];
399  edgePoints[0] = geom->vertexAt( afterVertex - 1 );
400  edgePoints[1] = geom->vertexAt( afterVertex );
401  const QgsPointLocator::Match m( QgsPointLocator::Edge, mLocator->mLayer, id, std::sqrt( sqrDist ), pt, afterVertex - 1, edgePoints );
402  // in range queries the filter may reject some matches
403  if ( mFilter && !mFilter->acceptMatch( m ) )
404  return;
405 
406  if ( !mBest.isValid() || m.distance() < mBest.distance() )
407  mBest = m;
408  }
409 
410  private:
411  QgsPointLocator *mLocator = nullptr;
412  QgsPointLocator::Match &mBest;
413  QgsPointXY mSrcPoint;
414  QgsPointLocator::MatchFilter *mFilter = nullptr;
415 };
416 
417 
419 
425 class QgsPointLocator_VisitorArea : public IVisitor
426 {
427  public:
430  : mLocator( pl )
431  , mList( list )
432  , mGeomPt( QgsGeometry::fromPointXY( origPt ) )
433  {}
434 
435  void visitNode( const INode &n ) override { Q_UNUSED( n ) }
436  void visitData( std::vector<const IData *> &v ) override { Q_UNUSED( v ) }
437 
438  void visitData( const IData &d ) override
439  {
440  const QgsFeatureId id = d.getIdentifier();
441  QgsGeometry *g = mLocator->mGeoms.value( id );
442  if ( !g )
443  return; // should not happen, but be safe
444 
445  if ( g->intersects( mGeomPt ) )
446  mList << QgsPointLocator::Match( QgsPointLocator::Area, mLocator->mLayer, id, 0, mGeomPt.asPoint() );
447  }
448  private:
449  QgsPointLocator *mLocator = nullptr;
451  QgsGeometry mGeomPt;
452 };
453 
454 
456 
457 // code adapted from
458 // http://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm
460 {
461  explicit _CohenSutherland( const QgsRectangle &rect ) : mRect( rect ) {}
462 
463  typedef int OutCode;
464 
465  static const int INSIDE = 0; // 0000
466  static const int LEFT = 1; // 0001
467  static const int RIGHT = 2; // 0010
468  static const int BOTTOM = 4; // 0100
469  static const int TOP = 8; // 1000
470 
472 
473  OutCode computeOutCode( double x, double y )
474  {
475  OutCode code = INSIDE; // initialized as being inside of clip window
476  if ( x < mRect.xMinimum() ) // to the left of clip window
477  code |= LEFT;
478  else if ( x > mRect.xMaximum() ) // to the right of clip window
479  code |= RIGHT;
480  if ( y < mRect.yMinimum() ) // below the clip window
481  code |= BOTTOM;
482  else if ( y > mRect.yMaximum() ) // above the clip window
483  code |= TOP;
484  return code;
485  }
486 
487  bool isSegmentInRect( double x0, double y0, double x1, double y1 )
488  {
489  // compute outcodes for P0, P1, and whatever point lies outside the clip rectangle
490  OutCode outcode0 = computeOutCode( x0, y0 );
491  OutCode outcode1 = computeOutCode( x1, y1 );
492  bool accept = false;
493 
494  while ( true )
495  {
496  if ( !( outcode0 | outcode1 ) )
497  {
498  // Bitwise OR is 0. Trivially accept and get out of loop
499  accept = true;
500  break;
501  }
502  else if ( outcode0 & outcode1 )
503  {
504  // Bitwise AND is not 0. Trivially reject and get out of loop
505  break;
506  }
507  else
508  {
509  // failed both tests, so calculate the line segment to clip
510  // from an outside point to an intersection with clip edge
511  double x, y;
512 
513  // At least one endpoint is outside the clip rectangle; pick it.
514  const OutCode outcodeOut = outcode0 ? outcode0 : outcode1;
515 
516  // Now find the intersection point;
517  // use formulas y = y0 + slope * (x - x0), x = x0 + (1 / slope) * (y - y0)
518  if ( outcodeOut & TOP )
519  {
520  // point is above the clip rectangle
521  x = x0 + ( x1 - x0 ) * ( mRect.yMaximum() - y0 ) / ( y1 - y0 );
522  y = mRect.yMaximum();
523  }
524  else if ( outcodeOut & BOTTOM )
525  {
526  // point is below the clip rectangle
527  x = x0 + ( x1 - x0 ) * ( mRect.yMinimum() - y0 ) / ( y1 - y0 );
528  y = mRect.yMinimum();
529  }
530  else if ( outcodeOut & RIGHT )
531  {
532  // point is to the right of clip rectangle
533  y = y0 + ( y1 - y0 ) * ( mRect.xMaximum() - x0 ) / ( x1 - x0 );
534  x = mRect.xMaximum();
535  }
536  else if ( outcodeOut & LEFT )
537  {
538  // point is to the left of clip rectangle
539  y = y0 + ( y1 - y0 ) * ( mRect.xMinimum() - x0 ) / ( x1 - x0 );
540  x = mRect.xMinimum();
541  }
542  else
543  break;
544 
545  // Now we move outside point to intersection point to clip
546  // and get ready for next pass.
547  if ( outcodeOut == outcode0 )
548  {
549  x0 = x;
550  y0 = y;
551  outcode0 = computeOutCode( x0, y0 );
552  }
553  else
554  {
555  x1 = x;
556  y1 = y;
557  outcode1 = computeOutCode( x1, y1 );
558  }
559  }
560  }
561  return accept;
562  }
563 };
564 
565 
566 static QgsPointLocator::MatchList _geometrySegmentsInRect( QgsGeometry *geom, const QgsRectangle &rect, QgsVectorLayer *vl, QgsFeatureId fid )
567 {
568  // this code is stupidly based on QgsGeometry::closestSegmentWithContext
569  // we need iterator for segments...
570 
572 
573  // geom is converted to a MultiCurve
574  QgsGeometry straightGeom = geom->convertToType( QgsWkbTypes::LineGeometry, true );
575  // and convert to straight segemnt / converts curve to linestring
576  straightGeom.convertToStraightSegment();
577 
578  // so, you must have multilinestring
579  //
580  // Special case: Intersections cannot be done on an empty linestring like
581  // QgsGeometry(QgsLineString()) or QgsGeometry::fromWkt("LINESTRING EMPTY")
582  if ( straightGeom.isEmpty() || ( ( straightGeom.type() != QgsWkbTypes::LineGeometry ) && ( !straightGeom.isMultipart() ) ) )
583  return lst;
584 
585  _CohenSutherland cs( rect );
586 
587  int pointIndex = 0;
588  for ( auto part = straightGeom.const_parts_begin(); part != straightGeom.const_parts_end(); ++part )
589  {
590  // Checking for invalid linestrings
591  // A linestring should/(must?) have at least two points.
592  QgsCurve *curve = qgsgeometry_cast<QgsCurve *>( *part );
593  Q_ASSERT( !curve->hasCurvedSegments() );
594  if ( curve->numPoints() < 2 )
595  continue;
596 
597  QgsAbstractGeometry::vertex_iterator it = ( *part )->vertices_begin();
598  QgsPointXY prevPoint( *it );
599  it++;
600  while ( it != ( *part )->vertices_end() )
601  {
602  const QgsPointXY thisPoint( *it );
603  if ( cs.isSegmentInRect( prevPoint.x(), prevPoint.y(), thisPoint.x(), thisPoint.y() ) )
604  {
605  QgsPointXY edgePoints[2];
606  edgePoints[0] = prevPoint;
607  edgePoints[1] = thisPoint;
608  lst << QgsPointLocator::Match( QgsPointLocator::Edge, vl, fid, 0, QgsPointXY(), pointIndex - 1, edgePoints );
609  }
610  prevPoint = QgsPointXY( *it );
611  it++;
612  pointIndex += 1;
613 
614  }
615  }
616  return lst;
617 }
618 
624 class QgsPointLocator_VisitorEdgesInRect : public IVisitor
625 {
626  public:
628  : mLocator( pl )
629  , mList( lst )
630  , mSrcRect( srcRect )
631  , mFilter( filter )
632  {}
633 
634  void visitNode( const INode &n ) override { Q_UNUSED( n ) }
635  void visitData( std::vector<const IData *> &v ) override { Q_UNUSED( v ) }
636 
637  void visitData( const IData &d ) override
638  {
639  const QgsFeatureId id = d.getIdentifier();
640  QgsGeometry *geom = mLocator->mGeoms.value( id );
641  if ( !geom )
642  return; // should not happen, but be safe
643 
644  const auto segmentsInRect {_geometrySegmentsInRect( geom, mSrcRect, mLocator->mLayer, id )};
645  for ( const QgsPointLocator::Match &m : segmentsInRect )
646  {
647  // in range queries the filter may reject some matches
648  if ( mFilter && !mFilter->acceptMatch( m ) )
649  continue;
650 
651  mList << m;
652  }
653  }
654 
655  private:
656  QgsPointLocator *mLocator = nullptr;
658  QgsRectangle mSrcRect;
659  QgsPointLocator::MatchFilter *mFilter = nullptr;
660 };
661 
663 
671 {
672  public:
675  : mLocator( pl )
676  , mList( lst )
677  , mSrcRect( srcRect )
678  , mFilter( filter )
679  {}
680 
681  void visitNode( const INode &n ) override { Q_UNUSED( n ) }
682  void visitData( std::vector<const IData *> &v ) override { Q_UNUSED( v ) }
683 
684  void visitData( const IData &d ) override
685  {
686  const QgsFeatureId id = d.getIdentifier();
687  const QgsGeometry *geom = mLocator->mGeoms.value( id );
688  if ( !geom )
689  return; // should not happen, but be safe
690 
691  for ( QgsAbstractGeometry::vertex_iterator it = geom->vertices_begin(); it != geom->vertices_end(); ++it )
692  {
693  if ( mSrcRect.contains( *it ) )
694  {
695  const QgsPointLocator::Match m( QgsPointLocator::Vertex, mLocator->mLayer, id, 0, *it, geom->vertexNrFromVertexId( it.vertexId() ) );
696 
697  // in range queries the filter may reject some matches
698  if ( mFilter && !mFilter->acceptMatch( m ) )
699  continue;
700 
701  mList << m;
702  }
703  }
704  }
705 
706  private:
707  QgsPointLocator *mLocator = nullptr;
709  QgsRectangle mSrcRect;
710  QgsPointLocator::MatchFilter *mFilter = nullptr;
711 };
712 
720 {
721  public:
724  : mLocator( pl )
725  , mList( lst )
726  , mSrcRect( srcRect )
727  , mFilter( filter )
728  {}
729 
730  void visitNode( const INode &n ) override { Q_UNUSED( n ); }
731  void visitData( std::vector<const IData *> &v ) override { Q_UNUSED( v ); }
732 
733  void visitData( const IData &d ) override
734  {
735  const QgsFeatureId id = d.getIdentifier();
736  const QgsGeometry *geom = mLocator->mGeoms.value( id );
737  if ( !geom )
738  return; // should not happen, but be safe
739 
740  const QgsPointXY centroid = geom->centroid().asPoint();
741  if ( mSrcRect.contains( centroid ) )
742  {
743  const QgsPointLocator::Match m( QgsPointLocator::Centroid, mLocator->mLayer, id, 0, centroid, -1 );
744 
745  // in range queries the filter may reject some matches
746  if ( !( mFilter && !mFilter->acceptMatch( m ) ) )
747  mList << m;
748  }
749  }
750 
751  private:
752  QgsPointLocator *mLocator = nullptr;
754  QgsRectangle mSrcRect;
755  QgsPointLocator::MatchFilter *mFilter = nullptr;
756 };
757 
765 {
766  public:
769  : mLocator( pl )
770  , mList( lst )
771  , mSrcRect( srcRect )
772  , mFilter( filter )
773  {}
774 
775  void visitNode( const INode &n ) override { Q_UNUSED( n ); }
776  void visitData( std::vector<const IData *> &v ) override { Q_UNUSED( v ); }
777 
778  void visitData( const IData &d ) override
779  {
780  const QgsFeatureId id = d.getIdentifier();
781  const QgsGeometry *geom = mLocator->mGeoms.value( id );
782  if ( !geom )
783  return; // should not happen, but be safe
784 
785  for ( QgsAbstractGeometry::const_part_iterator itPart = geom->const_parts_begin() ; itPart != geom->const_parts_end() ; ++itPart )
786  {
787  QgsAbstractGeometry::vertex_iterator it = ( *itPart )->vertices_begin();
788  QgsAbstractGeometry::vertex_iterator itPrevious = ( *itPart )->vertices_begin();
789  it++;
790  for ( ; it != geom->vertices_end(); ++it, ++itPrevious )
791  {
792  const QgsPointXY pt( ( ( *itPrevious ).x() + ( *it ).x() ) / 2.0, ( ( *itPrevious ).y() + ( *it ).y() ) / 2.0 );
793  if ( mSrcRect.contains( pt ) )
794  {
795  const QgsPointLocator::Match m( QgsPointLocator::MiddleOfSegment, mLocator->mLayer, id, 0, pt, geom->vertexNrFromVertexId( it.vertexId() ) );
796 
797  // in range queries the filter may reject some matches
798  if ( mFilter && !mFilter->acceptMatch( m ) )
799  continue;
800 
801  mList << m;
802  }
803  }
804  }
805  }
806 
807  private:
808  QgsPointLocator *mLocator = nullptr;
810  QgsRectangle mSrcRect;
811  QgsPointLocator::MatchFilter *mFilter = nullptr;
812 };
813 
815 #include <QStack>
816 
822 class QgsPointLocator_DumpTree : public SpatialIndex::IQueryStrategy
823 {
824  private:
825  QStack<id_type> ids;
826 
827  public:
828 
829  void getNextEntry( const IEntry &entry, id_type &nextEntry, bool &hasNext ) override
830  {
831  const INode *n = dynamic_cast<const INode *>( &entry );
832  if ( !n )
833  return;
834 
835  QgsDebugMsgLevel( QStringLiteral( "NODE: %1" ).arg( n->getIdentifier() ), 4 );
836  if ( n->getLevel() > 0 )
837  {
838  // inner nodes
839  for ( uint32_t cChild = 0; cChild < n->getChildrenCount(); cChild++ )
840  {
841  QgsDebugMsgLevel( QStringLiteral( "- CH: %1" ).arg( n->getChildIdentifier( cChild ) ), 4 );
842  ids.push( n->getChildIdentifier( cChild ) );
843  }
844  }
845  else
846  {
847  // leaves
848  for ( uint32_t cChild = 0; cChild < n->getChildrenCount(); cChild++ )
849  {
850  QgsDebugMsgLevel( QStringLiteral( "- L: %1" ).arg( n->getChildIdentifier( cChild ) ), 4 );
851  }
852  }
853 
854  if ( ! ids.empty() )
855  {
856  nextEntry = ids.back();
857  ids.pop();
858  hasNext = true;
859  }
860  else
861  hasNext = false;
862  }
863 };
864 
866 
867 
869  : mLayer( layer )
870 {
871  if ( destCRS.isValid() )
872  {
873  mTransform = QgsCoordinateTransform( layer->crs(), destCRS, transformContext );
874  }
875 
876  setExtent( extent );
877 
878  mStorage.reset( StorageManager::createNewMemoryStorageManager() );
879 
880  connect( mLayer, &QgsVectorLayer::featureAdded, this, &QgsPointLocator::onFeatureAdded );
881  connect( mLayer, &QgsVectorLayer::featureDeleted, this, &QgsPointLocator::onFeatureDeleted );
882  connect( mLayer, &QgsVectorLayer::geometryChanged, this, &QgsPointLocator::onGeometryChanged );
883  connect( mLayer, &QgsVectorLayer::attributeValueChanged, this, &QgsPointLocator::onAttributeValueChanged );
885 }
886 
887 
889 {
890  // don't delete a locator if there is an indexing task running on it
891  mIsDestroying = true;
892  if ( mIsIndexing )
894 
895  destroyIndex();
896 }
897 
899 {
900  return mTransform.isValid() ? mTransform.destinationCrs() : QgsCoordinateReferenceSystem();
901 }
902 
904 {
905  if ( mIsIndexing )
906  // already indexing, return!
907  return;
908 
909  mExtent.reset( extent ? new QgsRectangle( *extent ) : nullptr );
910 
911  destroyIndex();
912 }
913 
915 {
916  if ( mIsIndexing )
917  // already indexing, return!
918  return;
919 
920  disconnect( mLayer, &QgsVectorLayer::styleChanged, this, &QgsPointLocator::destroyIndex );
921 
922  destroyIndex();
923  mContext.reset( nullptr );
924 
925  if ( context )
926  {
927  mContext = std::unique_ptr<QgsRenderContext>( new QgsRenderContext( *context ) );
929  }
930 
931 }
932 
933 void QgsPointLocator::onInitTaskFinished()
934 {
935  Q_ASSERT_X( QThread::currentThread() == qApp->thread(), "QgsPointLocator::onInitTaskFinished", "was not called on main thread" );
936 
937  // Check that we don't call this method twice, when calling waitForFinished
938  // for instance (because of taskCompleted signal)
939  if ( !mIsIndexing )
940  return;
941 
942  if ( mIsDestroying )
943  return;
944 
945  mIsIndexing = false;
946  mRenderer.reset();
947  mSource.reset();
948 
949  // treat added and deleted feature while indexing
950  for ( const QgsFeatureId fid : std::as_const( mAddedFeatures ) )
951  onFeatureAdded( fid );
952  mAddedFeatures.clear();
953 
954  for ( const QgsFeatureId fid : std::as_const( mDeletedFeatures ) )
955  onFeatureDeleted( fid );
956  mDeletedFeatures.clear();
957 
958  emit initFinished( mInitTask->isBuildOK() );
959 }
960 
961 bool QgsPointLocator::init( int maxFeaturesToIndex, bool relaxed )
962 {
963  const QgsWkbTypes::GeometryType geomType = mLayer->geometryType();
964  if ( geomType == QgsWkbTypes::NullGeometry // nothing to index
965  || hasIndex()
966  || mIsIndexing ) // already indexing, return!
967  return true;
968 
969  if ( !mLayer->dataProvider()
970  || !mLayer->dataProvider()->isValid() )
971  return false;
972 
973  mSource.reset( new QgsVectorLayerFeatureSource( mLayer ) );
974 
975  if ( mContext )
976  {
977  mRenderer.reset( mLayer->renderer() ? mLayer->renderer()->clone() : nullptr );
978  mContext->expressionContext() << QgsExpressionContextUtils::layerScope( mLayer );
979  }
980 
981  mIsIndexing = true;
982 
983  if ( relaxed )
984  {
985  mInitTask = new QgsPointLocatorInitTask( this );
986  connect( mInitTask, &QgsPointLocatorInitTask::taskTerminated, this, &QgsPointLocator::onInitTaskFinished );
987  connect( mInitTask, &QgsPointLocatorInitTask::taskCompleted, this, &QgsPointLocator::onInitTaskFinished );
988  QgsApplication::taskManager()->addTask( mInitTask );
989  return true;
990  }
991  else
992  {
993  const bool ok = rebuildIndex( maxFeaturesToIndex );
994  mIsIndexing = false;
995  emit initFinished( ok );
996  return ok;
997  }
998 }
999 
1001 {
1002  disconnect( mInitTask, &QgsPointLocatorInitTask::taskTerminated, this, &QgsPointLocator::onInitTaskFinished );
1003  disconnect( mInitTask, &QgsPointLocatorInitTask::taskCompleted, this, &QgsPointLocator::onInitTaskFinished );
1004  mInitTask->waitForFinished();
1005 
1006  if ( !mIsDestroying )
1007  onInitTaskFinished();
1008 }
1009 
1011 {
1012  return mIsIndexing || mRTree || mIsEmptyLayer;
1013 }
1014 
1015 bool QgsPointLocator::prepare( bool relaxed )
1016 {
1017  if ( mIsIndexing )
1018  {
1019  if ( relaxed )
1020  return false;
1021  else
1023  }
1024 
1025  if ( !mRTree )
1026  {
1027  init( -1, relaxed );
1028  if ( ( relaxed && mIsIndexing ) || !mRTree ) // relaxed mode and currently indexing or still invalid?
1029  return false;
1030  }
1031 
1032  return true;
1033 }
1034 
1035 bool QgsPointLocator::rebuildIndex( int maxFeaturesToIndex )
1036 {
1037  QElapsedTimer t;
1038  t.start();
1039 
1040  QgsDebugMsgLevel( QStringLiteral( "RebuildIndex start : %1" ).arg( mSource->id() ), 2 );
1041 
1042  destroyIndex();
1043 
1044  QLinkedList<RTree::Data *> dataList;
1045  QgsFeature f;
1046 
1047  QgsFeatureRequest request;
1048  request.setNoAttributes();
1049 
1050  if ( mExtent )
1051  {
1052  QgsRectangle rect = *mExtent;
1053  if ( !mTransform.isShortCircuited() )
1054  {
1055  QgsCoordinateTransform rectTransform = mTransform;
1056  rectTransform.setBallparkTransformsAreAppropriate( true );
1057  try
1058  {
1059  rect = rectTransform.transformBoundingBox( rect, Qgis::TransformDirection::Reverse );
1060  }
1061  catch ( const QgsException &e )
1062  {
1063  Q_UNUSED( e )
1064  // See https://github.com/qgis/QGIS/issues/20749
1065  QgsDebugMsg( QStringLiteral( "could not transform bounding box to map, skipping the snap filter (%1)" ).arg( e.what() ) );
1066  }
1067  }
1068  request.setFilterRect( rect );
1069  }
1070 
1071  bool filter = false;
1072  QgsRenderContext *ctx = nullptr;
1073  if ( mContext )
1074  {
1075  ctx = mContext.get();
1076  if ( mRenderer )
1077  {
1078  // setup scale for scale dependent visibility (rule based)
1079  mRenderer->startRender( *ctx, mSource->fields() );
1080  filter = mRenderer->capabilities() & QgsFeatureRenderer::Filter;
1081  request.setSubsetOfAttributes( mRenderer->usedAttributes( *ctx ), mSource->fields() );
1082  }
1083  }
1084 
1085  QgsFeatureIterator fi = mSource->getFeatures( request );
1086  int indexedCount = 0;
1087 
1088  while ( fi.nextFeature( f ) )
1089  {
1090  if ( !f.hasGeometry() )
1091  continue;
1092 
1093  if ( filter && ctx && mRenderer )
1094  {
1095  ctx->expressionContext().setFeature( f );
1096  if ( !mRenderer->willRenderFeature( f, *ctx ) )
1097  {
1098  continue;
1099  }
1100  }
1101 
1102  if ( mTransform.isValid() )
1103  {
1104  try
1105  {
1106  QgsGeometry transformedGeometry = f.geometry();
1107  transformedGeometry.transform( mTransform );
1108  f.setGeometry( transformedGeometry );
1109  }
1110  catch ( const QgsException &e )
1111  {
1112  Q_UNUSED( e )
1113  // See https://github.com/qgis/QGIS/issues/20749
1114  QgsDebugMsg( QStringLiteral( "could not transform geometry to map, skipping the snap for it (%1)" ).arg( e.what() ) );
1115  continue;
1116  }
1117  }
1118 
1119  const QgsRectangle bbox = f.geometry().boundingBox();
1120  if ( bbox.isFinite() )
1121  {
1122  SpatialIndex::Region r( rect2region( bbox ) );
1123  dataList << new RTree::Data( 0, nullptr, r, f.id() );
1124 
1125  auto it = mGeoms.find( f.id() );
1126  if ( it != mGeoms.end() )
1127  {
1128  delete *it;
1129  *it = new QgsGeometry( f.geometry() );
1130  }
1131  else
1132  {
1133  mGeoms[f.id()] = new QgsGeometry( f.geometry() );
1134  }
1135  ++indexedCount;
1136  }
1137 
1138  if ( maxFeaturesToIndex != -1 && indexedCount > maxFeaturesToIndex )
1139  {
1140  qDeleteAll( dataList );
1141  destroyIndex();
1142  return false;
1143  }
1144  }
1145 
1146  // R-Tree parameters
1147  const double fillFactor = 0.7;
1148  const unsigned long indexCapacity = 10;
1149  const unsigned long leafCapacity = 10;
1150  const unsigned long dimension = 2;
1151  const RTree::RTreeVariant variant = RTree::RV_RSTAR;
1152  SpatialIndex::id_type indexId;
1153 
1154  if ( dataList.isEmpty() )
1155  {
1156  mIsEmptyLayer = true;
1157  return true; // no features
1158  }
1159 
1160  QgsPointLocator_Stream stream( dataList );
1161  mRTree.reset( RTree::createAndBulkLoadNewRTree( RTree::BLM_STR, stream, *mStorage, fillFactor, indexCapacity,
1162  leafCapacity, dimension, variant, indexId ) );
1163 
1164  if ( ctx && mRenderer )
1165  {
1166  mRenderer->stopRender( *ctx );
1167  }
1168 
1169  QgsDebugMsgLevel( QStringLiteral( "RebuildIndex end : %1 ms (%2)" ).arg( t.elapsed() ).arg( mSource->id() ), 2 );
1170 
1171  return true;
1172 }
1173 
1174 
1176 {
1177  mRTree.reset();
1178 
1179  mIsEmptyLayer = false;
1180 
1181  qDeleteAll( mGeoms );
1182 
1183  mGeoms.clear();
1184 }
1185 
1186 void QgsPointLocator::onFeatureAdded( QgsFeatureId fid )
1187 {
1188  if ( mIsIndexing )
1189  {
1190  // will modify index once current indexing is finished
1191  mAddedFeatures << fid;
1192  return;
1193  }
1194 
1195  if ( !mRTree )
1196  {
1197  if ( mIsEmptyLayer )
1198  {
1199  // layer is not empty any more, let's build the index
1200  mIsEmptyLayer = false;
1201  init();
1202  }
1203  return; // nothing to do if we are not initialized yet
1204  }
1205 
1206  QgsFeature f;
1207  if ( mLayer->getFeatures( mContext ? QgsFeatureRequest( fid ) : QgsFeatureRequest( fid ).setNoAttributes() ).nextFeature( f ) )
1208  {
1209  if ( !f.hasGeometry() )
1210  return;
1211 
1212  if ( mContext )
1213  {
1214  std::unique_ptr< QgsFeatureRenderer > renderer( mLayer->renderer() ? mLayer->renderer()->clone() : nullptr );
1215  QgsRenderContext *ctx = nullptr;
1216 
1217  mContext->expressionContext() << QgsExpressionContextUtils::layerScope( mLayer );
1218  ctx = mContext.get();
1219  if ( renderer && ctx )
1220  {
1221  bool pass = false;
1222  renderer->startRender( *ctx, mLayer->fields() );
1223 
1224  ctx->expressionContext().setFeature( f );
1225  if ( !renderer->willRenderFeature( f, *ctx ) )
1226  {
1227  pass = true;
1228  }
1229 
1230  renderer->stopRender( *ctx );
1231  if ( pass )
1232  return;
1233  }
1234  }
1235 
1236  if ( mTransform.isValid() )
1237  {
1238  try
1239  {
1240  QgsGeometry transformedGeom = f.geometry();
1241  transformedGeom.transform( mTransform );
1242  f.setGeometry( transformedGeom );
1243  }
1244  catch ( const QgsException &e )
1245  {
1246  Q_UNUSED( e )
1247  // See https://github.com/qgis/QGIS/issues/20749
1248  QgsDebugMsg( QStringLiteral( "could not transform geometry to map, skipping the snap for it (%1)" ).arg( e.what() ) );
1249  return;
1250  }
1251  }
1252 
1253  const QgsRectangle bbox = f.geometry().boundingBox();
1254  if ( bbox.isFinite() )
1255  {
1256  const SpatialIndex::Region r( rect2region( bbox ) );
1257  mRTree->insertData( 0, nullptr, r, f.id() );
1258 
1259  auto it = mGeoms.find( f.id() );
1260  if ( it != mGeoms.end() )
1261  {
1262  delete *it;
1263  *it = new QgsGeometry( f.geometry() );
1264  }
1265  else
1266  {
1267  mGeoms[fid] = new QgsGeometry( f.geometry() );
1268  }
1269  }
1270  }
1271 }
1272 
1273 void QgsPointLocator::onFeatureDeleted( QgsFeatureId fid )
1274 {
1275  if ( mIsIndexing )
1276  {
1277  if ( mAddedFeatures.contains( fid ) )
1278  {
1279  mAddedFeatures.remove( fid );
1280  }
1281  else
1282  {
1283  // will modify index once current indexing is finished
1284  mDeletedFeatures << fid;
1285  }
1286  return;
1287  }
1288 
1289  if ( !mRTree )
1290  return; // nothing to do if we are not initialized yet
1291 
1292  auto it = mGeoms.find( fid );
1293  if ( it != mGeoms.end() )
1294  {
1295  mRTree->deleteData( rect2region( ( *it )->boundingBox() ), fid );
1296  delete *it;
1297  mGeoms.erase( it );
1298  }
1299 
1300 }
1301 
1302 void QgsPointLocator::onGeometryChanged( QgsFeatureId fid, const QgsGeometry &geom )
1303 {
1304  Q_UNUSED( geom )
1305  onFeatureDeleted( fid );
1306  onFeatureAdded( fid );
1307 }
1308 
1309 void QgsPointLocator::onAttributeValueChanged( QgsFeatureId fid, int idx, const QVariant &value )
1310 {
1311  Q_UNUSED( idx )
1312  Q_UNUSED( value )
1313  if ( mContext )
1314  {
1315  onFeatureDeleted( fid );
1316  onFeatureAdded( fid );
1317  }
1318 }
1319 
1320 
1321 QgsPointLocator::Match QgsPointLocator::nearestVertex( const QgsPointXY &point, double tolerance, MatchFilter *filter, bool relaxed )
1322 {
1323  if ( !prepare( relaxed ) )
1324  return Match();
1325 
1326  Match m;
1327  QgsPointLocator_VisitorNearestVertex visitor( this, m, point, filter );
1328  const QgsRectangle rect( point.x() - tolerance, point.y() - tolerance, point.x() + tolerance, point.y() + tolerance );
1329  mRTree->intersectsWithQuery( rect2region( rect ), visitor );
1330  if ( m.isValid() && m.distance() > tolerance )
1331  return Match(); // make sure that only match strictly within the tolerance is returned
1332  return m;
1333 }
1334 
1335 QgsPointLocator::Match QgsPointLocator::nearestCentroid( const QgsPointXY &point, double tolerance, MatchFilter *filter, bool relaxed )
1336 {
1337  if ( !prepare( relaxed ) )
1338  return Match();
1339 
1340  Match m;
1341  QgsPointLocator_VisitorNearestCentroid visitor( this, m, point, filter );
1342 
1343  const QgsRectangle rect( point.x() - tolerance, point.y() - tolerance, point.x() + tolerance, point.y() + tolerance );
1344  mRTree->intersectsWithQuery( rect2region( rect ), visitor );
1345  if ( m.isValid() && m.distance() > tolerance )
1346  return Match(); // make sure that only match strictly within the tolerance is returned
1347  return m;
1348 }
1349 
1350 QgsPointLocator::Match QgsPointLocator::nearestMiddleOfSegment( const QgsPointXY &point, double tolerance, MatchFilter *filter, bool relaxed )
1351 {
1352  if ( !prepare( relaxed ) )
1353  return Match();
1354 
1355  Match m;
1356  QgsPointLocator_VisitorNearestMiddleOfSegment visitor( this, m, point, filter );
1357 
1358  const QgsRectangle rect( point.x() - tolerance, point.y() - tolerance, point.x() + tolerance, point.y() + tolerance );
1359  mRTree->intersectsWithQuery( rect2region( rect ), visitor );
1360  if ( m.isValid() && m.distance() > tolerance )
1361  return Match(); // make sure that only match strictly within the tolerance is returned
1362  return m;
1363 }
1364 
1366 {
1367  if ( !prepare( relaxed ) )
1368  return Match();
1369 
1370  Match m;
1371  QgsPointLocator_VisitorNearestLineEndpoint visitor( this, m, point, filter );
1372 
1373  const QgsRectangle rect( point.x() - tolerance, point.y() - tolerance, point.x() + tolerance, point.y() + tolerance );
1374  mRTree->intersectsWithQuery( rect2region( rect ), visitor );
1375  if ( m.isValid() && m.distance() > tolerance )
1376  return Match(); // make sure that only match strictly within the tolerance is returned
1377  return m;
1378 }
1379 
1380 QgsPointLocator::Match QgsPointLocator::nearestEdge( const QgsPointXY &point, double tolerance, MatchFilter *filter, bool relaxed )
1381 {
1382  if ( !prepare( relaxed ) )
1383  return Match();
1384 
1385  const QgsWkbTypes::GeometryType geomType = mLayer->geometryType();
1386  if ( geomType == QgsWkbTypes::PointGeometry )
1387  return Match();
1388 
1389  Match m;
1390  QgsPointLocator_VisitorNearestEdge visitor( this, m, point, filter );
1391  const QgsRectangle rect( point.x() - tolerance, point.y() - tolerance, point.x() + tolerance, point.y() + tolerance );
1392  mRTree->intersectsWithQuery( rect2region( rect ), visitor );
1393  if ( m.isValid() && m.distance() > tolerance )
1394  return Match(); // make sure that only match strictly within the tolerance is returned
1395  return m;
1396 }
1397 
1398 QgsPointLocator::Match QgsPointLocator::nearestArea( const QgsPointXY &point, double tolerance, MatchFilter *filter, bool relaxed )
1399 {
1400  if ( !prepare( relaxed ) )
1401  return Match();
1402 
1403  const MatchList mlist = pointInPolygon( point );
1404  if ( !mlist.isEmpty() && mlist.at( 0 ).isValid() )
1405  {
1406  return mlist.at( 0 );
1407  }
1408 
1409  if ( tolerance == 0 )
1410  {
1411  return Match();
1412  }
1413 
1414  // discard point and line layers to keep only polygons
1415  const QgsWkbTypes::GeometryType geomType = mLayer->geometryType();
1416  if ( geomType == QgsWkbTypes::PointGeometry || geomType == QgsWkbTypes::LineGeometry )
1417  return Match();
1418 
1419  // use edges for adding tolerance
1420  const Match m = nearestEdge( point, tolerance, filter );
1421  if ( m.isValid() )
1422  return Match( Area, m.layer(), m.featureId(), m.distance(), m.point() );
1423  else
1424  return Match();
1425 }
1426 
1427 
1429 {
1430  if ( !prepare( relaxed ) )
1431  return MatchList();
1432 
1433  const QgsWkbTypes::GeometryType geomType = mLayer->geometryType();
1434  if ( geomType == QgsWkbTypes::PointGeometry )
1435  return MatchList();
1436 
1437  MatchList lst;
1438  QgsPointLocator_VisitorEdgesInRect visitor( this, lst, rect, filter );
1439  mRTree->intersectsWithQuery( rect2region( rect ), visitor );
1440 
1441  return lst;
1442 }
1443 
1445 {
1446  const QgsRectangle rect( point.x() - tolerance, point.y() - tolerance, point.x() + tolerance, point.y() + tolerance );
1447  return edgesInRect( rect, filter, relaxed );
1448 }
1449 
1451 {
1452  if ( !prepare( relaxed ) )
1453  return MatchList();
1454 
1455  MatchList lst;
1456  QgsPointLocator_VisitorVerticesInRect visitor( this, lst, rect, filter );
1457  mRTree->intersectsWithQuery( rect2region( rect ), visitor );
1458 
1459  return lst;
1460 }
1461 
1463 {
1464  const QgsRectangle rect( point.x() - tolerance, point.y() - tolerance, point.x() + tolerance, point.y() + tolerance );
1465  return verticesInRect( rect, filter, relaxed );
1466 }
1467 
1469 {
1470  if ( !prepare( relaxed ) )
1471  return MatchList();
1472 
1473  const QgsWkbTypes::GeometryType geomType = mLayer->geometryType();
1474  if ( geomType == QgsWkbTypes::PointGeometry || geomType == QgsWkbTypes::LineGeometry )
1475  return MatchList();
1476 
1477  MatchList lst;
1478  QgsPointLocator_VisitorArea visitor( this, point, lst );
1479  mRTree->intersectsWithQuery( point2point( point ), visitor );
1480  return lst;
1481 }
The part_iterator class provides STL-style iterator for const references to geometry parts.
The vertex_iterator class provides STL-style iterator for vertices.
QgsVertexId vertexId() const
Returns vertex ID of the current item.
virtual bool hasCurvedSegments() const
Returns true if the geometry contains curved segments.
static QgsTaskManager * taskManager()
Returns the application's task manager, used for managing application wide background task handling.
This class represents a coordinate reference system (CRS).
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
Contains information about the context in which a coordinate transform is executed.
Class for doing transforms between two map coordinate systems.
void setBallparkTransformsAreAppropriate(bool appropriate)
Sets whether approximate "ballpark" results are appropriate for this coordinate transform.
bool isShortCircuited() const
Returns true if the transform short circuits because the source and destination are equivalent.
bool isValid() const
Returns true if the coordinate transform is valid, ie both the source and destination CRS have been s...
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination coordinate reference system, which the transform will transform coordinates t...
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool handle180Crossover=false) const SIP_THROW(QgsCsException)
Transforms a rectangle from the source CRS to the destination CRS.
Curve polygon geometry type.
Abstract base class for curved geometry type.
Definition: qgscurve.h:36
virtual int numPoints() const =0
Returns the number of points in the curve.
virtual QgsPoint startPoint() const =0
Returns the starting point of the curve.
virtual bool isValid() const =0
Returns true if this is a valid layer.
Defines a QGIS exception class.
Definition: qgsexception.h:35
QString what() const
Definition: qgsexception.h:48
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
virtual QgsFeatureRenderer * clone() const =0
Create a deep copy of this renderer.
@ Filter
Features may be filtered, i.e. some features may not be rendered (categorized, rule based ....
Definition: qgsrenderer.h:265
This class wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
QgsFeatureRequest & setNoAttributes()
Set that no attributes will be fetched.
QgsFeatureRequest & setFilterRect(const QgsRectangle &rectangle)
Sets the rectangle from which features will be taken.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
QgsGeometry geometry
Definition: qgsfeature.h:67
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Definition: qgsfeature.cpp:223
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
Definition: qgsfeature.cpp:163
Q_GADGET QgsFeatureId id
Definition: qgsfeature.h:64
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:125
QgsWkbTypes::Type wkbType() const SIP_HOLDGIL
Returns type of the geometry as a WKB type (point / linestring / polygon etc.)
QgsAbstractGeometry::const_part_iterator const_parts_begin() const
Returns STL-style const iterator pointing to the first part of the geometry.
Qgis::GeometryOperationResult transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool transformZ=false) SIP_THROW(QgsCsException)
Transforms this geometry as described by the coordinate transform ct.
QgsPoint vertexAt(int atVertex) const
Returns coordinates of a vertex.
QgsPointXY closestVertex(const QgsPointXY &point, int &closestVertexIndex, int &previousVertexIndex, int &nextVertexIndex, double &sqrDist) const
Returns the vertex closest to the given point, the corresponding vertex index, squared distance snap ...
bool isMultipart() const SIP_HOLDGIL
Returns true if WKB of the geometry is of WKBMulti* type.
QgsGeometry convertToType(QgsWkbTypes::GeometryType destType, bool destMultipart=false) const
Try to convert the geometry to the requested type.
QgsPointXY asPoint() const
Returns the contents of the geometry as a 2-dimensional point.
QgsWkbTypes::GeometryType type
Definition: qgsgeometry.h:128
void convertToStraightSegment(double tolerance=M_PI/180., QgsAbstractGeometry::SegmentationToleranceType toleranceType=QgsAbstractGeometry::MaximumAngle)
Converts the geometry to straight line segments, if it is a curved geometry type.
QgsGeometry centroid() const
Returns the center of mass of a geometry.
bool isEmpty() const
Returns true if the geometry is empty (eg a linestring with no vertices, or a collection with no geom...
int vertexNrFromVertexId(QgsVertexId id) const
Returns the vertex number corresponding to a vertex id.
QgsAbstractGeometry::const_part_iterator const_parts_end() const
Returns STL-style iterator pointing to the imaginary part after the last part of the geometry.
QgsAbstractGeometry::vertex_iterator vertices_begin() const
Returns STL-style iterator pointing to the first vertex of the geometry.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
double closestSegmentWithContext(const QgsPointXY &point, QgsPointXY &minDistPoint, int &nextVertexIndex, int *leftOrRightOfSegment=nullptr, double epsilon=DEFAULT_SEGMENT_EPSILON) const
Searches for the closest segment of geometry to the given point.
bool intersects(const QgsRectangle &rectangle) const
Returns true if this geometry exactly intersects with a rectangle.
QgsAbstractGeometry::vertex_iterator vertices_end() const
Returns STL-style iterator pointing to the imaginary vertex after the last vertex of the geometry.
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:79
void styleChanged()
Signal emitted whenever a change affects the layer's style.
void dataChanged()
Data of layer changed.
Helper class to dump the R-index nodes and their content.
void getNextEntry(const IEntry &entry, id_type &nextEntry, bool &hasNext) override
Helper class for bulk loading of R-trees.
IData * getNext() override
uint32_t size() override
QgsPointLocator_Stream(const QLinkedList< RTree::Data * > &dataList)
Helper class used when traversing the index with areas - builds a list of matches.
void visitNode(const INode &n) override
void visitData(const IData &d) override
void visitData(std::vector< const IData * > &v) override
QgsPointLocator_VisitorArea(QgsPointLocator *pl, const QgsPointXY &origPt, QgsPointLocator::MatchList &list)
constructor
Helper class used when traversing the index looking for centroid - builds a list of matches.
QgsPointLocator_VisitorCentroidsInRect(QgsPointLocator *pl, QgsPointLocator::MatchList &lst, const QgsRectangle &srcRect, QgsPointLocator::MatchFilter *filter=nullptr)
Constructs the visitor.
void visitData(const IData &d) override
void visitData(std::vector< const IData * > &v) override
void visitNode(const INode &n) override
Helper class used when traversing the index looking for edges - builds a list of matches.
void visitData(std::vector< const IData * > &v) override
QgsPointLocator_VisitorEdgesInRect(QgsPointLocator *pl, QgsPointLocator::MatchList &lst, const QgsRectangle &srcRect, QgsPointLocator::MatchFilter *filter=nullptr)
void visitNode(const INode &n) override
void visitData(const IData &d) override
Helper class used when traversing the index looking for middle segment - builds a list of matches.
void visitData(std::vector< const IData * > &v) override
void visitNode(const INode &n) override
void visitData(const IData &d) override
QgsPointLocator_VisitorMiddlesInRect(QgsPointLocator *pl, QgsPointLocator::MatchList &lst, const QgsRectangle &srcRect, QgsPointLocator::MatchFilter *filter=nullptr)
Constructs the visitor.
Helper class used when traversing the index looking for centroid - builds a list of matches.
void visitData(const IData &d) override
void visitNode(const INode &n) override
void visitData(std::vector< const IData * > &v) override
Helper class used when traversing the index looking for edges - builds a list of matches.
QgsPointLocator_VisitorNearestEdge(QgsPointLocator *pl, QgsPointLocator::Match &m, const QgsPointXY &srcPoint, QgsPointLocator::MatchFilter *filter=nullptr)
void visitData(const IData &d) override
void visitData(std::vector< const IData * > &v) override
void visitNode(const INode &n) override
Helper class used when traversing the index looking for line endpoints (start or end vertex) - builds...
void visitData(std::vector< const IData * > &v) override
void visitNode(const INode &n) override
void visitData(const IData &d) override
Helper class used when traversing the index looking for middle segment - builds a list of matches.
void visitData(std::vector< const IData * > &v) override
Helper class used when traversing the index looking for vertices - builds a list of matches.
void visitData(const IData &d) override
void visitNode(const INode &n) override
void visitData(std::vector< const IData * > &v) override
QgsPointLocator_VisitorNearestVertex(QgsPointLocator *pl, QgsPointLocator::Match &m, const QgsPointXY &srcPoint, QgsPointLocator::MatchFilter *filter=nullptr)
Helper class used when traversing the index looking for vertices - builds a list of matches.
void visitData(const IData &d) override
void visitNode(const INode &n) override
void visitData(std::vector< const IData * > &v) override
QgsPointLocator_VisitorVerticesInRect(QgsPointLocator *pl, QgsPointLocator::MatchList &lst, const QgsRectangle &srcRect, QgsPointLocator::MatchFilter *filter=nullptr)
Constructs the visitor.
The class defines interface for querying point location:
const QgsRectangle * extent() const
Gets extent of the area point locator covers - if nullptr then it caches the whole layer.
friend class QgsPointLocatorInitTask
MatchList pointInPolygon(const QgsPointXY &point, bool relaxed=false)
find out if the point is in any polygons This method is either blocking or non blocking according to ...
Match nearestEdge(const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter=nullptr, bool relaxed=false)
Find nearest edge to the specified point - up to distance specified by tolerance Optional filter may ...
void setRenderContext(const QgsRenderContext *context)
Configure render context - if not nullptr, it will use to index only visible feature.
QgsCoordinateReferenceSystem destinationCrs() const
Gets destination CRS - may be an invalid QgsCoordinateReferenceSystem if not doing OTF reprojection.
void setExtent(const QgsRectangle *extent)
Configure extent - if not nullptr, it will index only that area.
bool init(int maxFeaturesToIndex=-1, bool relaxed=false)
Prepare the index for queries.
MatchList verticesInRect(const QgsRectangle &rect, QgsPointLocator::MatchFilter *filter=nullptr, bool relaxed=false)
Find vertices within a specified rectangle This method is either blocking or non blocking according t...
class QList< QgsPointLocator::Match > MatchList
Match nearestVertex(const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter=nullptr, bool relaxed=false)
Find nearest vertex to the specified point - up to distance specified by tolerance Optional filter ma...
QgsVectorLayer * layer() const
Gets associated layer.
MatchList edgesInRect(const QgsRectangle &rect, QgsPointLocator::MatchFilter *filter=nullptr, bool relaxed=false)
Find edges within a specified rectangle Optional filter may discard unwanted matches.
bool hasIndex() const
Indicate whether the data have been already indexed.
Match nearestMiddleOfSegment(const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter=nullptr, bool relaxed=false)
Find nearest middle of segment to the specified point - up to distance specified by tolerance Optiona...
void waitForIndexingFinished()
If the point locator has been initialized relaxedly and is currently indexing, this methods waits for...
Match nearestCentroid(const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter=nullptr, bool relaxed=false)
Find nearest centroid to the specified point - up to distance specified by tolerance Optional filter ...
void initFinished(bool ok)
Emitted whenever index has been built and initialization is finished.
bool rebuildIndex(int maxFeaturesToIndex=-1)
Match nearestArea(const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter=nullptr, bool relaxed=false)
Find nearest area to the specified point - up to distance specified by tolerance Optional filter may ...
@ Area
Snapped to an area.
@ MiddleOfSegment
Snapped to the middle of a segment.
@ Vertex
Snapped to a vertex. Can be a vertex of the geometry or an intersection.
@ Centroid
Snapped to a centroid.
@ Edge
Snapped to an edge.
@ LineEndpoint
Start or end points of lines only (since QGIS 3.20)
QgsPointLocator(QgsVectorLayer *layer, const QgsCoordinateReferenceSystem &destinationCrs=QgsCoordinateReferenceSystem(), const QgsCoordinateTransformContext &transformContext=QgsCoordinateTransformContext(), const QgsRectangle *extent=nullptr)
Construct point locator for a layer.
~QgsPointLocator() override
Match nearestLineEndpoints(const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter=nullptr, bool relaxed=false)
Find nearest line endpoint (start or end vertex) to the specified point - up to distance specified by...
A class to represent a 2D point.
Definition: qgspointxy.h:59
bool isEmpty() const SIP_HOLDGIL
Returns true if the geometry is empty.
Definition: qgspointxy.h:249
double sqrDist(double x, double y) const SIP_HOLDGIL
Returns the squared distance between this point a specified x, y coordinate.
Definition: qgspointxy.h:190
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:49
A rectangle specified with double values.
Definition: qgsrectangle.h:42
double yMaximum() const SIP_HOLDGIL
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:193
double xMaximum() const SIP_HOLDGIL
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:183
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:188
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:198
bool isFinite() const
Returns true if the rectangle has finite boundaries.
Definition: qgsrectangle.h:559
Contains information about the context of a rendering operation.
QgsExpressionContext & expressionContext()
Gets the expression context.
long addTask(QgsTask *task, int priority=0)
Adds a task to the manager.
Partial snapshot of vector layer's state (only the members necessary for access to features)
Represents a vector layer which manages a vector based data sets.
Q_INVOKABLE QgsWkbTypes::GeometryType geometryType() const
Returns point, line or polygon.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
QgsVectorDataProvider * dataProvider() FINAL
Returns the layer's data provider, it may be nullptr.
void attributeValueChanged(QgsFeatureId fid, int idx, const QVariant &value)
Emitted whenever an attribute value change is done in the edit buffer.
void featureAdded(QgsFeatureId fid)
Emitted when a new feature has been added to the layer.
void featureDeleted(QgsFeatureId fid)
Emitted when a feature has been deleted.
void geometryChanged(QgsFeatureId fid, const QgsGeometry &geometry)
Emitted whenever a geometry change is done in the edit buffer.
QgsFeatureRenderer * renderer()
Returns the feature renderer used for rendering the features in the layer in 2D map views.
static GeometryType geometryType(Type type) SIP_HOLDGIL
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
Definition: qgswkbtypes.h:968
GeometryType
The geometry types are used to group QgsWkbTypes::Type in a coarse way.
Definition: qgswkbtypes.h:141
QgsPointLocator_VisitorNearestCentroid(QgsPointLocator *pl, QgsPointLocator::Match &m, const QgsPointXY &srcPoint, QgsPointLocator::MatchFilter *filter=nullptr)
Helper class used when traversing the index looking for centroid - builds a list of matches.
QgsPointLocator_VisitorNearestMiddleOfSegment(QgsPointLocator *pl, QgsPointLocator::Match &m, const QgsPointXY &srcPoint, QgsPointLocator::MatchFilter *filter=nullptr)
Helper class used when traversing the index looking for middle segment - builds a list of matches.
QgsPointLocator_VisitorNearestLineEndpoint(QgsPointLocator *pl, QgsPointLocator::Match &m, const QgsPointXY &srcPoint, QgsPointLocator::MatchFilter *filter=nullptr)
Helper class used when traversing the index looking for line endpoints (start or end vertex) - builds...
CORE_EXPORT QgsMeshVertex centroid(const QgsMeshFace &face, const QVector< QgsMeshVertex > &vertices)
Returns the centroid of the face.
#define LEFT(x)
Definition: priorityqueue.h:38
#define RIGHT(x)
Definition: priorityqueue.h:39
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
Definition: qgsfeatureid.h:28
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
Interface that allows rejection of some matches in intersection queries (e.g.
QgsVectorLayer * layer() const
The vector layer where the snap occurred.
QgsFeatureId featureId() const
The id of the feature to which the snapped geometry belongs.
double distance() const
for vertex / edge match units depending on what class returns it (geom.cache: layer units,...
QgsPointXY point() const
for vertex / edge match coords depending on what class returns it (geom.cache: layer coords,...
OutCode computeOutCode(double x, double y)
bool isSegmentInRect(double x0, double y0, double x1, double y1)
_CohenSutherland(const QgsRectangle &rect)