QGIS API Documentation  3.2.0-Bonn (bc43194)
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
360  FALLTHROUGH;
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
394  FALLTHROUGH;
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
435  FALLTHROUGH;
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
476  FALLTHROUGH;
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  QgsDebugMsg( QString( "NODE: %1" ).arg( n->getIdentifier() ) );
591  if ( n->getLevel() > 0 )
592  {
593  // inner nodes
594  for ( uint32_t cChild = 0; cChild < n->getChildrenCount(); cChild++ )
595  {
596  QgsDebugMsg( QString( "- CH: %1" ).arg( n->getChildIdentifier( cChild ) ) );
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  QgsDebugMsg( QString( "- L: %1" ).arg( n->getChildIdentifier( cChild ) ) );
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  : mIsEmptyLayer( false )
625  , mLayer( layer )
626 {
627  if ( destCRS.isValid() )
628  {
629  mTransform = QgsCoordinateTransform( layer->crs(), destCRS, transformContext );
630  }
631 
632  setExtent( extent );
633 
634  mStorage = StorageManager::createNewMemoryStorageManager();
635 
636  connect( mLayer, &QgsVectorLayer::featureAdded, this, &QgsPointLocator::onFeatureAdded );
637  connect( mLayer, &QgsVectorLayer::featureDeleted, this, &QgsPointLocator::onFeatureDeleted );
638  connect( mLayer, &QgsVectorLayer::geometryChanged, this, &QgsPointLocator::onGeometryChanged );
639  connect( mLayer, &QgsVectorLayer::attributeValueChanged, this, &QgsPointLocator::onAttributeValueChanged );
641 }
642 
643 
645 {
646  destroyIndex();
647  delete mStorage;
648  delete mExtent;
649 }
650 
652 {
653  return mTransform.isValid() ? mTransform.destinationCrs() : QgsCoordinateReferenceSystem();
654 }
655 
657 {
658  if ( extent )
659  {
660  mExtent = new QgsRectangle( *extent );
661  }
662 
663  destroyIndex();
664 }
665 
667 {
668  disconnect( mLayer, &QgsVectorLayer::styleChanged, this, &QgsPointLocator::destroyIndex );
669 
670  destroyIndex();
671  mContext.reset( nullptr );
672 
673  if ( context )
674  {
675  mContext = std::unique_ptr<QgsRenderContext>( new QgsRenderContext( *context ) );
677  }
678 
679 }
680 
681 bool QgsPointLocator::init( int maxFeaturesToIndex )
682 {
683  return hasIndex() ? true : rebuildIndex( maxFeaturesToIndex );
684 }
685 
686 
688 {
689  return mRTree || mIsEmptyLayer;
690 }
691 
692 
693 bool QgsPointLocator::rebuildIndex( int maxFeaturesToIndex )
694 {
695  destroyIndex();
696 
697  QLinkedList<RTree::Data *> dataList;
698  QgsFeature f;
699  QgsWkbTypes::GeometryType geomType = mLayer->geometryType();
700  if ( geomType == QgsWkbTypes::NullGeometry )
701  return true; // nothing to index
702 
703  QgsFeatureRequest request;
705 
706  if ( mExtent )
707  {
708  QgsRectangle rect = *mExtent;
709  if ( mTransform.isValid() )
710  {
711  try
712  {
714  }
715  catch ( const QgsException &e )
716  {
717  Q_UNUSED( e );
718  // See https://issues.qgis.org/issues/12634
719  QgsDebugMsg( QString( "could not transform bounding box to map, skipping the snap filter (%1)" ).arg( e.what() ) );
720  }
721  }
722  request.setFilterRect( rect );
723  }
724 
725  bool filter = false;
726  std::unique_ptr< QgsFeatureRenderer > renderer( mLayer->renderer() ? mLayer->renderer()->clone() : nullptr );
727  QgsRenderContext *ctx = nullptr;
728  if ( mContext )
729  {
730  mContext->expressionContext() << QgsExpressionContextUtils::layerScope( mLayer );
731  ctx = mContext.get();
732  if ( renderer )
733  {
734  // setup scale for scale dependent visibility (rule based)
735  renderer->startRender( *ctx, mLayer->fields() );
736  filter = renderer->capabilities() & QgsFeatureRenderer::Filter;
737  request.setSubsetOfAttributes( renderer->usedAttributes( *ctx ), mLayer->fields() );
738  }
739  }
740 
741  QgsFeatureIterator fi = mLayer->getFeatures( request );
742  int indexedCount = 0;
743 
744  while ( fi.nextFeature( f ) )
745  {
746  if ( !f.hasGeometry() )
747  continue;
748 
749  if ( filter && ctx && renderer )
750  {
751  ctx->expressionContext().setFeature( f );
752  if ( !renderer->willRenderFeature( f, *ctx ) )
753  {
754  continue;
755  }
756  }
757 
758  if ( mTransform.isValid() )
759  {
760  try
761  {
762  QgsGeometry transformedGeometry = f.geometry();
763  transformedGeometry.transform( mTransform );
764  f.setGeometry( transformedGeometry );
765  }
766  catch ( const QgsException &e )
767  {
768  Q_UNUSED( e );
769  // See https://issues.qgis.org/issues/12634
770  QgsDebugMsg( QString( "could not transform geometry to map, skipping the snap for it (%1)" ).arg( e.what() ) );
771  continue;
772  }
773  }
774 
775  SpatialIndex::Region r( rect2region( f.geometry().boundingBox() ) );
776  dataList << new RTree::Data( 0, nullptr, r, f.id() );
777 
778  if ( mGeoms.contains( f.id() ) )
779  delete mGeoms.take( f.id() );
780  mGeoms[f.id()] = new QgsGeometry( f.geometry() );
781  ++indexedCount;
782 
783  if ( maxFeaturesToIndex != -1 && indexedCount > maxFeaturesToIndex )
784  {
785  qDeleteAll( dataList );
786  destroyIndex();
787  return false;
788  }
789  }
790 
791  // R-Tree parameters
792  double fillFactor = 0.7;
793  unsigned long indexCapacity = 10;
794  unsigned long leafCapacity = 10;
795  unsigned long dimension = 2;
796  RTree::RTreeVariant variant = RTree::RV_RSTAR;
797  SpatialIndex::id_type indexId;
798 
799  if ( dataList.isEmpty() )
800  {
801  mIsEmptyLayer = true;
802  return true; // no features
803  }
804 
805  QgsPointLocator_Stream stream( dataList );
806  mRTree = RTree::createAndBulkLoadNewRTree( RTree::BLM_STR, stream, *mStorage, fillFactor, indexCapacity,
807  leafCapacity, dimension, variant, indexId );
808 
809  if ( ctx && renderer )
810  {
811  renderer->stopRender( *ctx );
812  renderer.release();
813  }
814  return true;
815 }
816 
817 
819 {
820  delete mRTree;
821  mRTree = nullptr;
822 
823  mIsEmptyLayer = false;
824 
825  qDeleteAll( mGeoms );
826 
827  mGeoms.clear();
828 }
829 
830 void QgsPointLocator::onFeatureAdded( QgsFeatureId fid )
831 {
832  if ( !mRTree )
833  {
834  if ( mIsEmptyLayer )
835  rebuildIndex(); // first feature - let's built the index
836  return; // nothing to do if we are not initialized yet
837  }
838 
839  QgsFeature f;
840  if ( mLayer->getFeatures( QgsFeatureRequest( fid ) ).nextFeature( f ) )
841  {
842  if ( !f.hasGeometry() )
843  return;
844 
845  if ( mContext )
846  {
847  std::unique_ptr< QgsFeatureRenderer > renderer( mLayer->renderer() ? mLayer->renderer()->clone() : nullptr );
848  QgsRenderContext *ctx = nullptr;
849 
850  mContext->expressionContext() << QgsExpressionContextUtils::layerScope( mLayer );
851  ctx = mContext.get();
852  if ( renderer && ctx )
853  {
854  bool pass = false;
855  renderer->startRender( *ctx, mLayer->fields() );
856 
857  ctx->expressionContext().setFeature( f );
858  if ( !renderer->willRenderFeature( f, *ctx ) )
859  {
860  pass = true;
861  }
862 
863  renderer->stopRender( *ctx );
864  renderer.release();
865  if ( pass )
866  return;
867  }
868  }
869 
870  if ( mTransform.isValid() )
871  {
872  try
873  {
874  QgsGeometry transformedGeom = f.geometry();
875  transformedGeom.transform( mTransform );
876  f.setGeometry( transformedGeom );
877  }
878  catch ( const QgsException &e )
879  {
880  Q_UNUSED( e );
881  // See https://issues.qgis.org/issues/12634
882  QgsDebugMsg( QString( "could not transform geometry to map, skipping the snap for it (%1)" ).arg( e.what() ) );
883  return;
884  }
885  }
886 
887  QgsRectangle bbox = f.geometry().boundingBox();
888  if ( !bbox.isNull() )
889  {
890  SpatialIndex::Region r( rect2region( bbox ) );
891  mRTree->insertData( 0, nullptr, r, f.id() );
892 
893  if ( mGeoms.contains( f.id() ) )
894  delete mGeoms.take( f.id() );
895  mGeoms[fid] = new QgsGeometry( f.geometry() );
896  }
897  }
898 }
899 
900 void QgsPointLocator::onFeatureDeleted( QgsFeatureId fid )
901 {
902  if ( !mRTree )
903  return; // nothing to do if we are not initialized yet
904 
905  if ( mGeoms.contains( fid ) )
906  {
907  mRTree->deleteData( rect2region( mGeoms[fid]->boundingBox() ), fid );
908  delete mGeoms.take( fid );
909  }
910 
911 }
912 
913 void QgsPointLocator::onGeometryChanged( QgsFeatureId fid, const QgsGeometry &geom )
914 {
915  Q_UNUSED( geom );
916  onFeatureDeleted( fid );
917  onFeatureAdded( fid );
918 }
919 
920 void QgsPointLocator::onAttributeValueChanged( QgsFeatureId fid, int idx, const QVariant &value )
921 {
922  Q_UNUSED( idx );
923  Q_UNUSED( value );
924  if ( mContext )
925  {
926  onFeatureDeleted( fid );
927  onFeatureAdded( fid );
928  }
929 }
930 
931 
933 {
934  if ( !mRTree )
935  {
936  init();
937  if ( !mRTree ) // still invalid?
938  return Match();
939  }
940 
941  Match m;
942  QgsPointLocator_VisitorNearestVertex visitor( this, m, point, filter );
943  QgsRectangle rect( point.x() - tolerance, point.y() - tolerance, point.x() + tolerance, point.y() + tolerance );
944  mRTree->intersectsWithQuery( rect2region( rect ), visitor );
945  if ( m.isValid() && m.distance() > tolerance )
946  return Match(); // make sure that only match strictly within the tolerance is returned
947  return m;
948 }
949 
951 {
952  if ( !mRTree )
953  {
954  init();
955  if ( !mRTree ) // still invalid?
956  return Match();
957  }
958 
959  QgsWkbTypes::GeometryType geomType = mLayer->geometryType();
960  if ( geomType == QgsWkbTypes::PointGeometry )
961  return Match();
962 
963  Match m;
964  QgsPointLocator_VisitorNearestEdge visitor( this, m, point, filter );
965  QgsRectangle rect( point.x() - tolerance, point.y() - tolerance, point.x() + tolerance, point.y() + tolerance );
966  mRTree->intersectsWithQuery( rect2region( rect ), visitor );
967  if ( m.isValid() && m.distance() > tolerance )
968  return Match(); // make sure that only match strictly within the tolerance is returned
969  return m;
970 }
971 
973 {
974  if ( !mRTree )
975  {
976  init();
977  if ( !mRTree ) // still invalid?
978  return Match();
979  }
980 
981  MatchList mlist = pointInPolygon( point );
982  if ( mlist.count() && mlist.at( 0 ).isValid() )
983  {
984  return mlist.at( 0 );
985  }
986 
987  if ( tolerance == 0 )
988  {
989  return Match();
990  }
991 
992  // discard point and line layers to keep only polygons
993  QgsWkbTypes::GeometryType geomType = mLayer->geometryType();
994  if ( geomType == QgsWkbTypes::PointGeometry || geomType == QgsWkbTypes::LineGeometry )
995  return Match();
996 
997  // use edges for adding tolerance
998  Match m = nearestEdge( point, tolerance, filter );
999  if ( m.isValid() )
1000  return Match( Area, m.layer(), m.featureId(), m.distance(), m.point() );
1001  else
1002  return Match();
1003 }
1004 
1005 
1007 {
1008  if ( !mRTree )
1009  {
1010  init();
1011  if ( !mRTree ) // still invalid?
1012  return MatchList();
1013  }
1014 
1015  QgsWkbTypes::GeometryType geomType = mLayer->geometryType();
1016  if ( geomType == QgsWkbTypes::PointGeometry )
1017  return MatchList();
1018 
1019  MatchList lst;
1020  QgsPointLocator_VisitorEdgesInRect visitor( this, lst, rect, filter );
1021  mRTree->intersectsWithQuery( rect2region( rect ), visitor );
1022 
1023  return lst;
1024 }
1025 
1027 {
1028  QgsRectangle rect( point.x() - tolerance, point.y() - tolerance, point.x() + tolerance, point.y() + tolerance );
1029  return edgesInRect( rect, filter );
1030 }
1031 
1032 
1034 {
1035  if ( !mRTree )
1036  {
1037  init();
1038  if ( !mRTree ) // still invalid?
1039  return MatchList();
1040  }
1041 
1042  QgsWkbTypes::GeometryType geomType = mLayer->geometryType();
1043  if ( geomType == QgsWkbTypes::PointGeometry || geomType == QgsWkbTypes::LineGeometry )
1044  return MatchList();
1045 
1046  MatchList lst;
1047  QgsPointLocator_VisitorArea visitor( this, point, lst );
1048  mRTree->intersectsWithQuery( point2point( point ), visitor );
1049  return lst;
1050 }
#define LEFT(x)
Definition: priorityqueue.h:38
The class defines interface for querying point location:
QgsFeatureId id
Definition: qgsfeature.h:71
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 ...
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:104
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:62
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:190
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:67
#define FALLTHROUGH
Definition: qgis.h:570
QgsFields fields() const override
Returns the list of fields of this layer.
QgsPointLocator_VisitorNearestVertex(QgsPointLocator *pl, QgsPointLocator::Match &m, const QgsPointXY &srcPoint, QgsPointLocator::MatchFilter *filter=nullptr)
OutCode computeOutCode(double x, double y)
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.
QgsCoordinateReferenceSystem crs() const
Returns the layer&#39;s spatial reference system.
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.
QgsGeometry geometry() const
Returns the geometry associated with this feature.
Definition: qgsfeature.cpp:101
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
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const override
Query the layer for features specified in request.
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:137
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:429
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
qint64 QgsFeatureId
Definition: qgsfeature.h:37
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
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.
QList< int > QgsAttributeList
Definition: qgsfield.h:27
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.
bool isValid() const
Returns whether this CRS is correctly initialized and usable.