QGIS API Documentation  3.4.3-Madeira (2f64a3c)
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 
26 #include <SpatialIndex.h>
27 
28 #include <QLinkedListIterator>
29 
30 using namespace SpatialIndex;
31 
32 
33 
34 static SpatialIndex::Point point2point( const QgsPointXY &point )
35 {
36  double plow[2] = { point.x(), point.y() };
37  return Point( plow, 2 );
38 }
39 
40 
41 static SpatialIndex::Region rect2region( const QgsRectangle &rect )
42 {
43  double pLow[2] = { rect.xMinimum(), rect.yMinimum() };
44  double pHigh[2] = { rect.xMaximum(), rect.yMaximum() };
45  return SpatialIndex::Region( pLow, pHigh, 2 );
46 }
47 
48 
49 // Ahh.... another magic number. Taken from QgsVectorLayer::snapToGeometry() call to closestSegmentWithContext().
50 // The default epsilon used for sqrDistToSegment (1e-8) is too high when working with lat/lon coordinates
51 // I still do not fully understand why the sqrDistToSegment() code uses epsilon and if the square distance
52 // is lower than epsilon it will have a special logic...
53 static const double POINT_LOC_EPSILON = 1e-12;
54 
56 
57 
63 class QgsPointLocator_Stream : public IDataStream
64 {
65  public:
66  explicit QgsPointLocator_Stream( const QLinkedList<RTree::Data *> &dataList )
67  : mDataList( dataList )
68  , mIt( mDataList )
69  { }
70 
71  IData *getNext() override { return mIt.next(); }
72  bool hasNext() override { return mIt.hasNext(); }
73 
74  uint32_t size() override { Q_ASSERT( false && "not available" ); return 0; }
75  void rewind() override { Q_ASSERT( false && "not available" ); }
76 
77  private:
78  QLinkedList<RTree::Data *> mDataList;
79  QLinkedListIterator<RTree::Data *> mIt;
80 };
81 
82 
84 
85 
91 class QgsPointLocator_VisitorNearestVertex : public IVisitor
92 {
93  public:
95  : mLocator( pl )
96  , mBest( m )
97  , mSrcPoint( srcPoint )
98  , mFilter( filter )
99  {}
100 
101  void visitNode( const INode &n ) override { Q_UNUSED( n ); }
102  void visitData( std::vector<const IData *> &v ) override { Q_UNUSED( v ); }
103 
104  void visitData( const IData &d ) override
105  {
106  QgsFeatureId id = d.getIdentifier();
107  QgsGeometry *geom = mLocator->mGeoms.value( id );
108  int vertexIndex, beforeVertex, afterVertex;
109  double sqrDist;
110 
111  QgsPointXY pt = geom->closestVertex( mSrcPoint, vertexIndex, beforeVertex, afterVertex, sqrDist );
112  if ( sqrDist < 0 )
113  return; // probably empty geometry
114 
115  QgsPointLocator::Match m( QgsPointLocator::Vertex, mLocator->mLayer, id, std::sqrt( sqrDist ), pt, vertexIndex );
116  // in range queries the filter may reject some matches
117  if ( mFilter && !mFilter->acceptMatch( m ) )
118  return;
119 
120  if ( !mBest.isValid() || m.distance() < mBest.distance() )
121  mBest = m;
122  }
123 
124  private:
125  QgsPointLocator *mLocator = nullptr;
126  QgsPointLocator::Match &mBest;
127  QgsPointXY mSrcPoint;
128  QgsPointLocator::MatchFilter *mFilter = nullptr;
129 };
130 
131 
133 
134 
140 class QgsPointLocator_VisitorNearestEdge : public IVisitor
141 {
142  public:
144  : mLocator( pl )
145  , mBest( m )
146  , mSrcPoint( srcPoint )
147  , mFilter( filter )
148  {}
149 
150  void visitNode( const INode &n ) override { Q_UNUSED( n ); }
151  void visitData( std::vector<const IData *> &v ) override { Q_UNUSED( v ); }
152 
153  void visitData( const IData &d ) override
154  {
155  QgsFeatureId id = d.getIdentifier();
156  QgsGeometry *geom = mLocator->mGeoms.value( id );
157  QgsPointXY pt;
158  int afterVertex;
159  double sqrDist = geom->closestSegmentWithContext( mSrcPoint, pt, afterVertex, nullptr, POINT_LOC_EPSILON );
160  if ( sqrDist < 0 )
161  return;
162 
163  QgsPointXY edgePoints[2];
164  edgePoints[0] = geom->vertexAt( afterVertex - 1 );
165  edgePoints[1] = geom->vertexAt( afterVertex );
166  QgsPointLocator::Match m( QgsPointLocator::Edge, mLocator->mLayer, id, std::sqrt( sqrDist ), pt, afterVertex - 1, edgePoints );
167  // in range queries the filter may reject some matches
168  if ( mFilter && !mFilter->acceptMatch( m ) )
169  return;
170 
171  if ( !mBest.isValid() || m.distance() < mBest.distance() )
172  mBest = m;
173  }
174 
175  private:
176  QgsPointLocator *mLocator = nullptr;
177  QgsPointLocator::Match &mBest;
178  QgsPointXY mSrcPoint;
179  QgsPointLocator::MatchFilter *mFilter = nullptr;
180 };
181 
182 
184 
190 class QgsPointLocator_VisitorArea : public IVisitor
191 {
192  public:
195  : mLocator( pl )
196  , mList( list )
197  , mGeomPt( QgsGeometry::fromPointXY( origPt ) )
198  {}
199 
200  void visitNode( const INode &n ) override { Q_UNUSED( n ); }
201  void visitData( std::vector<const IData *> &v ) override { Q_UNUSED( v ); }
202 
203  void visitData( const IData &d ) override
204  {
205  QgsFeatureId id = d.getIdentifier();
206  QgsGeometry *g = mLocator->mGeoms.value( id );
207  if ( g->intersects( mGeomPt ) )
208  mList << QgsPointLocator::Match( QgsPointLocator::Area, mLocator->mLayer, id, 0, mGeomPt.asPoint() );
209  }
210  private:
211  QgsPointLocator *mLocator = nullptr;
213  QgsGeometry mGeomPt;
214 };
215 
216 
218 
219 // code adapted from
220 // http://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm
222 {
223  explicit _CohenSutherland( const QgsRectangle &rect ) : mRect( rect ) {}
224 
225  typedef int OutCode;
226 
227  static const int INSIDE = 0; // 0000
228  static const int LEFT = 1; // 0001
229  static const int RIGHT = 2; // 0010
230  static const int BOTTOM = 4; // 0100
231  static const int TOP = 8; // 1000
232 
234 
235  OutCode computeOutCode( double x, double y )
236  {
237  OutCode code = INSIDE; // initialized as being inside of clip window
238  if ( x < mRect.xMinimum() ) // to the left of clip window
239  code |= LEFT;
240  else if ( x > mRect.xMaximum() ) // to the right of clip window
241  code |= RIGHT;
242  if ( y < mRect.yMinimum() ) // below the clip window
243  code |= BOTTOM;
244  else if ( y > mRect.yMaximum() ) // above the clip window
245  code |= TOP;
246  return code;
247  }
248 
249  bool isSegmentInRect( double x0, double y0, double x1, double y1 )
250  {
251  // compute outcodes for P0, P1, and whatever point lies outside the clip rectangle
252  OutCode outcode0 = computeOutCode( x0, y0 );
253  OutCode outcode1 = computeOutCode( x1, y1 );
254  bool accept = false;
255 
256  while ( true )
257  {
258  if ( !( outcode0 | outcode1 ) )
259  {
260  // Bitwise OR is 0. Trivially accept and get out of loop
261  accept = true;
262  break;
263  }
264  else if ( outcode0 & outcode1 )
265  {
266  // Bitwise AND is not 0. Trivially reject and get out of loop
267  break;
268  }
269  else
270  {
271  // failed both tests, so calculate the line segment to clip
272  // from an outside point to an intersection with clip edge
273  double x, y;
274 
275  // At least one endpoint is outside the clip rectangle; pick it.
276  OutCode outcodeOut = outcode0 ? outcode0 : outcode1;
277 
278  // Now find the intersection point;
279  // use formulas y = y0 + slope * (x - x0), x = x0 + (1 / slope) * (y - y0)
280  if ( outcodeOut & TOP )
281  {
282  // point is above the clip rectangle
283  x = x0 + ( x1 - x0 ) * ( mRect.yMaximum() - y0 ) / ( y1 - y0 );
284  y = mRect.yMaximum();
285  }
286  else if ( outcodeOut & BOTTOM )
287  {
288  // point is below the clip rectangle
289  x = x0 + ( x1 - x0 ) * ( mRect.yMinimum() - y0 ) / ( y1 - y0 );
290  y = mRect.yMinimum();
291  }
292  else if ( outcodeOut & RIGHT )
293  {
294  // point is to the right of clip rectangle
295  y = y0 + ( y1 - y0 ) * ( mRect.xMaximum() - x0 ) / ( x1 - x0 );
296  x = mRect.xMaximum();
297  }
298  else if ( outcodeOut & LEFT )
299  {
300  // point is to the left of clip rectangle
301  y = y0 + ( y1 - y0 ) * ( mRect.xMinimum() - x0 ) / ( x1 - x0 );
302  x = mRect.xMinimum();
303  }
304  else
305  break;
306 
307  // Now we move outside point to intersection point to clip
308  // and get ready for next pass.
309  if ( outcodeOut == outcode0 )
310  {
311  x0 = x;
312  y0 = y;
313  outcode0 = computeOutCode( x0, y0 );
314  }
315  else
316  {
317  x1 = x;
318  y1 = y;
319  outcode1 = computeOutCode( x1, y1 );
320  }
321  }
322  }
323  return accept;
324  }
325 };
326 
327 
328 static QgsPointLocator::MatchList _geometrySegmentsInRect( QgsGeometry *geom, const QgsRectangle &rect, QgsVectorLayer *vl, QgsFeatureId fid )
329 {
330  // this code is stupidly based on QgsGeometry::closestSegmentWithContext
331  // we need iterator for segments...
332 
334  QByteArray wkb( geom->asWkb() );
335  if ( wkb.isEmpty() )
336  return lst;
337 
338  _CohenSutherland cs( rect );
339 
340  QgsConstWkbPtr wkbPtr( wkb );
341  wkbPtr.readHeader();
342 
343  QgsWkbTypes::Type wkbType = geom->wkbType();
344 
345  bool hasZValue = false;
346  switch ( wkbType )
347  {
349  case QgsWkbTypes::Point:
352  {
353  // Points have no lines
354  return lst;
355  }
356 
358  hasZValue = true;
359  //intentional fall-through
362  {
363  int nPoints;
364  wkbPtr >> nPoints;
365 
366  double prevx = 0.0, prevy = 0.0;
367  for ( int index = 0; index < nPoints; ++index )
368  {
369  double thisx = 0.0, thisy = 0.0;
370  wkbPtr >> thisx >> thisy;
371  if ( hasZValue )
372  wkbPtr += sizeof( double );
373 
374  if ( index > 0 )
375  {
376  if ( cs.isSegmentInRect( prevx, prevy, thisx, thisy ) )
377  {
378  QgsPointXY edgePoints[2];
379  edgePoints[0].set( prevx, prevy );
380  edgePoints[1].set( thisx, thisy );
381  lst << QgsPointLocator::Match( QgsPointLocator::Edge, vl, fid, 0, QgsPointXY(), index - 1, edgePoints );
382  }
383  }
384 
385  prevx = thisx;
386  prevy = thisy;
387  }
388  break;
389  }
390 
392  hasZValue = true;
393  //intentional fall-through
396  {
397  int nLines;
398  wkbPtr >> nLines;
399  for ( int linenr = 0, pointIndex = 0; linenr < nLines; ++linenr )
400  {
401  wkbPtr.readHeader();
402  int nPoints;
403  wkbPtr >> nPoints;
404 
405  double prevx = 0.0, prevy = 0.0;
406  for ( int pointnr = 0; pointnr < nPoints; ++pointnr )
407  {
408  double thisx = 0.0, thisy = 0.0;
409  wkbPtr >> thisx >> thisy;
410  if ( hasZValue )
411  wkbPtr += sizeof( double );
412 
413  if ( pointnr > 0 )
414  {
415  if ( cs.isSegmentInRect( prevx, prevy, thisx, thisy ) )
416  {
417  QgsPointXY edgePoints[2];
418  edgePoints[0].set( prevx, prevy );
419  edgePoints[1].set( thisx, thisy );
420  lst << QgsPointLocator::Match( QgsPointLocator::Edge, vl, fid, 0, QgsPointXY(), pointIndex - 1, edgePoints );
421  }
422  }
423 
424  prevx = thisx;
425  prevy = thisy;
426  ++pointIndex;
427  }
428  }
429  break;
430  }
431 
433  hasZValue = true;
434  //intentional fall-through
437  {
438  int nRings;
439  wkbPtr >> nRings;
440 
441  for ( int ringnr = 0, pointIndex = 0; ringnr < nRings; ++ringnr )//loop over rings
442  {
443  int nPoints;
444  wkbPtr >> nPoints;
445 
446  double prevx = 0.0, prevy = 0.0;
447  for ( int pointnr = 0; pointnr < nPoints; ++pointnr )//loop over points in a ring
448  {
449  double thisx = 0.0, thisy = 0.0;
450  wkbPtr >> thisx >> thisy;
451  if ( hasZValue )
452  wkbPtr += sizeof( double );
453 
454  if ( pointnr > 0 )
455  {
456  if ( cs.isSegmentInRect( prevx, prevy, thisx, thisy ) )
457  {
458  QgsPointXY edgePoints[2];
459  edgePoints[0].set( prevx, prevy );
460  edgePoints[1].set( thisx, thisy );
461  lst << QgsPointLocator::Match( QgsPointLocator::Edge, vl, fid, 0, QgsPointXY(), pointIndex - 1, edgePoints );
462  }
463  }
464 
465  prevx = thisx;
466  prevy = thisy;
467  ++pointIndex;
468  }
469  }
470  break;
471  }
472 
474  hasZValue = true;
475  //intentional fall-through
478  {
479  int nPolygons;
480  wkbPtr >> nPolygons;
481  for ( int polynr = 0, pointIndex = 0; polynr < nPolygons; ++polynr )
482  {
483  wkbPtr.readHeader();
484  int nRings;
485  wkbPtr >> nRings;
486  for ( int ringnr = 0; ringnr < nRings; ++ringnr )
487  {
488  int nPoints;
489  wkbPtr >> nPoints;
490 
491  double prevx = 0.0, prevy = 0.0;
492  for ( int pointnr = 0; pointnr < nPoints; ++pointnr )
493  {
494  double thisx = 0.0, thisy = 0.0;
495  wkbPtr >> thisx >> thisy;
496  if ( hasZValue )
497  wkbPtr += sizeof( double );
498 
499  if ( pointnr > 0 )
500  {
501  if ( cs.isSegmentInRect( prevx, prevy, thisx, thisy ) )
502  {
503  QgsPointXY edgePoints[2];
504  edgePoints[0].set( prevx, prevy );
505  edgePoints[1].set( thisx, thisy );
506  lst << QgsPointLocator::Match( QgsPointLocator::Edge, vl, fid, 0, QgsPointXY(), pointIndex - 1, edgePoints );
507  }
508  }
509 
510  prevx = thisx;
511  prevy = thisy;
512  ++pointIndex;
513  }
514  }
515  }
516  break;
517  }
518 
520  default:
521  return lst;
522  } // switch (wkbType)
523 
524  return lst;
525 }
526 
532 class QgsPointLocator_VisitorEdgesInRect : public IVisitor
533 {
534  public:
536  : mLocator( pl )
537  , mList( lst )
538  , mSrcRect( srcRect )
539  , mFilter( filter )
540  {}
541 
542  void visitNode( const INode &n ) override { Q_UNUSED( n ); }
543  void visitData( std::vector<const IData *> &v ) override { Q_UNUSED( v ); }
544 
545  void visitData( const IData &d ) override
546  {
547  QgsFeatureId id = d.getIdentifier();
548  QgsGeometry *geom = mLocator->mGeoms.value( id );
549 
550  Q_FOREACH ( const QgsPointLocator::Match &m, _geometrySegmentsInRect( geom, mSrcRect, mLocator->mLayer, id ) )
551  {
552  // in range queries the filter may reject some matches
553  if ( mFilter && !mFilter->acceptMatch( m ) )
554  continue;
555 
556  mList << m;
557  }
558  }
559 
560  private:
561  QgsPointLocator *mLocator = nullptr;
563  QgsRectangle mSrcRect;
564  QgsPointLocator::MatchFilter *mFilter = nullptr;
565 };
566 
567 
568 
570 #include <QStack>
571 
577 class QgsPointLocator_DumpTree : public SpatialIndex::IQueryStrategy
578 {
579  private:
580  QStack<id_type> ids;
581 
582  public:
583 
584  void getNextEntry( const IEntry &entry, id_type &nextEntry, bool &hasNext ) override
585  {
586  const INode *n = dynamic_cast<const INode *>( &entry );
587  if ( !n )
588  return;
589 
590  QgsDebugMsgLevel( QStringLiteral( "NODE: %1" ).arg( n->getIdentifier() ), 4 );
591  if ( n->getLevel() > 0 )
592  {
593  // inner nodes
594  for ( uint32_t cChild = 0; cChild < n->getChildrenCount(); cChild++ )
595  {
596  QgsDebugMsgLevel( QStringLiteral( "- CH: %1" ).arg( n->getChildIdentifier( cChild ) ), 4 );
597  ids.push( n->getChildIdentifier( cChild ) );
598  }
599  }
600  else
601  {
602  // leaves
603  for ( uint32_t cChild = 0; cChild < n->getChildrenCount(); cChild++ )
604  {
605  QgsDebugMsgLevel( QStringLiteral( "- L: %1" ).arg( n->getChildIdentifier( cChild ) ), 4 );
606  }
607  }
608 
609  if ( ! ids.empty() )
610  {
611  nextEntry = ids.back();
612  ids.pop();
613  hasNext = true;
614  }
615  else
616  hasNext = false;
617  }
618 };
619 
621 
622 
624  : mLayer( layer )
625 {
626  if ( destCRS.isValid() )
627  {
628  mTransform = QgsCoordinateTransform( layer->crs(), destCRS, transformContext );
629  }
630 
631  setExtent( extent );
632 
633  mStorage.reset( StorageManager::createNewMemoryStorageManager() );
634 
635  connect( mLayer, &QgsVectorLayer::featureAdded, this, &QgsPointLocator::onFeatureAdded );
636  connect( mLayer, &QgsVectorLayer::featureDeleted, this, &QgsPointLocator::onFeatureDeleted );
637  connect( mLayer, &QgsVectorLayer::geometryChanged, this, &QgsPointLocator::onGeometryChanged );
638  connect( mLayer, &QgsVectorLayer::attributeValueChanged, this, &QgsPointLocator::onAttributeValueChanged );
640 }
641 
642 
644 {
645  destroyIndex();
646 }
647 
649 {
650  return mTransform.isValid() ? mTransform.destinationCrs() : QgsCoordinateReferenceSystem();
651 }
652 
654 {
655  mExtent.reset( extent ? new QgsRectangle( *extent ) : nullptr );
656 
657  destroyIndex();
658 }
659 
661 {
662  disconnect( mLayer, &QgsVectorLayer::styleChanged, this, &QgsPointLocator::destroyIndex );
663 
664  destroyIndex();
665  mContext.reset( nullptr );
666 
667  if ( context )
668  {
669  mContext = std::unique_ptr<QgsRenderContext>( new QgsRenderContext( *context ) );
671  }
672 
673 }
674 
675 bool QgsPointLocator::init( int maxFeaturesToIndex )
676 {
677  return hasIndex() ? true : rebuildIndex( maxFeaturesToIndex );
678 }
679 
680 
682 {
683  return mRTree || mIsEmptyLayer;
684 }
685 
686 
687 bool QgsPointLocator::rebuildIndex( int maxFeaturesToIndex )
688 {
689  destroyIndex();
690 
691  QLinkedList<RTree::Data *> dataList;
692  QgsFeature f;
693  QgsWkbTypes::GeometryType geomType = mLayer->geometryType();
694  if ( geomType == QgsWkbTypes::NullGeometry )
695  return true; // nothing to index
696 
697  QgsFeatureRequest request;
698  request.setNoAttributes();
699 
700  if ( mExtent )
701  {
702  QgsRectangle rect = *mExtent;
703  if ( mTransform.isValid() )
704  {
705  try
706  {
708  }
709  catch ( const QgsException &e )
710  {
711  Q_UNUSED( e );
712  // See https://issues.qgis.org/issues/12634
713  QgsDebugMsg( QStringLiteral( "could not transform bounding box to map, skipping the snap filter (%1)" ).arg( e.what() ) );
714  }
715  }
716  request.setFilterRect( rect );
717  }
718 
719  bool filter = false;
720  std::unique_ptr< QgsFeatureRenderer > renderer( mLayer->renderer() ? mLayer->renderer()->clone() : nullptr );
721  QgsRenderContext *ctx = nullptr;
722  if ( mContext )
723  {
724  mContext->expressionContext() << QgsExpressionContextUtils::layerScope( mLayer );
725  ctx = mContext.get();
726  if ( renderer )
727  {
728  // setup scale for scale dependent visibility (rule based)
729  renderer->startRender( *ctx, mLayer->fields() );
730  filter = renderer->capabilities() & QgsFeatureRenderer::Filter;
731  request.setSubsetOfAttributes( renderer->usedAttributes( *ctx ), mLayer->fields() );
732  }
733  }
734 
735  QgsFeatureIterator fi = mLayer->getFeatures( request );
736  int indexedCount = 0;
737 
738  while ( fi.nextFeature( f ) )
739  {
740  if ( !f.hasGeometry() )
741  continue;
742 
743  if ( filter && ctx && renderer )
744  {
745  ctx->expressionContext().setFeature( f );
746  if ( !renderer->willRenderFeature( f, *ctx ) )
747  {
748  continue;
749  }
750  }
751 
752  if ( mTransform.isValid() )
753  {
754  try
755  {
756  QgsGeometry transformedGeometry = f.geometry();
757  transformedGeometry.transform( mTransform );
758  f.setGeometry( transformedGeometry );
759  }
760  catch ( const QgsException &e )
761  {
762  Q_UNUSED( e );
763  // See https://issues.qgis.org/issues/12634
764  QgsDebugMsg( QStringLiteral( "could not transform geometry to map, skipping the snap for it (%1)" ).arg( e.what() ) );
765  continue;
766  }
767  }
768 
769  SpatialIndex::Region r( rect2region( f.geometry().boundingBox() ) );
770  dataList << new RTree::Data( 0, nullptr, r, f.id() );
771 
772  if ( mGeoms.contains( f.id() ) )
773  delete mGeoms.take( f.id() );
774  mGeoms[f.id()] = new QgsGeometry( f.geometry() );
775  ++indexedCount;
776 
777  if ( maxFeaturesToIndex != -1 && indexedCount > maxFeaturesToIndex )
778  {
779  qDeleteAll( dataList );
780  destroyIndex();
781  return false;
782  }
783  }
784 
785  // R-Tree parameters
786  double fillFactor = 0.7;
787  unsigned long indexCapacity = 10;
788  unsigned long leafCapacity = 10;
789  unsigned long dimension = 2;
790  RTree::RTreeVariant variant = RTree::RV_RSTAR;
791  SpatialIndex::id_type indexId;
792 
793  if ( dataList.isEmpty() )
794  {
795  mIsEmptyLayer = true;
796  return true; // no features
797  }
798 
799  QgsPointLocator_Stream stream( dataList );
800  mRTree.reset( RTree::createAndBulkLoadNewRTree( RTree::BLM_STR, stream, *mStorage, fillFactor, indexCapacity,
801  leafCapacity, dimension, variant, indexId ) );
802 
803  if ( ctx && renderer )
804  {
805  renderer->stopRender( *ctx );
806  }
807  return true;
808 }
809 
810 
812 {
813  mRTree.reset();
814 
815  mIsEmptyLayer = false;
816 
817  qDeleteAll( mGeoms );
818 
819  mGeoms.clear();
820 }
821 
822 void QgsPointLocator::onFeatureAdded( QgsFeatureId fid )
823 {
824  if ( !mRTree )
825  {
826  if ( mIsEmptyLayer )
827  rebuildIndex(); // first feature - let's built the index
828  return; // nothing to do if we are not initialized yet
829  }
830 
831  QgsFeature f;
832  if ( mLayer->getFeatures( QgsFeatureRequest( fid ) ).nextFeature( f ) )
833  {
834  if ( !f.hasGeometry() )
835  return;
836 
837  if ( mContext )
838  {
839  std::unique_ptr< QgsFeatureRenderer > renderer( mLayer->renderer() ? mLayer->renderer()->clone() : nullptr );
840  QgsRenderContext *ctx = nullptr;
841 
842  mContext->expressionContext() << QgsExpressionContextUtils::layerScope( mLayer );
843  ctx = mContext.get();
844  if ( renderer && ctx )
845  {
846  bool pass = false;
847  renderer->startRender( *ctx, mLayer->fields() );
848 
849  ctx->expressionContext().setFeature( f );
850  if ( !renderer->willRenderFeature( f, *ctx ) )
851  {
852  pass = true;
853  }
854 
855  renderer->stopRender( *ctx );
856  if ( pass )
857  return;
858  }
859  }
860 
861  if ( mTransform.isValid() )
862  {
863  try
864  {
865  QgsGeometry transformedGeom = f.geometry();
866  transformedGeom.transform( mTransform );
867  f.setGeometry( transformedGeom );
868  }
869  catch ( const QgsException &e )
870  {
871  Q_UNUSED( e );
872  // See https://issues.qgis.org/issues/12634
873  QgsDebugMsg( QStringLiteral( "could not transform geometry to map, skipping the snap for it (%1)" ).arg( e.what() ) );
874  return;
875  }
876  }
877 
878  QgsRectangle bbox = f.geometry().boundingBox();
879  if ( !bbox.isNull() )
880  {
881  SpatialIndex::Region r( rect2region( bbox ) );
882  mRTree->insertData( 0, nullptr, r, f.id() );
883 
884  if ( mGeoms.contains( f.id() ) )
885  delete mGeoms.take( f.id() );
886  mGeoms[fid] = new QgsGeometry( f.geometry() );
887  }
888  }
889 }
890 
891 void QgsPointLocator::onFeatureDeleted( QgsFeatureId fid )
892 {
893  if ( !mRTree )
894  return; // nothing to do if we are not initialized yet
895 
896  if ( mGeoms.contains( fid ) )
897  {
898  mRTree->deleteData( rect2region( mGeoms[fid]->boundingBox() ), fid );
899  delete mGeoms.take( fid );
900  }
901 
902 }
903 
904 void QgsPointLocator::onGeometryChanged( QgsFeatureId fid, const QgsGeometry &geom )
905 {
906  Q_UNUSED( geom );
907  onFeatureDeleted( fid );
908  onFeatureAdded( fid );
909 }
910 
911 void QgsPointLocator::onAttributeValueChanged( QgsFeatureId fid, int idx, const QVariant &value )
912 {
913  Q_UNUSED( idx );
914  Q_UNUSED( value );
915  if ( mContext )
916  {
917  onFeatureDeleted( fid );
918  onFeatureAdded( fid );
919  }
920 }
921 
922 
924 {
925  if ( !mRTree )
926  {
927  init();
928  if ( !mRTree ) // still invalid?
929  return Match();
930  }
931 
932  Match m;
933  QgsPointLocator_VisitorNearestVertex visitor( this, m, point, filter );
934  QgsRectangle rect( point.x() - tolerance, point.y() - tolerance, point.x() + tolerance, point.y() + tolerance );
935  mRTree->intersectsWithQuery( rect2region( rect ), visitor );
936  if ( m.isValid() && m.distance() > tolerance )
937  return Match(); // make sure that only match strictly within the tolerance is returned
938  return m;
939 }
940 
942 {
943  if ( !mRTree )
944  {
945  init();
946  if ( !mRTree ) // still invalid?
947  return Match();
948  }
949 
950  QgsWkbTypes::GeometryType geomType = mLayer->geometryType();
951  if ( geomType == QgsWkbTypes::PointGeometry )
952  return Match();
953 
954  Match m;
955  QgsPointLocator_VisitorNearestEdge visitor( this, m, point, filter );
956  QgsRectangle rect( point.x() - tolerance, point.y() - tolerance, point.x() + tolerance, point.y() + tolerance );
957  mRTree->intersectsWithQuery( rect2region( rect ), visitor );
958  if ( m.isValid() && m.distance() > tolerance )
959  return Match(); // make sure that only match strictly within the tolerance is returned
960  return m;
961 }
962 
964 {
965  if ( !mRTree )
966  {
967  init();
968  if ( !mRTree ) // still invalid?
969  return Match();
970  }
971 
972  MatchList mlist = pointInPolygon( point );
973  if ( mlist.count() && mlist.at( 0 ).isValid() )
974  {
975  return mlist.at( 0 );
976  }
977 
978  if ( tolerance == 0 )
979  {
980  return Match();
981  }
982 
983  // discard point and line layers to keep only polygons
984  QgsWkbTypes::GeometryType geomType = mLayer->geometryType();
985  if ( geomType == QgsWkbTypes::PointGeometry || geomType == QgsWkbTypes::LineGeometry )
986  return Match();
987 
988  // use edges for adding tolerance
989  Match m = nearestEdge( point, tolerance, filter );
990  if ( m.isValid() )
991  return Match( Area, m.layer(), m.featureId(), m.distance(), m.point() );
992  else
993  return Match();
994 }
995 
996 
998 {
999  if ( !mRTree )
1000  {
1001  init();
1002  if ( !mRTree ) // still invalid?
1003  return MatchList();
1004  }
1005 
1006  QgsWkbTypes::GeometryType geomType = mLayer->geometryType();
1007  if ( geomType == QgsWkbTypes::PointGeometry )
1008  return MatchList();
1009 
1010  MatchList lst;
1011  QgsPointLocator_VisitorEdgesInRect visitor( this, lst, rect, filter );
1012  mRTree->intersectsWithQuery( rect2region( rect ), visitor );
1013 
1014  return lst;
1015 }
1016 
1018 {
1019  QgsRectangle rect( point.x() - tolerance, point.y() - tolerance, point.x() + tolerance, point.y() + tolerance );
1020  return edgesInRect( rect, filter );
1021 }
1022 
1023 
1025 {
1026  if ( !mRTree )
1027  {
1028  init();
1029  if ( !mRTree ) // still invalid?
1030  return MatchList();
1031  }
1032 
1033  QgsWkbTypes::GeometryType geomType = mLayer->geometryType();
1034  if ( geomType == QgsWkbTypes::PointGeometry || geomType == QgsWkbTypes::LineGeometry )
1035  return MatchList();
1036 
1037  MatchList lst;
1038  QgsPointLocator_VisitorArea visitor( this, point, lst );
1039  mRTree->intersectsWithQuery( point2point( point ), visitor );
1040  return lst;
1041 }
#define LEFT(x)
Definition: priorityqueue.h:38
The class defines interface for querying point location:
QgsFeatureId id
Definition: qgsfeature.h:64
Wrapper for iterator of features from vector data provider or vector layer.
double closestSegmentWithContext(const QgsPointXY &point, QgsPointXY &minDistPoint, int &afterVertex, int *leftOf=nullptr, double epsilon=DEFAULT_SEGMENT_EPSILON) const
Searches for the closest segment of geometry to the given point.
void visitData(std::vector< const IData *> &v) override
void set(double x, double y)
Sets the x and y value of the point.
Definition: qgspointxy.h:119
A rectangle specified with double values.
Definition: qgsrectangle.h:40
OperationResult transform(const QgsCoordinateTransform &ct, QgsCoordinateTransform::TransformDirection direction=QgsCoordinateTransform::ForwardTransform, bool transformZ=false) SIP_THROW(QgsCsException)
Transforms this geometry as described by the coordinate transform ct.
QgsVectorLayer * layer() const
The vector layer where the snap occurred.
uint32_t size() override
void visitData(const IData &d) override
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
bool hasIndex() const
Indicate whether the data have been already indexed.
Features may be filtered, i.e. some features may not be rendered (categorized, rule based ...
Definition: qgsrenderer.h:244
QgsWkbTypes::Type wkbType() const
Returns type of the geometry as a WKB type (point / linestring / polygon etc.)
double y
Definition: qgspointxy.h:48
QgsPointLocator_VisitorArea(QgsPointLocator *pl, const QgsPointXY &origPt, QgsPointLocator::MatchList &list)
constructor
A class to represent a 2D point.
Definition: qgspointxy.h:43
Match nearestEdge(const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter=nullptr)
Find nearest edge to the specified point - up to distance specified by tolerance Optional filter may ...
qint64 QgsFeatureId
Definition: qgsfeatureid.h:25
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
QgsCoordinateReferenceSystem destinationCrs() const
Gets destination CRS - may be an invalid QgsCoordinateReferenceSystem if not doing OTF reprojection...
QgsWkbTypes::GeometryType geometryType() const
Returns point, line or polygon.
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination coordinate reference system, which the transform will transform coordinates t...
class QList< QgsPointLocator::Match > MatchList
Helper class used when traversing the index looking for edges - builds a list of matches.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:106
Match nearestArea(const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter=nullptr)
Find nearest area to the specified point - up to distance specified by tolerance Optional filter may ...
bool rebuildIndex(int maxFeaturesToIndex=-1)
void visitNode(const INode &n) override
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:55
Interface that allows rejection of some matches in intersection queries (e.g.
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Definition: qgsfeature.cpp:197
void featureDeleted(QgsFeatureId fid)
Emitted when a feature has been deleted.
bool isValid() const
Returns true if the coordinate transform is valid, ie both the source and destination CRS have been s...
void styleChanged()
Signal emitted whenever a change affects the layer&#39;s style.
QgsPointLocator_Stream(const QLinkedList< RTree::Data *> &dataList)
QString what() const
Definition: qgsexception.h:48
QgsPointXY closestVertex(const QgsPointXY &point, int &atVertex, int &beforeVertex, int &afterVertex, double &sqrDist) const
Returns the vertex closest to the given point, the corresponding vertex index, squared distance snap ...
bool intersects(const QgsRectangle &rectangle) const
Returns true if this geometry exactly intersects with a rectangle.
~QgsPointLocator() override
Type
The WKB type describes the number of dimensions a geometry has.
Definition: qgswkbtypes.h:68
QgsFields fields() const FINAL
Returns the list of fields of this layer.
#define FALLTHROUGH
Definition: qgis.h:639
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
QgsPointLocator_VisitorNearestVertex(QgsPointLocator *pl, QgsPointLocator::Match &m, const QgsPointXY &srcPoint, QgsPointLocator::MatchFilter *filter=nullptr)
OutCode computeOutCode(double x, double y)
QgsFeatureRequest & setNoAttributes()
Set that no attributes will be fetched.
void setRenderContext(const QgsRenderContext *context)
Configure render context - if not null, it will use to index only visible feature.
Snapped to a vertex. Can be a vertex of the geometry or an intersection.
Helper class to dump the R-index nodes and their content.
This class wraps a request for features to a vector layer (or directly its vector data provider)...
bool isSegmentInRect(double x0, double y0, double x1, double y1)
QgsFeatureRequest & setFilterRect(const QgsRectangle &rectangle)
Sets the rectangle from which features will be taken.
void geometryChanged(QgsFeatureId fid, const QgsGeometry &geometry)
Is emitted whenever a geometry change is done in the edit buffer.
void visitNode(const INode &n) override
void setExtent(const QgsRectangle *extent)
Configure extent - if not null, it will index only that area.
Helper class used when traversing the index with areas - builds a list of matches.
QgsFeatureRenderer * renderer()
Returns renderer.
void attributeValueChanged(QgsFeatureId fid, int idx, const QVariant &value)
Is 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.
Contains information about the context in which a coordinate transform is executed.
void visitData(const IData &d) override
Helper class used when traversing the index looking for vertices - builds a list of matches...
const QgsRectangle * extent() const
Gets extent of the area point locator covers - if null then it caches the whole layer.
void visitData(std::vector< const IData *> &v) override
QByteArray asWkb() const
Export the geometry to WKB.
double x
Definition: qgspointxy.h:47
IData * getNext() override
void visitData(std::vector< const IData *> &v) override
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:176
double xMaximum() const
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:161
GeometryType
The geometry types are used to group QgsWkbTypes::Type in a coarse way.
Definition: qgswkbtypes.h:138
QgsPointLocator(QgsVectorLayer *layer, const QgsCoordinateReferenceSystem &destinationCrs=QgsCoordinateReferenceSystem(), const QgsCoordinateTransformContext &transformContext=QgsCoordinateTransformContext(), const QgsRectangle *extent=nullptr)
Construct point locator for a layer.
QgsPointXY point() const
for vertex / edge match coords depending on what class returns it (geom.cache: layer coords...
Contains information about the context of a rendering operation.
QgsPoint vertexAt(int atVertex) const
Returns coordinates of a vertex.
void visitData(std::vector< const IData *> &v) override
Transform from destination to source CRS.
QgsPointLocator_VisitorNearestEdge(QgsPointLocator *pl, QgsPointLocator::Match &m, const QgsPointXY &srcPoint, QgsPointLocator::MatchFilter *filter=nullptr)
Helper class for bulk loading of R-trees.
#define RIGHT(x)
Definition: priorityqueue.h:39
void visitData(const IData &d) override
MatchList pointInPolygon(const QgsPointXY &point)
find out if the point is in any polygons
void visitNode(const INode &n) override
bool init(int maxFeaturesToIndex=-1)
Prepare the index for queries.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
This class represents a coordinate reference system (CRS).
_CohenSutherland(const QgsRectangle &rect)
void setGeometry(const QgsGeometry &geometry)
Set the feature&#39;s geometry.
Definition: qgsfeature.cpp:137
bool isNull() const
Test if the rectangle is null (all coordinates zero or after call to setMinimal()).
Definition: qgsrectangle.h:435
void getNextEntry(const IEntry &entry, id_type &nextEntry, bool &hasNext) override
Match nearestVertex(const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter=nullptr)
Find nearest vertex to the specified point - up to distance specified by tolerance Optional filter ma...
void visitData(const IData &d) override
Class for doing transforms between two map coordinate systems.
double xMinimum() const
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:166
void dataChanged()
Data of layer changed.
Snapped to an edge.
double distance() const
for vertex / edge match units depending on what class returns it (geom.cache: layer units...
double yMaximum() const
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:171
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Query the layer for features specified in request.
Helper class used when traversing the index looking for edges - builds a list of matches.
void visitNode(const INode &n) override
MatchList edgesInRect(const QgsRectangle &rect, QgsPointLocator::MatchFilter *filter=nullptr)
Find edges within a specified recangle Optional filter may discard unwanted matches.
QgsGeometry geometry
Definition: qgsfeature.h:67
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
QgsFeatureId featureId() const
The id of the feature to which the snapped geometry belongs.
bool nextFeature(QgsFeature &f)
Represents a vector layer which manages a vector based data sets.
QgsWkbTypes::Type readHeader() const
readHeader
Definition: qgswkbptr.cpp:53
Defines a QGIS exception class.
Definition: qgsexception.h:34
Snapped to an area.
virtual QgsFeatureRenderer * clone() const =0
Create a deep copy of this renderer.
QgsPointLocator_VisitorEdgesInRect(QgsPointLocator *pl, QgsPointLocator::MatchList &lst, const QgsRectangle &srcRect, QgsPointLocator::MatchFilter *filter=nullptr)
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, TransformDirection direction=ForwardTransform, bool handle180Crossover=false) const SIP_THROW(QgsCsException)
Transforms a rectangle from the source CRS to the destination CRS.
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:70
bool isValid() const
Returns whether this CRS is correctly initialized and usable.