QGIS API Documentation 3.43.0-Master (c67cf405802)
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"
26
27#include <spatialindex/SpatialIndex.h>
28#include <QMutex>
29#include <QMutexLocker>
30
31using namespace SpatialIndex;
32
33
34
41class QgisVisitor : public SpatialIndex::IVisitor
42{
43 public:
44 explicit QgisVisitor( QList<QgsFeatureId> &list )
45 : mList( list ) {}
46
47 void visitNode( const INode &n ) override
48 { Q_UNUSED( n ) }
49
50 void visitData( const IData &d ) override
51 {
52 mList.append( d.getIdentifier() );
53 }
54
55 void visitData( std::vector<const IData *> &v ) override
56 { Q_UNUSED( v ) }
57
58 private:
59 QList<QgsFeatureId> &mList;
60};
61
68class QgsSpatialIndexCopyVisitor : public SpatialIndex::IVisitor
69{
70 public:
71 explicit QgsSpatialIndexCopyVisitor( SpatialIndex::ISpatialIndex *newIndex )
72 : mNewIndex( newIndex ) {}
73
74 void visitNode( const INode &n ) override
75 { Q_UNUSED( n ) }
76
77 void visitData( const IData &d ) override
78 {
79 SpatialIndex::IShape *shape = nullptr;
80 d.getShape( &shape );
81 mNewIndex->insertData( 0, nullptr, *shape, d.getIdentifier() );
82 delete shape;
83 }
84
85 void visitData( std::vector<const IData *> &v ) override
86 { Q_UNUSED( v ) }
87
88 private:
89 SpatialIndex::ISpatialIndex *mNewIndex = nullptr;
90};
91
93class QgsNearestNeighborComparator : public INearestNeighborComparator
94{
95 public:
96
97 QgsNearestNeighborComparator( const QHash< QgsFeatureId, QgsGeometry > *geometries, const QgsPointXY &point, double maxDistance )
98 : mGeometries( geometries )
99 , mGeom( QgsGeometry::fromPointXY( point ) )
100 , mMaxDistance( maxDistance )
101 {
102 }
103
104 QgsNearestNeighborComparator( const QHash< QgsFeatureId, QgsGeometry > *geometries, const QgsGeometry &geometry, double maxDistance )
105 : mGeometries( geometries )
106 , mGeom( geometry )
107 , mMaxDistance( maxDistance )
108 {
109 }
110
111 const QHash< QgsFeatureId, QgsGeometry > *mGeometries = nullptr;
112 QgsGeometry mGeom;
113 double mMaxDistance = 0;
114 QSet< QgsFeatureId > mFeaturesOutsideMaxDistance;
115
116 double getMinimumDistance( const IShape &query, const IShape &entry ) override
117 {
118 return query.getMinimumDistance( entry );
119 }
120
121 double getMinimumDistance( const IShape &query, const IData &data ) override
122 {
123 // start with the default implementation, which gives distance to bounding box only
124 IShape *pS;
125 data.getShape( &pS );
126 double dist = query.getMinimumDistance( *pS );
127 delete pS;
128
129 // if doing exact distance search, AND either no max distance specified OR the
130 // distance to the bounding box is less than the max distance, calculate the exact
131 // distance now.
132 // (note: if bounding box is already greater than the distance away then max distance, there's no
133 // point doing this more expensive calculation, since we can't possibly use this feature anyway!)
134 if ( mGeometries && ( mMaxDistance <= 0.0 || dist <= mMaxDistance ) )
135 {
136 const QgsGeometry other = mGeometries->value( data.getIdentifier() );
137 dist = other.distance( mGeom );
138 }
139
140 if ( mMaxDistance > 0 && dist > mMaxDistance )
141 {
142 // feature is outside of maximum distance threshold. Flag it,
143 // but "trick" libspatialindex into considering it as just outside
144 // our max distance region. This means if there's no other closer features (i.e.,
145 // within our actual maximum search distance), the libspatialindex
146 // nearest neighbor test will use this feature and not needlessly continue hunting
147 // through the remaining more distant features in the index.
148 // TODO: add proper API to libspatialindex to allow a maximum search distance in
149 // nearest neighbor tests
150 mFeaturesOutsideMaxDistance.insert( data.getIdentifier() );
151 return mMaxDistance + 0.00000001;
152 }
153 return dist;
154 }
155};
156
163class QgsFeatureIteratorDataStream : public IDataStream
164{
165 public:
167 explicit QgsFeatureIteratorDataStream( const QgsFeatureIterator &fi, QgsFeedback *feedback = nullptr, QgsSpatialIndex::Flags flags = QgsSpatialIndex::Flags(),
168 const std::function< bool( const QgsFeature & ) > *callback = nullptr )
169 : mFi( fi )
170 , mFeedback( feedback )
171 , mFlags( flags )
172 , mCallback( callback )
173 {
174 readNextEntry();
175 }
176
177 ~QgsFeatureIteratorDataStream() override
178 {
179 delete mNextData;
180 }
181
183 IData *getNext() override
184 {
185 if ( mFeedback && mFeedback->isCanceled() )
186 return nullptr;
187
188 RTree::Data *ret = mNextData;
189 mNextData = nullptr;
190 readNextEntry();
191 return ret;
192 }
193
195 bool hasNext() override { return nullptr != mNextData; }
196
198 uint32_t size() override { Q_ASSERT( false && "not available" ); return 0; }
199
201 void rewind() override { Q_ASSERT( false && "not available" ); }
202
203 QHash< QgsFeatureId, QgsGeometry > geometries;
204
205 protected:
206 void readNextEntry()
207 {
208 QgsFeature f;
209 SpatialIndex::Region r;
210 QgsFeatureId id;
211 while ( mFi.nextFeature( f ) )
212 {
213 if ( mCallback )
214 {
215 const bool res = ( *mCallback )( f );
216 if ( !res )
217 {
218 mNextData = nullptr;
219 return;
220 }
221 }
222 if ( QgsSpatialIndex::featureInfo( f, r, id ) )
223 {
224 mNextData = new RTree::Data( 0, nullptr, r, id );
226 geometries.insert( f.id(), f.geometry() );
227 return;
228 }
229 }
230 }
231
232 private:
234 RTree::Data *mNextData = nullptr;
235 QgsFeedback *mFeedback = nullptr;
237 const std::function< bool( const QgsFeature & ) > *mCallback = nullptr;
238
239};
240
241
248class QgsSpatialIndexData : public QSharedData
249{
250 public:
251 QgsSpatialIndexData( QgsSpatialIndex::Flags flags )
252 : mFlags( flags )
253 {
254 initTree();
255 }
256
258
259 QHash< QgsFeatureId, QgsGeometry > mGeometries;
260
269 explicit QgsSpatialIndexData( const QgsFeatureIterator &fi, QgsFeedback *feedback = nullptr, QgsSpatialIndex::Flags flags = QgsSpatialIndex::Flags(),
270 const std::function< bool( const QgsFeature & ) > *callback = nullptr )
271 : mFlags( flags )
272 {
273 QgsFeatureIteratorDataStream fids( fi, feedback, mFlags, callback );
274 initTree( &fids );
276 mGeometries = fids.geometries;
277 }
278
279 QgsSpatialIndexData( const QgsSpatialIndexData &other )
280 : QSharedData( other )
281 , mFlags( other.mFlags )
282 , mGeometries( other.mGeometries )
283 {
284 const QMutexLocker locker( &other.mMutex );
285
286 initTree();
287
288 // copy R-tree data one by one (is there a faster way??)
289 double low[] = { std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest() };
290 double high[] = { std::numeric_limits<double>::max(), std::numeric_limits<double>::max() };
291 const SpatialIndex::Region query( low, high, 2 );
292 QgsSpatialIndexCopyVisitor visitor( mRTree );
293 other.mRTree->intersectsWithQuery( query, visitor );
294 }
295
296 ~QgsSpatialIndexData()
297 {
298 delete mRTree;
299 delete mStorage;
300 }
301
302 QgsSpatialIndexData &operator=( const QgsSpatialIndexData &rh ) = delete;
303
304 void initTree( IDataStream *inputStream = nullptr )
305 {
306 // for now only memory manager
307 mStorage = StorageManager::createNewMemoryStorageManager();
308
309 // R-Tree parameters
310 const double fillFactor = 0.7;
311 const unsigned long indexCapacity = 10;
312 const unsigned long leafCapacity = 10;
313 const unsigned long dimension = 2;
314 const RTree::RTreeVariant variant = RTree::RV_RSTAR;
315
316 // create R-tree
317 SpatialIndex::id_type indexId;
318
319 if ( inputStream && inputStream->hasNext() )
320 mRTree = RTree::createAndBulkLoadNewRTree( RTree::BLM_STR, *inputStream, *mStorage, fillFactor, indexCapacity,
321 leafCapacity, dimension, variant, indexId );
322 else
323 mRTree = RTree::createNewRTree( *mStorage, fillFactor, indexCapacity,
324 leafCapacity, dimension, variant, indexId );
325 }
326
328 SpatialIndex::IStorageManager *mStorage = nullptr;
329
331 SpatialIndex::ISpatialIndex *mRTree = nullptr;
332
333 mutable QRecursiveMutex mMutex;
334
335};
336
338
339// -------------------------------------------------------------------------
340
341
343{
344 d = new QgsSpatialIndexData( flags );
345}
346
348{
349 d = new QgsSpatialIndexData( fi, feedback, flags );
350}
351
353QgsSpatialIndex::QgsSpatialIndex( const QgsFeatureIterator &fi, const std::function< bool( const QgsFeature & )> &callback, QgsSpatialIndex::Flags flags )
354{
355 d = new QgsSpatialIndexData( fi, nullptr, flags, &callback );
356}
358
360{
361 d = new QgsSpatialIndexData( source.getFeatures( QgsFeatureRequest().setNoAttributes() ), feedback, flags );
362}
363
365 : d( other.d )
366{
367}
368
372
374{
375 if ( this != &other )
376 d = other.d;
377 return *this;
378}
379
380bool QgsSpatialIndex::featureInfo( const QgsFeature &f, SpatialIndex::Region &r, QgsFeatureId &id )
381{
382 QgsRectangle rect;
383 if ( !featureInfo( f, rect, id ) )
384 return false;
385
387 return true;
388}
389
390bool QgsSpatialIndex::featureInfo( const QgsFeature &f, QgsRectangle &rect, QgsFeatureId &id )
391{
392 if ( !f.hasGeometry() )
393 return false;
394
395 id = f.id();
396 rect = f.geometry().boundingBox();
397
398 if ( !rect.isFinite() )
399 return false;
400
401 return true;
402}
403
405{
406 QgsRectangle rect;
407 QgsFeatureId id;
408 if ( !featureInfo( feature, rect, id ) )
409 return false;
410
411 if ( addFeature( id, rect ) )
412 {
414 {
415 const QMutexLocker locker( &d->mMutex );
416 d->mGeometries.insert( feature.id(), feature.geometry() );
417 }
418 return true;
419 }
420 return false;
421}
422
424{
425 QgsFeatureList::iterator fIt = features.begin();
426 bool result = true;
427 for ( ; fIt != features.end(); ++fIt )
428 {
429 result = result && addFeature( *fIt, flags );
430 }
431 return result;
432}
433
435{
436 QgsFeature feature( f );
437 return addFeature( feature );
438}
439
441{
442 return addFeature( id, bounds );
443}
444
446{
447 const SpatialIndex::Region r( QgsSpatialIndexUtils::rectangleToRegion( bounds ) );
448
449 const QMutexLocker locker( &d->mMutex );
450
451 // TODO: handle possible exceptions correctly
452 try
453 {
454 d->mRTree->insertData( 0, nullptr, r, FID_TO_NUMBER( id ) );
455 return true;
456 }
457 catch ( Tools::Exception &e )
458 {
459 Q_UNUSED( e )
460 QgsDebugError( QStringLiteral( "Tools::Exception caught: " ).arg( e.what().c_str() ) );
461 }
462 catch ( const std::exception &e )
463 {
464 Q_UNUSED( e )
465 QgsDebugError( QStringLiteral( "std::exception caught: " ).arg( e.what() ) );
466 }
467 catch ( ... )
468 {
469 QgsDebugError( QStringLiteral( "unknown spatial index exception caught" ) );
470 }
471
472 return false;
473}
474
476{
477 SpatialIndex::Region r;
478 QgsFeatureId id;
479 if ( !featureInfo( f, r, id ) )
480 return false;
481
482 const QMutexLocker locker( &d->mMutex );
483 // TODO: handle exceptions
485 d->mGeometries.remove( f.id() );
486 return d->mRTree->deleteData( r, FID_TO_NUMBER( id ) );
487}
488
490{
491 const SpatialIndex::Region r = QgsSpatialIndexUtils::rectangleToRegion( bounds );
492
493 const QMutexLocker locker( &d->mMutex );
494 // TODO: handle exceptions (if the comment in the other ::deleteFeature implementation is correct!)
496 d->mGeometries.remove( id );
497 return d->mRTree->deleteData( r, FID_TO_NUMBER( id ) );
498}
499
500QList<QgsFeatureId> QgsSpatialIndex::intersects( const QgsRectangle &rect ) const
501{
502 QList<QgsFeatureId> list;
503 QgisVisitor visitor( list );
504
505 const SpatialIndex::Region r = QgsSpatialIndexUtils::rectangleToRegion( rect );
506
507 const QMutexLocker locker( &d->mMutex );
508 d->mRTree->intersectsWithQuery( r, visitor );
509
510 return list;
511}
512
513QList<QgsFeatureId> QgsSpatialIndex::nearestNeighbor( const QgsPointXY &point, const int neighbors, const double maxDistance ) const
514{
515 QList<QgsFeatureId> list;
516 QgisVisitor visitor( list );
517
518 double pt[2] = { point.x(), point.y() };
519 const Point p( pt, 2 );
520
521 const QMutexLocker locker( &d->mMutex );
522 QgsNearestNeighborComparator nnc( ( d->mFlags & QgsSpatialIndex::FlagStoreFeatureGeometries ) ? &d->mGeometries : nullptr,
523 point, maxDistance );
524 d->mRTree->nearestNeighborQuery( neighbors, p, visitor, nnc );
525
526 if ( maxDistance > 0 )
527 {
528 // trim features outside of max distance
529 list.erase( std::remove_if( list.begin(), list.end(),
530 [&nnc]( QgsFeatureId id )
531 {
532 return nnc.mFeaturesOutsideMaxDistance.contains( id );
533 } ), list.end() );
534 }
535
536 return list;
537}
538
539QList<QgsFeatureId> QgsSpatialIndex::nearestNeighbor( const QgsGeometry &geometry, int neighbors, double maxDistance ) const
540{
541 QList<QgsFeatureId> list;
542 QgisVisitor visitor( list );
543
544 const SpatialIndex::Region r = QgsSpatialIndexUtils::rectangleToRegion( geometry.boundingBox() );
545
546 const QMutexLocker locker( &d->mMutex );
547 QgsNearestNeighborComparator nnc( ( d->mFlags & QgsSpatialIndex::FlagStoreFeatureGeometries ) ? &d->mGeometries : nullptr,
548 geometry, maxDistance );
549 d->mRTree->nearestNeighborQuery( neighbors, r, visitor, nnc );
550
551 if ( maxDistance > 0 )
552 {
553 // trim features outside of max distance
554 list.erase( std::remove_if( list.begin(), list.end(),
555 [&nnc]( QgsFeatureId id )
556 {
557 return nnc.mFeaturesOutsideMaxDistance.contains( id );
558 } ), list.end() );
559 }
560
561 return list;
562}
563
565{
566 const QMutexLocker locker( &d->mMutex );
567 return d->mGeometries.value( id );
568}
569
570QAtomicInt QgsSpatialIndex::refs() const
571{
572 return d->ref;
573}
Custom visitor that adds found features to list.
void visitNode(const INode &n) override
void visitData(std::vector< const IData * > &v) override
QgisVisitor(QList< QgsFeatureId > &list)
void visitData(const IData &d) override
Wrapper for iterator of features from vector data provider or vector layer.
Wraps a request for features to a vector layer (or directly its vector data provider).
QFlags< Flag > Flags
An interface for objects which provide features via a getFeatures method.
virtual QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const =0
Returns an iterator for the features in the source.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:58
QgsFeatureId id
Definition qgsfeature.h:66
QgsGeometry geometry
Definition qgsfeature.h:69
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition qgsfeedback.h:44
A geometry is the spatial representation of a feature.
double distance(const QgsGeometry &geom) const
Returns the minimum distance between this geometry and another geometry.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
Represents a 2D point.
Definition qgspointxy.h:60
double y
Definition qgspointxy.h:64
double x
Definition qgspointxy.h:63
A rectangle specified with double values.
bool isFinite() const
Returns true if the rectangle has finite boundaries.
SpatialIndex visitor which copies data to a new index.
void visitData(std::vector< const IData * > &v) override
void visitData(const IData &d) override
QgsSpatialIndexCopyVisitor(SpatialIndex::ISpatialIndex *newIndex)
void visitNode(const INode &n) override
static SpatialIndex::Region rectangleToRegion(const QgsRectangle &rectangle)
Converts a QGIS rectangle to a SpatialIndex region.
A spatial index for QgsFeature objects.
bool addFeatures(QgsFeatureList &features, QgsFeatureSink::Flags flags=QgsFeatureSink::Flags()) override
Adds a list of features to the index.
@ FlagStoreFeatureGeometries
Indicates that the spatial index should also store feature geometries. This requires more memory,...
QgsSpatialIndex & operator=(const QgsSpatialIndex &other)
QgsSpatialIndex(QgsSpatialIndex::Flags flags=QgsSpatialIndex::Flags())
Constructor for QgsSpatialIndex.
QAtomicInt refs() const
Gets reference count - just for debugging!
QList< QgsFeatureId > nearestNeighbor(const QgsPointXY &point, int neighbors=1, double maxDistance=0) const
Returns nearest neighbors to a point.
QList< QgsFeatureId > intersects(const QgsRectangle &rectangle) const
Returns a list of features with a bounding box which intersects the specified rectangle.
~QgsSpatialIndex() override
Destructor finalizes work with spatial index.
bool addFeature(QgsFeature &feature, QgsFeatureSink::Flags flags=QgsFeatureSink::Flags()) override
Adds a feature to the index.
QFlags< Flag > Flags
Q_DECL_DEPRECATED bool insertFeature(const QgsFeature &feature)
Adds a feature to the index.
QgsGeometry geometry(QgsFeatureId id) const
Returns the stored geometry for the indexed feature with matching id.
bool deleteFeature(const QgsFeature &feature)
Removes a feature from the index.
QList< QgsFeature > QgsFeatureList
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
#define FID_TO_NUMBER(fid)
#define QgsDebugError(str)
Definition qgslogger.h:40