QGIS API Documentation  3.6.0-Noosa (5873452)
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"
26 
27 #include <spatialindex/SpatialIndex.h>
28 
29 #include <QLinkedListIterator>
30 
31 using namespace SpatialIndex;
32 
33 
34 
35 static SpatialIndex::Point point2point( const QgsPointXY &point )
36 {
37  double plow[2] = { point.x(), point.y() };
38  return Point( plow, 2 );
39 }
40 
41 
42 static SpatialIndex::Region rect2region( const QgsRectangle &rect )
43 {
44  double pLow[2] = { rect.xMinimum(), rect.yMinimum() };
45  double pHigh[2] = { rect.xMaximum(), rect.yMaximum() };
46  return SpatialIndex::Region( pLow, pHigh, 2 );
47 }
48 
49 
50 // Ahh.... another magic number. Taken from QgsVectorLayer::snapToGeometry() call to closestSegmentWithContext().
51 // The default epsilon used for sqrDistToSegment (1e-8) is too high when working with lat/lon coordinates
52 // I still do not fully understand why the sqrDistToSegment() code uses epsilon and if the square distance
53 // is lower than epsilon it will have a special logic...
54 static const double POINT_LOC_EPSILON = 1e-12;
55 
57 
58 
64 class QgsPointLocator_Stream : public IDataStream
65 {
66  public:
67  explicit QgsPointLocator_Stream( const QLinkedList<RTree::Data *> &dataList )
68  : mDataList( dataList )
69  , mIt( mDataList )
70  { }
71 
72  IData *getNext() override { return mIt.next(); }
73  bool hasNext() override { return mIt.hasNext(); }
74 
75  uint32_t size() override { Q_ASSERT( false && "not available" ); return 0; }
76  void rewind() override { Q_ASSERT( false && "not available" ); }
77 
78  private:
79  QLinkedList<RTree::Data *> mDataList;
80  QLinkedListIterator<RTree::Data *> mIt;
81 };
82 
83 
85 
86 
92 class QgsPointLocator_VisitorNearestVertex : public IVisitor
93 {
94  public:
96  : mLocator( pl )
97  , mBest( m )
98  , mSrcPoint( srcPoint )
99  , mFilter( filter )
100  {}
101 
102  void visitNode( const INode &n ) override { Q_UNUSED( n ); }
103  void visitData( std::vector<const IData *> &v ) override { Q_UNUSED( v ); }
104 
105  void visitData( const IData &d ) override
106  {
107  QgsFeatureId id = d.getIdentifier();
108  QgsGeometry *geom = mLocator->mGeoms.value( id );
109  int vertexIndex, beforeVertex, afterVertex;
110  double sqrDist;
111 
112  QgsPointXY pt = geom->closestVertex( mSrcPoint, vertexIndex, beforeVertex, afterVertex, sqrDist );
113  if ( sqrDist < 0 )
114  return; // probably empty geometry
115 
116  QgsPointLocator::Match m( QgsPointLocator::Vertex, mLocator->mLayer, id, std::sqrt( sqrDist ), pt, vertexIndex );
117  // in range queries the filter may reject some matches
118  if ( mFilter && !mFilter->acceptMatch( m ) )
119  return;
120 
121  if ( !mBest.isValid() || m.distance() < mBest.distance() )
122  mBest = m;
123  }
124 
125  private:
126  QgsPointLocator *mLocator = nullptr;
127  QgsPointLocator::Match &mBest;
128  QgsPointXY mSrcPoint;
129  QgsPointLocator::MatchFilter *mFilter = nullptr;
130 };
131 
132 
134 
135 
141 class QgsPointLocator_VisitorNearestEdge : public IVisitor
142 {
143  public:
145  : mLocator( pl )
146  , mBest( m )
147  , mSrcPoint( srcPoint )
148  , mFilter( filter )
149  {}
150 
151  void visitNode( const INode &n ) override { Q_UNUSED( n ); }
152  void visitData( std::vector<const IData *> &v ) override { Q_UNUSED( v ); }
153 
154  void visitData( const IData &d ) override
155  {
156  QgsFeatureId id = d.getIdentifier();
157  QgsGeometry *geom = mLocator->mGeoms.value( id );
158  QgsPointXY pt;
159  int afterVertex;
160  double sqrDist = geom->closestSegmentWithContext( mSrcPoint, pt, afterVertex, nullptr, POINT_LOC_EPSILON );
161  if ( sqrDist < 0 )
162  return;
163 
164  QgsPointXY edgePoints[2];
165  edgePoints[0] = geom->vertexAt( afterVertex - 1 );
166  edgePoints[1] = geom->vertexAt( afterVertex );
167  QgsPointLocator::Match m( QgsPointLocator::Edge, mLocator->mLayer, id, std::sqrt( sqrDist ), pt, afterVertex - 1, edgePoints );
168  // in range queries the filter may reject some matches
169  if ( mFilter && !mFilter->acceptMatch( m ) )
170  return;
171 
172  if ( !mBest.isValid() || m.distance() < mBest.distance() )
173  mBest = m;
174  }
175 
176  private:
177  QgsPointLocator *mLocator = nullptr;
178  QgsPointLocator::Match &mBest;
179  QgsPointXY mSrcPoint;
180  QgsPointLocator::MatchFilter *mFilter = nullptr;
181 };
182 
183 
185 
191 class QgsPointLocator_VisitorArea : public IVisitor
192 {
193  public:
196  : mLocator( pl )
197  , mList( list )
198  , mGeomPt( QgsGeometry::fromPointXY( origPt ) )
199  {}
200 
201  void visitNode( const INode &n ) override { Q_UNUSED( n ); }
202  void visitData( std::vector<const IData *> &v ) override { Q_UNUSED( v ); }
203 
204  void visitData( const IData &d ) override
205  {
206  QgsFeatureId id = d.getIdentifier();
207  QgsGeometry *g = mLocator->mGeoms.value( id );
208  if ( g->intersects( mGeomPt ) )
209  mList << QgsPointLocator::Match( QgsPointLocator::Area, mLocator->mLayer, id, 0, mGeomPt.asPoint() );
210  }
211  private:
212  QgsPointLocator *mLocator = nullptr;
214  QgsGeometry mGeomPt;
215 };
216 
217 
219 
220 // code adapted from
221 // http://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm
223 {
224  explicit _CohenSutherland( const QgsRectangle &rect ) : mRect( rect ) {}
225 
226  typedef int OutCode;
227 
228  static const int INSIDE = 0; // 0000
229  static const int LEFT = 1; // 0001
230  static const int RIGHT = 2; // 0010
231  static const int BOTTOM = 4; // 0100
232  static const int TOP = 8; // 1000
233 
235 
236  OutCode computeOutCode( double x, double y )
237  {
238  OutCode code = INSIDE; // initialized as being inside of clip window
239  if ( x < mRect.xMinimum() ) // to the left of clip window
240  code |= LEFT;
241  else if ( x > mRect.xMaximum() ) // to the right of clip window
242  code |= RIGHT;
243  if ( y < mRect.yMinimum() ) // below the clip window
244  code |= BOTTOM;
245  else if ( y > mRect.yMaximum() ) // above the clip window
246  code |= TOP;
247  return code;
248  }
249 
250  bool isSegmentInRect( double x0, double y0, double x1, double y1 )
251  {
252  // compute outcodes for P0, P1, and whatever point lies outside the clip rectangle
253  OutCode outcode0 = computeOutCode( x0, y0 );
254  OutCode outcode1 = computeOutCode( x1, y1 );
255  bool accept = false;
256 
257  while ( true )
258  {
259  if ( !( outcode0 | outcode1 ) )
260  {
261  // Bitwise OR is 0. Trivially accept and get out of loop
262  accept = true;
263  break;
264  }
265  else if ( outcode0 & outcode1 )
266  {
267  // Bitwise AND is not 0. Trivially reject and get out of loop
268  break;
269  }
270  else
271  {
272  // failed both tests, so calculate the line segment to clip
273  // from an outside point to an intersection with clip edge
274  double x, y;
275 
276  // At least one endpoint is outside the clip rectangle; pick it.
277  OutCode outcodeOut = outcode0 ? outcode0 : outcode1;
278 
279  // Now find the intersection point;
280  // use formulas y = y0 + slope * (x - x0), x = x0 + (1 / slope) * (y - y0)
281  if ( outcodeOut & TOP )
282  {
283  // point is above the clip rectangle
284  x = x0 + ( x1 - x0 ) * ( mRect.yMaximum() - y0 ) / ( y1 - y0 );
285  y = mRect.yMaximum();
286  }
287  else if ( outcodeOut & BOTTOM )
288  {
289  // point is below the clip rectangle
290  x = x0 + ( x1 - x0 ) * ( mRect.yMinimum() - y0 ) / ( y1 - y0 );
291  y = mRect.yMinimum();
292  }
293  else if ( outcodeOut & RIGHT )
294  {
295  // point is to the right of clip rectangle
296  y = y0 + ( y1 - y0 ) * ( mRect.xMaximum() - x0 ) / ( x1 - x0 );
297  x = mRect.xMaximum();
298  }
299  else if ( outcodeOut & LEFT )
300  {
301  // point is to the left of clip rectangle
302  y = y0 + ( y1 - y0 ) * ( mRect.xMinimum() - x0 ) / ( x1 - x0 );
303  x = mRect.xMinimum();
304  }
305  else
306  break;
307 
308  // Now we move outside point to intersection point to clip
309  // and get ready for next pass.
310  if ( outcodeOut == outcode0 )
311  {
312  x0 = x;
313  y0 = y;
314  outcode0 = computeOutCode( x0, y0 );
315  }
316  else
317  {
318  x1 = x;
319  y1 = y;
320  outcode1 = computeOutCode( x1, y1 );
321  }
322  }
323  }
324  return accept;
325  }
326 };
327 
328 
329 static QgsPointLocator::MatchList _geometrySegmentsInRect( QgsGeometry *geom, const QgsRectangle &rect, QgsVectorLayer *vl, QgsFeatureId fid )
330 {
331  // this code is stupidly based on QgsGeometry::closestSegmentWithContext
332  // we need iterator for segments...
333 
335  QByteArray wkb( geom->asWkb() );
336  if ( wkb.isEmpty() )
337  return lst;
338 
339  _CohenSutherland cs( rect );
340 
341  QgsConstWkbPtr wkbPtr( wkb );
342  wkbPtr.readHeader();
343 
344  QgsWkbTypes::Type wkbType = geom->wkbType();
345 
346  bool hasZValue = false;
347  switch ( wkbType )
348  {
350  case QgsWkbTypes::Point:
353  {
354  // Points have no lines
355  return lst;
356  }
357 
359  hasZValue = true;
360  //intentional fall-through
363  {
364  int nPoints;
365  wkbPtr >> nPoints;
366 
367  double prevx = 0.0, prevy = 0.0;
368  for ( int index = 0; index < nPoints; ++index )
369  {
370  double thisx = 0.0, thisy = 0.0;
371  wkbPtr >> thisx >> thisy;
372  if ( hasZValue )
373  wkbPtr += sizeof( double );
374 
375  if ( index > 0 )
376  {
377  if ( cs.isSegmentInRect( prevx, prevy, thisx, thisy ) )
378  {
379  QgsPointXY edgePoints[2];
380  edgePoints[0].set( prevx, prevy );
381  edgePoints[1].set( thisx, thisy );
382  lst << QgsPointLocator::Match( QgsPointLocator::Edge, vl, fid, 0, QgsPointXY(), index - 1, edgePoints );
383  }
384  }
385 
386  prevx = thisx;
387  prevy = thisy;
388  }
389  break;
390  }
391 
393  hasZValue = true;
394  //intentional fall-through
397  {
398  int nLines;
399  wkbPtr >> nLines;
400  for ( int linenr = 0, pointIndex = 0; linenr < nLines; ++linenr )
401  {
402  wkbPtr.readHeader();
403  int nPoints;
404  wkbPtr >> nPoints;
405 
406  double prevx = 0.0, prevy = 0.0;
407  for ( int pointnr = 0; pointnr < nPoints; ++pointnr )
408  {
409  double thisx = 0.0, thisy = 0.0;
410  wkbPtr >> thisx >> thisy;
411  if ( hasZValue )
412  wkbPtr += sizeof( double );
413 
414  if ( pointnr > 0 )
415  {
416  if ( cs.isSegmentInRect( prevx, prevy, thisx, thisy ) )
417  {
418  QgsPointXY edgePoints[2];
419  edgePoints[0].set( prevx, prevy );
420  edgePoints[1].set( thisx, thisy );
421  lst << QgsPointLocator::Match( QgsPointLocator::Edge, vl, fid, 0, QgsPointXY(), pointIndex - 1, edgePoints );
422  }
423  }
424 
425  prevx = thisx;
426  prevy = thisy;
427  ++pointIndex;
428  }
429  }
430  break;
431  }
432 
434  hasZValue = true;
435  //intentional fall-through
438  {
439  int nRings;
440  wkbPtr >> nRings;
441 
442  for ( int ringnr = 0, pointIndex = 0; ringnr < nRings; ++ringnr )//loop over rings
443  {
444  int nPoints;
445  wkbPtr >> nPoints;
446 
447  double prevx = 0.0, prevy = 0.0;
448  for ( int pointnr = 0; pointnr < nPoints; ++pointnr )//loop over points in a ring
449  {
450  double thisx = 0.0, thisy = 0.0;
451  wkbPtr >> thisx >> thisy;
452  if ( hasZValue )
453  wkbPtr += sizeof( double );
454 
455  if ( pointnr > 0 )
456  {
457  if ( cs.isSegmentInRect( prevx, prevy, thisx, thisy ) )
458  {
459  QgsPointXY edgePoints[2];
460  edgePoints[0].set( prevx, prevy );
461  edgePoints[1].set( thisx, thisy );
462  lst << QgsPointLocator::Match( QgsPointLocator::Edge, vl, fid, 0, QgsPointXY(), pointIndex - 1, edgePoints );
463  }
464  }
465 
466  prevx = thisx;
467  prevy = thisy;
468  ++pointIndex;
469  }
470  }
471  break;
472  }
473 
475  hasZValue = true;
476  //intentional fall-through
479  {
480  int nPolygons;
481  wkbPtr >> nPolygons;
482  for ( int polynr = 0, pointIndex = 0; polynr < nPolygons; ++polynr )
483  {
484  wkbPtr.readHeader();
485  int nRings;
486  wkbPtr >> nRings;
487  for ( int ringnr = 0; ringnr < nRings; ++ringnr )
488  {
489  int nPoints;
490  wkbPtr >> nPoints;
491 
492  double prevx = 0.0, prevy = 0.0;
493  for ( int pointnr = 0; pointnr < nPoints; ++pointnr )
494  {
495  double thisx = 0.0, thisy = 0.0;
496  wkbPtr >> thisx >> thisy;
497  if ( hasZValue )
498  wkbPtr += sizeof( double );
499 
500  if ( pointnr > 0 )
501  {
502  if ( cs.isSegmentInRect( prevx, prevy, thisx, thisy ) )
503  {
504  QgsPointXY edgePoints[2];
505  edgePoints[0].set( prevx, prevy );
506  edgePoints[1].set( thisx, thisy );
507  lst << QgsPointLocator::Match( QgsPointLocator::Edge, vl, fid, 0, QgsPointXY(), pointIndex - 1, edgePoints );
508  }
509  }
510 
511  prevx = thisx;
512  prevy = thisy;
513  ++pointIndex;
514  }
515  }
516  }
517  break;
518  }
519 
521  default:
522  return lst;
523  } // switch (wkbType)
524 
525  return lst;
526 }
527 
533 class QgsPointLocator_VisitorEdgesInRect : public IVisitor
534 {
535  public:
537  : mLocator( pl )
538  , mList( lst )
539  , mSrcRect( srcRect )
540  , mFilter( filter )
541  {}
542 
543  void visitNode( const INode &n ) override { Q_UNUSED( n ); }
544  void visitData( std::vector<const IData *> &v ) override { Q_UNUSED( v ); }
545 
546  void visitData( const IData &d ) override
547  {
548  QgsFeatureId id = d.getIdentifier();
549  QgsGeometry *geom = mLocator->mGeoms.value( id );
550 
551  Q_FOREACH ( const QgsPointLocator::Match &m, _geometrySegmentsInRect( geom, mSrcRect, mLocator->mLayer, id ) )
552  {
553  // in range queries the filter may reject some matches
554  if ( mFilter && !mFilter->acceptMatch( m ) )
555  continue;
556 
557  mList << m;
558  }
559  }
560 
561  private:
562  QgsPointLocator *mLocator = nullptr;
564  QgsRectangle mSrcRect;
565  QgsPointLocator::MatchFilter *mFilter = nullptr;
566 };
567 
569 
577 {
578  public:
581  : mLocator( pl )
582  , mList( lst )
583  , mSrcRect( srcRect )
584  , mFilter( filter )
585  {}
586 
587  void visitNode( const INode &n ) override { Q_UNUSED( n ); }
588  void visitData( std::vector<const IData *> &v ) override { Q_UNUSED( v ); }
589 
590  void visitData( const IData &d ) override
591  {
592  QgsFeatureId id = d.getIdentifier();
593  const QgsGeometry *geom = mLocator->mGeoms.value( id );
594 
595  for ( QgsAbstractGeometry::vertex_iterator it = geom->vertices_begin(); it != geom->vertices_end(); ++it )
596  {
597  if ( mSrcRect.contains( *it ) )
598  {
599  QgsPointLocator::Match m( QgsPointLocator::Vertex, mLocator->mLayer, id, 0, *it, geom->vertexNrFromVertexId( it.vertexId() ) );
600 
601  // in range queries the filter may reject some matches
602  if ( mFilter && !mFilter->acceptMatch( m ) )
603  continue;
604 
605  mList << m;
606  }
607  }
608  }
609 
610  private:
611  QgsPointLocator *mLocator = nullptr;
613  QgsRectangle mSrcRect;
614  QgsPointLocator::MatchFilter *mFilter = nullptr;
615 };
616 
617 
619 #include <QStack>
620 
626 class QgsPointLocator_DumpTree : public SpatialIndex::IQueryStrategy
627 {
628  private:
629  QStack<id_type> ids;
630 
631  public:
632 
633  void getNextEntry( const IEntry &entry, id_type &nextEntry, bool &hasNext ) override
634  {
635  const INode *n = dynamic_cast<const INode *>( &entry );
636  if ( !n )
637  return;
638 
639  QgsDebugMsgLevel( QStringLiteral( "NODE: %1" ).arg( n->getIdentifier() ), 4 );
640  if ( n->getLevel() > 0 )
641  {
642  // inner nodes
643  for ( uint32_t cChild = 0; cChild < n->getChildrenCount(); cChild++ )
644  {
645  QgsDebugMsgLevel( QStringLiteral( "- CH: %1" ).arg( n->getChildIdentifier( cChild ) ), 4 );
646  ids.push( n->getChildIdentifier( cChild ) );
647  }
648  }
649  else
650  {
651  // leaves
652  for ( uint32_t cChild = 0; cChild < n->getChildrenCount(); cChild++ )
653  {
654  QgsDebugMsgLevel( QStringLiteral( "- L: %1" ).arg( n->getChildIdentifier( cChild ) ), 4 );
655  }
656  }
657 
658  if ( ! ids.empty() )
659  {
660  nextEntry = ids.back();
661  ids.pop();
662  hasNext = true;
663  }
664  else
665  hasNext = false;
666  }
667 };
668 
670 
671 
673  : mLayer( layer )
674 {
675  if ( destCRS.isValid() )
676  {
677  mTransform = QgsCoordinateTransform( layer->crs(), destCRS, transformContext );
678  }
679 
680  setExtent( extent );
681 
682  mStorage.reset( StorageManager::createNewMemoryStorageManager() );
683 
684  connect( mLayer, &QgsVectorLayer::featureAdded, this, &QgsPointLocator::onFeatureAdded );
685  connect( mLayer, &QgsVectorLayer::featureDeleted, this, &QgsPointLocator::onFeatureDeleted );
686  connect( mLayer, &QgsVectorLayer::geometryChanged, this, &QgsPointLocator::onGeometryChanged );
687  connect( mLayer, &QgsVectorLayer::attributeValueChanged, this, &QgsPointLocator::onAttributeValueChanged );
689 }
690 
691 
693 {
694  destroyIndex();
695 }
696 
698 {
699  return mTransform.isValid() ? mTransform.destinationCrs() : QgsCoordinateReferenceSystem();
700 }
701 
703 {
704  mExtent.reset( extent ? new QgsRectangle( *extent ) : nullptr );
705 
706  destroyIndex();
707 }
708 
710 {
711  disconnect( mLayer, &QgsVectorLayer::styleChanged, this, &QgsPointLocator::destroyIndex );
712 
713  destroyIndex();
714  mContext.reset( nullptr );
715 
716  if ( context )
717  {
718  mContext = std::unique_ptr<QgsRenderContext>( new QgsRenderContext( *context ) );
720  }
721 
722 }
723 
724 bool QgsPointLocator::init( int maxFeaturesToIndex )
725 {
726  return hasIndex() ? true : rebuildIndex( maxFeaturesToIndex );
727 }
728 
729 
731 {
732  return mRTree || mIsEmptyLayer;
733 }
734 
735 
736 bool QgsPointLocator::rebuildIndex( int maxFeaturesToIndex )
737 {
738  destroyIndex();
739 
740  QLinkedList<RTree::Data *> dataList;
741  QgsFeature f;
742  QgsWkbTypes::GeometryType geomType = mLayer->geometryType();
743  if ( geomType == QgsWkbTypes::NullGeometry )
744  return true; // nothing to index
745 
746  QgsFeatureRequest request;
747  request.setNoAttributes();
748 
749  if ( mExtent )
750  {
751  QgsRectangle rect = *mExtent;
752  if ( mTransform.isValid() )
753  {
754  try
755  {
757  }
758  catch ( const QgsException &e )
759  {
760  Q_UNUSED( e );
761  // See https://issues.qgis.org/issues/12634
762  QgsDebugMsg( QStringLiteral( "could not transform bounding box to map, skipping the snap filter (%1)" ).arg( e.what() ) );
763  }
764  }
765  request.setFilterRect( rect );
766  }
767 
768  bool filter = false;
769  std::unique_ptr< QgsFeatureRenderer > renderer( mLayer->renderer() ? mLayer->renderer()->clone() : nullptr );
770  QgsRenderContext *ctx = nullptr;
771  if ( mContext )
772  {
773  mContext->expressionContext() << QgsExpressionContextUtils::layerScope( mLayer );
774  ctx = mContext.get();
775  if ( renderer )
776  {
777  // setup scale for scale dependent visibility (rule based)
778  renderer->startRender( *ctx, mLayer->fields() );
779  filter = renderer->capabilities() & QgsFeatureRenderer::Filter;
780  request.setSubsetOfAttributes( renderer->usedAttributes( *ctx ), mLayer->fields() );
781  }
782  }
783 
784  QgsFeatureIterator fi = mLayer->getFeatures( request );
785  int indexedCount = 0;
786 
787  while ( fi.nextFeature( f ) )
788  {
789  if ( !f.hasGeometry() )
790  continue;
791 
792  if ( filter && ctx && renderer )
793  {
794  ctx->expressionContext().setFeature( f );
795  if ( !renderer->willRenderFeature( f, *ctx ) )
796  {
797  continue;
798  }
799  }
800 
801  if ( mTransform.isValid() )
802  {
803  try
804  {
805  QgsGeometry transformedGeometry = f.geometry();
806  transformedGeometry.transform( mTransform );
807  f.setGeometry( transformedGeometry );
808  }
809  catch ( const QgsException &e )
810  {
811  Q_UNUSED( e );
812  // See https://issues.qgis.org/issues/12634
813  QgsDebugMsg( QStringLiteral( "could not transform geometry to map, skipping the snap for it (%1)" ).arg( e.what() ) );
814  continue;
815  }
816  }
817 
818  SpatialIndex::Region r( rect2region( f.geometry().boundingBox() ) );
819  dataList << new RTree::Data( 0, nullptr, r, f.id() );
820 
821  if ( mGeoms.contains( f.id() ) )
822  delete mGeoms.take( f.id() );
823  mGeoms[f.id()] = new QgsGeometry( f.geometry() );
824  ++indexedCount;
825 
826  if ( maxFeaturesToIndex != -1 && indexedCount > maxFeaturesToIndex )
827  {
828  qDeleteAll( dataList );
829  destroyIndex();
830  return false;
831  }
832  }
833 
834  // R-Tree parameters
835  double fillFactor = 0.7;
836  unsigned long indexCapacity = 10;
837  unsigned long leafCapacity = 10;
838  unsigned long dimension = 2;
839  RTree::RTreeVariant variant = RTree::RV_RSTAR;
840  SpatialIndex::id_type indexId;
841 
842  if ( dataList.isEmpty() )
843  {
844  mIsEmptyLayer = true;
845  return true; // no features
846  }
847 
848  QgsPointLocator_Stream stream( dataList );
849  mRTree.reset( RTree::createAndBulkLoadNewRTree( RTree::BLM_STR, stream, *mStorage, fillFactor, indexCapacity,
850  leafCapacity, dimension, variant, indexId ) );
851 
852  if ( ctx && renderer )
853  {
854  renderer->stopRender( *ctx );
855  }
856  return true;
857 }
858 
859 
861 {
862  mRTree.reset();
863 
864  mIsEmptyLayer = false;
865 
866  qDeleteAll( mGeoms );
867 
868  mGeoms.clear();
869 }
870 
871 void QgsPointLocator::onFeatureAdded( QgsFeatureId fid )
872 {
873  if ( !mRTree )
874  {
875  if ( mIsEmptyLayer )
876  rebuildIndex(); // first feature - let's built the index
877  return; // nothing to do if we are not initialized yet
878  }
879 
880  QgsFeature f;
881  if ( mLayer->getFeatures( QgsFeatureRequest( fid ) ).nextFeature( f ) )
882  {
883  if ( !f.hasGeometry() )
884  return;
885 
886  if ( mContext )
887  {
888  std::unique_ptr< QgsFeatureRenderer > renderer( mLayer->renderer() ? mLayer->renderer()->clone() : nullptr );
889  QgsRenderContext *ctx = nullptr;
890 
891  mContext->expressionContext() << QgsExpressionContextUtils::layerScope( mLayer );
892  ctx = mContext.get();
893  if ( renderer && ctx )
894  {
895  bool pass = false;
896  renderer->startRender( *ctx, mLayer->fields() );
897 
898  ctx->expressionContext().setFeature( f );
899  if ( !renderer->willRenderFeature( f, *ctx ) )
900  {
901  pass = true;
902  }
903 
904  renderer->stopRender( *ctx );
905  if ( pass )
906  return;
907  }
908  }
909 
910  if ( mTransform.isValid() )
911  {
912  try
913  {
914  QgsGeometry transformedGeom = f.geometry();
915  transformedGeom.transform( mTransform );
916  f.setGeometry( transformedGeom );
917  }
918  catch ( const QgsException &e )
919  {
920  Q_UNUSED( e );
921  // See https://issues.qgis.org/issues/12634
922  QgsDebugMsg( QStringLiteral( "could not transform geometry to map, skipping the snap for it (%1)" ).arg( e.what() ) );
923  return;
924  }
925  }
926 
927  QgsRectangle bbox = f.geometry().boundingBox();
928  if ( !bbox.isNull() )
929  {
930  SpatialIndex::Region r( rect2region( bbox ) );
931  mRTree->insertData( 0, nullptr, r, f.id() );
932 
933  if ( mGeoms.contains( f.id() ) )
934  delete mGeoms.take( f.id() );
935  mGeoms[fid] = new QgsGeometry( f.geometry() );
936  }
937  }
938 }
939 
940 void QgsPointLocator::onFeatureDeleted( QgsFeatureId fid )
941 {
942  if ( !mRTree )
943  return; // nothing to do if we are not initialized yet
944 
945  if ( mGeoms.contains( fid ) )
946  {
947  mRTree->deleteData( rect2region( mGeoms[fid]->boundingBox() ), fid );
948  delete mGeoms.take( fid );
949  }
950 
951 }
952 
953 void QgsPointLocator::onGeometryChanged( QgsFeatureId fid, const QgsGeometry &geom )
954 {
955  Q_UNUSED( geom );
956  onFeatureDeleted( fid );
957  onFeatureAdded( fid );
958 }
959 
960 void QgsPointLocator::onAttributeValueChanged( QgsFeatureId fid, int idx, const QVariant &value )
961 {
962  Q_UNUSED( idx );
963  Q_UNUSED( value );
964  if ( mContext )
965  {
966  onFeatureDeleted( fid );
967  onFeatureAdded( fid );
968  }
969 }
970 
971 
973 {
974  if ( !mRTree )
975  {
976  init();
977  if ( !mRTree ) // still invalid?
978  return Match();
979  }
980 
981  Match m;
982  QgsPointLocator_VisitorNearestVertex visitor( this, m, point, filter );
983  QgsRectangle rect( point.x() - tolerance, point.y() - tolerance, point.x() + tolerance, point.y() + tolerance );
984  mRTree->intersectsWithQuery( rect2region( rect ), visitor );
985  if ( m.isValid() && m.distance() > tolerance )
986  return Match(); // make sure that only match strictly within the tolerance is returned
987  return m;
988 }
989 
991 {
992  if ( !mRTree )
993  {
994  init();
995  if ( !mRTree ) // still invalid?
996  return Match();
997  }
998 
999  QgsWkbTypes::GeometryType geomType = mLayer->geometryType();
1000  if ( geomType == QgsWkbTypes::PointGeometry )
1001  return Match();
1002 
1003  Match m;
1004  QgsPointLocator_VisitorNearestEdge visitor( this, m, point, filter );
1005  QgsRectangle rect( point.x() - tolerance, point.y() - tolerance, point.x() + tolerance, point.y() + tolerance );
1006  mRTree->intersectsWithQuery( rect2region( rect ), visitor );
1007  if ( m.isValid() && m.distance() > tolerance )
1008  return Match(); // make sure that only match strictly within the tolerance is returned
1009  return m;
1010 }
1011 
1013 {
1014  if ( !mRTree )
1015  {
1016  init();
1017  if ( !mRTree ) // still invalid?
1018  return Match();
1019  }
1020 
1021  MatchList mlist = pointInPolygon( point );
1022  if ( mlist.count() && mlist.at( 0 ).isValid() )
1023  {
1024  return mlist.at( 0 );
1025  }
1026 
1027  if ( tolerance == 0 )
1028  {
1029  return Match();
1030  }
1031 
1032  // discard point and line layers to keep only polygons
1033  QgsWkbTypes::GeometryType geomType = mLayer->geometryType();
1034  if ( geomType == QgsWkbTypes::PointGeometry || geomType == QgsWkbTypes::LineGeometry )
1035  return Match();
1036 
1037  // use edges for adding tolerance
1038  Match m = nearestEdge( point, tolerance, filter );
1039  if ( m.isValid() )
1040  return Match( Area, m.layer(), m.featureId(), m.distance(), m.point() );
1041  else
1042  return Match();
1043 }
1044 
1045 
1047 {
1048  if ( !mRTree )
1049  {
1050  init();
1051  if ( !mRTree ) // still invalid?
1052  return MatchList();
1053  }
1054 
1055  QgsWkbTypes::GeometryType geomType = mLayer->geometryType();
1056  if ( geomType == QgsWkbTypes::PointGeometry )
1057  return MatchList();
1058 
1059  MatchList lst;
1060  QgsPointLocator_VisitorEdgesInRect visitor( this, lst, rect, filter );
1061  mRTree->intersectsWithQuery( rect2region( rect ), visitor );
1062 
1063  return lst;
1064 }
1065 
1067 {
1068  QgsRectangle rect( point.x() - tolerance, point.y() - tolerance, point.x() + tolerance, point.y() + tolerance );
1069  return edgesInRect( rect, filter );
1070 }
1071 
1073 {
1074  if ( !mRTree )
1075  {
1076  init();
1077  if ( !mRTree ) // still invalid?
1078  return MatchList();
1079  }
1080 
1081  MatchList lst;
1082  QgsPointLocator_VisitorVerticesInRect visitor( this, lst, rect, filter );
1083  mRTree->intersectsWithQuery( rect2region( rect ), visitor );
1084 
1085  return lst;
1086 }
1087 
1089 {
1090  QgsRectangle rect( point.x() - tolerance, point.y() - tolerance, point.x() + tolerance, point.y() + tolerance );
1091  return verticesInRect( rect, filter );
1092 }
1093 
1094 
1096 {
1097  if ( !mRTree )
1098  {
1099  init();
1100  if ( !mRTree ) // still invalid?
1101  return MatchList();
1102  }
1103 
1104  QgsWkbTypes::GeometryType geomType = mLayer->geometryType();
1105  if ( geomType == QgsWkbTypes::PointGeometry || geomType == QgsWkbTypes::LineGeometry )
1106  return MatchList();
1107 
1108  MatchList lst;
1109  QgsPointLocator_VisitorArea visitor( this, point, lst );
1110  mRTree->intersectsWithQuery( point2point( point ), visitor );
1111  return lst;
1112 }
#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:41
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...
QgsPointLocator_VisitorVerticesInRect(QgsPointLocator *pl, QgsPointLocator::MatchList &lst, const QgsRectangle &srcRect, QgsPointLocator::MatchFilter *filter=nullptr)
Constructs the visitor.
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 visitData(std::vector< const IData *> &v) override
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:656
#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.
The vertex_iterator class provides STL-style iterator for vertices.
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
void visitNode(const INode &n) 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:177
double xMaximum() const
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:162
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.
MatchList verticesInRect(const QgsRectangle &rect, QgsPointLocator::MatchFilter *filter=nullptr)
Find vertices within a specified recangle Optional filter may discard unwanted matches.
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.
int vertexNrFromVertexId(QgsVertexId id) const
Returns the vertex number corresponding to a vertex id.
#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:436
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:167
void dataChanged()
Data of layer changed.
Helper class used when traversing the index looking for vertices - builds a list of matches...
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:172
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.
QgsAbstractGeometry::vertex_iterator vertices_end() const
Returns STL-style iterator pointing to the imaginary vertex after the last vertex of the geometry...
QgsWkbTypes::Type readHeader() const
readHeader
Definition: qgswkbptr.cpp:54
Defines a QGIS exception class.
Definition: qgsexception.h:34
QgsAbstractGeometry::vertex_iterator vertices_begin() const
Returns STL-style iterator pointing to the first vertex of the geometry.
void visitData(const IData &d) override
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:71
bool isValid() const
Returns whether this CRS is correctly initialized and usable.