QGIS API Documentation  3.6.0-Noosa (5873452)
qgsspatialindex.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsspatialindex.cpp - wrapper class for spatial index library
3  ----------------------
4  begin : December 2006
5  copyright : (C) 2006 by Martin Dobias
6  email : wonder.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 "qgsspatialindex.h"
17 
18 #include "qgsgeometry.h"
19 #include "qgsfeature.h"
20 #include "qgsfeatureiterator.h"
21 #include "qgsrectangle.h"
22 #include "qgslogger.h"
23 #include "qgsfeaturesource.h"
24 #include "qgsfeedback.h"
25 
26 #include <spatialindex/SpatialIndex.h>
27 #include <QMutex>
28 #include <QMutexLocker>
29 
30 using namespace SpatialIndex;
31 
32 
33 
40 class QgisVisitor : public SpatialIndex::IVisitor
41 {
42  public:
43  explicit QgisVisitor( QList<QgsFeatureId> &list )
44  : mList( list ) {}
45 
46  void visitNode( const INode &n ) override
47  { Q_UNUSED( n ); }
48 
49  void visitData( const IData &d ) override
50  {
51  mList.append( d.getIdentifier() );
52  }
53 
54  void visitData( std::vector<const IData *> &v ) override
55  { Q_UNUSED( v ); }
56 
57  private:
58  QList<QgsFeatureId> &mList;
59 };
60 
66 class QgsSpatialIndexCopyVisitor : public SpatialIndex::IVisitor
67 {
68  public:
69  explicit QgsSpatialIndexCopyVisitor( SpatialIndex::ISpatialIndex *newIndex )
70  : mNewIndex( newIndex ) {}
71 
72  void visitNode( const INode &n ) override
73  { Q_UNUSED( n ); }
74 
75  void visitData( const IData &d ) override
76  {
77  SpatialIndex::IShape *shape = nullptr;
78  d.getShape( &shape );
79  mNewIndex->insertData( 0, nullptr, *shape, d.getIdentifier() );
80  delete shape;
81  }
82 
83  void visitData( std::vector<const IData *> &v ) override
84  { Q_UNUSED( v ); }
85 
86  private:
87  SpatialIndex::ISpatialIndex *mNewIndex = nullptr;
88 };
89 
91 class QgsNearestNeighborComparator : public INearestNeighborComparator
92 {
93  public:
94 
95  QgsNearestNeighborComparator( const QHash< QgsFeatureId, QgsGeometry > *geometries, const QgsPointXY &point, double maxDistance )
96  : mGeometries( geometries )
97  , mPoint( QgsGeometry::fromPointXY( point ) )
98  , mMaxDistance( maxDistance )
99  {
100  }
101 
102  const QHash< QgsFeatureId, QgsGeometry > *mGeometries = nullptr;
103  QgsGeometry mPoint;
104  double mMaxDistance = 0;
105  QSet< QgsFeatureId > mFeaturesOutsideMaxDistance;
106 
107  double getMinimumDistance( const IShape &query, const IShape &entry ) override
108  {
109  return query.getMinimumDistance( entry );
110  }
111 
112  double getMinimumDistance( const IShape &query, const IData &data ) override
113  {
114  // start with the default implementation, which gives distance to bounding box only
115  IShape *pS;
116  data.getShape( &pS );
117  double dist = query.getMinimumDistance( *pS );
118  delete pS;
119 
120  // if doing exact distance search, AND either no max distance specified OR the
121  // distance to the bounding box is less than the max distance, calculate the exact
122  // distance now.
123  // (note: if bounding box is already greater than the distance away then max distance, there's no
124  // point doing this more expensive calculation, since we can't possibly use this feature anyway!)
125  if ( mGeometries && ( mMaxDistance <= 0.0 || dist <= mMaxDistance ) )
126  {
127  QgsGeometry other = mGeometries->value( data.getIdentifier() );
128  dist = other.distance( mPoint );
129  }
130 
131  if ( mMaxDistance > 0 && dist > mMaxDistance )
132  {
133  // feature is outside of maximum distance threshold. Flag it,
134  // but "trick" libspatialindex into considering it as just outside
135  // our max distance region. This means if there's no other closer features (i.e.,
136  // within our actual maximum search distance), the libspatialindex
137  // nearest neighbor test will use this feature and not needlessly continue hunting
138  // through the remaining more distant features in the index.
139  // TODO: add proper API to libspatialindex to allow a maximum search distance in
140  // nearest neighbor tests
141  mFeaturesOutsideMaxDistance.insert( data.getIdentifier() );
142  return mMaxDistance + 0.00000001;
143  }
144  return dist;
145  }
146 };
147 
154 class QgsFeatureIteratorDataStream : public IDataStream
155 {
156  public:
158  explicit QgsFeatureIteratorDataStream( const QgsFeatureIterator &fi, QgsFeedback *feedback = nullptr, QgsSpatialIndex::Flags flags = nullptr )
159  : mFi( fi )
160  , mFeedback( feedback )
161  , mFlags( flags )
162  {
163  readNextEntry();
164  }
165 
166  ~QgsFeatureIteratorDataStream() override
167  {
168  delete mNextData;
169  }
170 
172  IData *getNext() override
173  {
174  if ( mFeedback && mFeedback->isCanceled() )
175  return nullptr;
176 
177  RTree::Data *ret = mNextData;
178  mNextData = nullptr;
179  readNextEntry();
180  return ret;
181  }
182 
184  bool hasNext() override { return nullptr != mNextData; }
185 
187  uint32_t size() override { Q_ASSERT( false && "not available" ); return 0; }
188 
190  void rewind() override { Q_ASSERT( false && "not available" ); }
191 
192  QHash< QgsFeatureId, QgsGeometry > geometries;
193 
194  protected:
195  void readNextEntry()
196  {
197  QgsFeature f;
198  SpatialIndex::Region r;
199  QgsFeatureId id;
200  while ( mFi.nextFeature( f ) )
201  {
202  if ( QgsSpatialIndex::featureInfo( f, r, id ) )
203  {
204  mNextData = new RTree::Data( 0, nullptr, r, id );
206  geometries.insert( f.id(), f.geometry() );
207  return;
208  }
209  }
210  }
211 
212  private:
213  QgsFeatureIterator mFi;
214  RTree::Data *mNextData = nullptr;
215  QgsFeedback *mFeedback = nullptr;
216  QgsSpatialIndex::Flags mFlags = nullptr;
217 
218 };
219 
220 
227 class QgsSpatialIndexData : public QSharedData
228 {
229  public:
230  QgsSpatialIndexData( QgsSpatialIndex::Flags flags )
231  : mFlags( flags )
232  {
233  initTree();
234  }
235 
236  QgsSpatialIndex::Flags mFlags = nullptr;
237 
238  QHash< QgsFeatureId, QgsGeometry > mGeometries;
239 
248  explicit QgsSpatialIndexData( const QgsFeatureIterator &fi, QgsFeedback *feedback = nullptr, QgsSpatialIndex::Flags flags = nullptr )
249  : mFlags( flags )
250  {
251  QgsFeatureIteratorDataStream fids( fi, feedback, mFlags );
252  initTree( &fids );
254  mGeometries = fids.geometries;
255  }
256 
257  QgsSpatialIndexData( const QgsSpatialIndexData &other )
258  : QSharedData( other )
259  , mFlags( other.mFlags )
260  , mGeometries( other.mGeometries )
261  {
262  QMutexLocker locker( &other.mMutex );
263 
264  initTree();
265 
266  // copy R-tree data one by one (is there a faster way??)
267  double low[] = { std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest() };
268  double high[] = { std::numeric_limits<double>::max(), std::numeric_limits<double>::max() };
269  SpatialIndex::Region query( low, high, 2 );
270  QgsSpatialIndexCopyVisitor visitor( mRTree );
271  other.mRTree->intersectsWithQuery( query, visitor );
272  }
273 
274  ~QgsSpatialIndexData()
275  {
276  delete mRTree;
277  delete mStorage;
278  }
279 
280  QgsSpatialIndexData &operator=( const QgsSpatialIndexData &rh ) = delete;
281 
282  void initTree( IDataStream *inputStream = nullptr )
283  {
284  // for now only memory manager
285  mStorage = StorageManager::createNewMemoryStorageManager();
286 
287  // R-Tree parameters
288  double fillFactor = 0.7;
289  unsigned long indexCapacity = 10;
290  unsigned long leafCapacity = 10;
291  unsigned long dimension = 2;
292  RTree::RTreeVariant variant = RTree::RV_RSTAR;
293 
294  // create R-tree
295  SpatialIndex::id_type indexId;
296 
297  if ( inputStream && inputStream->hasNext() )
298  mRTree = RTree::createAndBulkLoadNewRTree( RTree::BLM_STR, *inputStream, *mStorage, fillFactor, indexCapacity,
299  leafCapacity, dimension, variant, indexId );
300  else
301  mRTree = RTree::createNewRTree( *mStorage, fillFactor, indexCapacity,
302  leafCapacity, dimension, variant, indexId );
303  }
304 
306  SpatialIndex::IStorageManager *mStorage = nullptr;
307 
309  SpatialIndex::ISpatialIndex *mRTree = nullptr;
310 
311  mutable QMutex mMutex;
312 
313 };
314 
316 
317 // -------------------------------------------------------------------------
318 
319 
320 QgsSpatialIndex::QgsSpatialIndex( QgsSpatialIndex::Flags flags )
321 {
322  d = new QgsSpatialIndexData( flags );
323 }
324 
325 QgsSpatialIndex::QgsSpatialIndex( const QgsFeatureIterator &fi, QgsFeedback *feedback, QgsSpatialIndex::Flags flags )
326 {
327  d = new QgsSpatialIndexData( fi, feedback, flags );
328 }
329 
330 QgsSpatialIndex::QgsSpatialIndex( const QgsFeatureSource &source, QgsFeedback *feedback, QgsSpatialIndex::Flags flags )
331 {
332  d = new QgsSpatialIndexData( source.getFeatures( QgsFeatureRequest().setNoAttributes() ), feedback, flags );
333 }
334 
336  : d( other.d )
337 {
338 }
339 
341 {
342 }
343 
345 {
346  if ( this != &other )
347  d = other.d;
348  return *this;
349 }
350 
351 SpatialIndex::Region QgsSpatialIndex::rectToRegion( const QgsRectangle &rect )
352 {
353  double pt1[2] = { rect.xMinimum(), rect.yMinimum() },
354  pt2[2] = { rect.xMaximum(), rect.yMaximum() };
355  return SpatialIndex::Region( pt1, pt2, 2 );
356 }
357 
358 bool QgsSpatialIndex::featureInfo( const QgsFeature &f, SpatialIndex::Region &r, QgsFeatureId &id )
359 {
360  QgsRectangle rect;
361  if ( !featureInfo( f, rect, id ) )
362  return false;
363 
364  r = rectToRegion( rect );
365  return true;
366 }
367 
368 bool QgsSpatialIndex::featureInfo( const QgsFeature &f, QgsRectangle &rect, QgsFeatureId &id )
369 {
370  if ( !f.hasGeometry() )
371  return false;
372 
373  id = f.id();
374  rect = f.geometry().boundingBox();
375  return true;
376 }
377 
378 bool QgsSpatialIndex::addFeature( QgsFeature &feature, QgsFeatureSink::Flags )
379 {
380  QgsRectangle rect;
381  QgsFeatureId id;
382  if ( !featureInfo( feature, rect, id ) )
383  return false;
384 
385  if ( addFeature( id, rect ) )
386  {
388  {
389  QMutexLocker locker( &d->mMutex );
390  d->mGeometries.insert( feature.id(), feature.geometry() );
391  }
392  return true;
393  }
394  return false;
395 }
396 
397 bool QgsSpatialIndex::addFeatures( QgsFeatureList &features, QgsFeatureSink::Flags flags )
398 {
399  QgsFeatureList::iterator fIt = features.begin();
400  bool result = true;
401  for ( ; fIt != features.end(); ++fIt )
402  {
403  result = result && addFeature( *fIt, flags );
404  }
405  return result;
406 }
407 
409 {
410  QgsFeature feature( f );
411  return addFeature( feature );
412 }
413 
415 {
416  return addFeature( id, bounds );
417 }
418 
420 {
421  SpatialIndex::Region r( rectToRegion( bounds ) );
422 
423  QMutexLocker locker( &d->mMutex );
424 
425  // TODO: handle possible exceptions correctly
426  try
427  {
428  d->mRTree->insertData( 0, nullptr, r, FID_TO_NUMBER( id ) );
429  return true;
430  }
431  catch ( Tools::Exception &e )
432  {
433  Q_UNUSED( e );
434  QgsDebugMsg( QStringLiteral( "Tools::Exception caught: " ).arg( e.what().c_str() ) );
435  }
436  catch ( const std::exception &e )
437  {
438  Q_UNUSED( e );
439  QgsDebugMsg( QStringLiteral( "std::exception caught: " ).arg( e.what() ) );
440  }
441  catch ( ... )
442  {
443  QgsDebugMsg( QStringLiteral( "unknown spatial index exception caught" ) );
444  }
445 
446  return false;
447 }
448 
450 {
451  SpatialIndex::Region r;
452  QgsFeatureId id;
453  if ( !featureInfo( f, r, id ) )
454  return false;
455 
456  QMutexLocker locker( &d->mMutex );
457  // TODO: handle exceptions
459  d->mGeometries.remove( f.id() );
460  return d->mRTree->deleteData( r, FID_TO_NUMBER( id ) );
461 }
462 
463 QList<QgsFeatureId> QgsSpatialIndex::intersects( const QgsRectangle &rect ) const
464 {
465  QList<QgsFeatureId> list;
466  QgisVisitor visitor( list );
467 
468  SpatialIndex::Region r = rectToRegion( rect );
469 
470  QMutexLocker locker( &d->mMutex );
471  d->mRTree->intersectsWithQuery( r, visitor );
472 
473  return list;
474 }
475 
476 QList<QgsFeatureId> QgsSpatialIndex::nearestNeighbor( const QgsPointXY &point, const int neighbors, const double maxDistance ) const
477 {
478  QList<QgsFeatureId> list;
479  QgisVisitor visitor( list );
480 
481  double pt[2] = { point.x(), point.y() };
482  Point p( pt, 2 );
483 
484  QMutexLocker locker( &d->mMutex );
485  QgsNearestNeighborComparator nnc( d->mFlags & QgsSpatialIndex::FlagStoreFeatureGeometries ? &d->mGeometries : nullptr,
486  point, maxDistance );
487  d->mRTree->nearestNeighborQuery( neighbors, p, visitor, nnc );
488 
489  if ( maxDistance > 0 )
490  {
491  // trim features outside of max distance
492  list.erase( std::remove_if( list.begin(), list.end(),
493  [&nnc]( QgsFeatureId id )
494  {
495  return nnc.mFeaturesOutsideMaxDistance.contains( id );
496  } ), list.end() );
497  }
498 
499  return list;
500 }
501 
503 {
504  QMutexLocker locker( &d->mMutex );
505  return d->mGeometries.value( id );
506 }
507 
508 QAtomicInt QgsSpatialIndex::refs() const
509 {
510  return d->ref;
511 }
QgsFeatureId id
Definition: qgsfeature.h:64
Wrapper for iterator of features from vector data provider or vector layer.
A rectangle specified with double values.
Definition: qgsrectangle.h:41
QgsGeometry geometry(QgsFeatureId id) const
Returns the stored geometry for the indexed feature with matching id.
QgsSpatialIndex & operator=(const QgsSpatialIndex &other)
Implement assignment operator.
double distance(const QgsGeometry &geom) const
Returns the minimum distance between this geometry and another geometry, using GEOS.
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QList< QgsFeature > QgsFeatureList
Definition: qgsfeature.h:571
double y
Definition: qgspointxy.h:48
A class to represent a 2D point.
Definition: qgspointxy.h:43
qint64 QgsFeatureId
Definition: qgsfeatureid.h:25
void visitNode(const INode &n) override
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:106
Q_DECL_DEPRECATED bool insertFeature(const QgsFeature &feature)
Adds a feature to the index.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:55
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Definition: qgsfeature.cpp:197
QgsSpatialIndexCopyVisitor(SpatialIndex::ISpatialIndex *newIndex)
void visitNode(const INode &n) override
Base class for feedback objects to be used for cancelation of something running in a worker thread...
Definition: qgsfeedback.h:44
QgisVisitor(QList< QgsFeatureId > &list)
void visitData(std::vector< const IData *> &v) override
This class wraps a request for features to a vector layer (or directly its vector data provider)...
void visitData(const IData &d) override
void visitData(std::vector< const IData *> &v) override
double x
Definition: qgspointxy.h:47
QgsSpatialIndex(QgsSpatialIndex::Flags flags=nullptr)
Constructor for QgsSpatialIndex.
void visitData(const IData &d) 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
bool deleteFeature(const QgsFeature &feature)
Removes a feature from the index.
A spatial index for QgsFeature objects.
An interface for objects which provide features via a getFeatures method.
bool addFeatures(QgsFeatureList &features, QgsFeatureSink::Flags flags=nullptr) override
Adds a list of features to the index.
Custom visitor that adds found features to list.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
#define FID_TO_NUMBER(fid)
Definition: qgsfeatureid.h:29
double xMinimum() const
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:167
double yMaximum() const
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:172
QgsGeometry geometry
Definition: qgsfeature.h:67
bool addFeature(QgsFeature &feature, QgsFeatureSink::Flags flags=nullptr) override
Adds a feature to the index.
QList< QgsFeatureId > nearestNeighbor(const QgsPointXY &point, int neighbors=1, double maxDistance=0) const
Returns nearest neighbors to a point.
~QgsSpatialIndex() override
Destructor finalizes work with spatial index.
QList< QgsFeatureId > intersects(const QgsRectangle &rectangle) const
Returns a list of features with a bounding box which intersects the specified rectangle.
static QgsGeometry fromPointXY(const QgsPointXY &point)
Creates a new geometry from a QgsPointXY object.
Indicates that the spatial index should also store feature geometries. This requires more memory...
virtual QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const =0
Returns an iterator for the features in the source.
QAtomicInt refs() const
Gets reference count - just for debugging!