QGIS API Documentation 3.99.0-Master (8e76e220402)
Loading...
Searching...
No Matches
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 <spatialindex/SpatialIndex.h>
19
20#include "qgsfeature.h"
21#include "qgsfeatureiterator.h"
22#include "qgsfeaturesource.h"
23#include "qgsfeedback.h"
24#include "qgsgeometry.h"
25#include "qgslogger.h"
26#include "qgsrectangle.h"
28
29#include <QMutex>
30#include <QMutexLocker>
31#include <QString>
32
33using namespace Qt::StringLiterals;
34
35using namespace SpatialIndex;
36
37
38
45class QgisVisitor : public SpatialIndex::IVisitor
46{
47 public:
48 explicit QgisVisitor( QList<QgsFeatureId> &list )
49 : mList( list ) {}
50
51 void visitNode( const INode &n ) override
52 { Q_UNUSED( n ) }
53
54 void visitData( const IData &d ) override
55 {
56 mList.append( d.getIdentifier() );
57 }
58
59 void visitData( std::vector<const IData *> &v ) override
60 { Q_UNUSED( v ) }
61
62 private:
63 QList<QgsFeatureId> &mList;
64};
65
72class QgsSpatialIndexCopyVisitor : public SpatialIndex::IVisitor
73{
74 public:
75 explicit QgsSpatialIndexCopyVisitor( SpatialIndex::ISpatialIndex *newIndex )
76 : mNewIndex( newIndex ) {}
77
78 void visitNode( const INode &n ) override
79 { Q_UNUSED( n ) }
80
81 void visitData( const IData &d ) override
82 {
83 SpatialIndex::IShape *shape = nullptr;
84 d.getShape( &shape );
85 mNewIndex->insertData( 0, nullptr, *shape, d.getIdentifier() );
86 delete shape;
87 }
88
89 void visitData( std::vector<const IData *> &v ) override
90 { Q_UNUSED( v ) }
91
92 private:
93 SpatialIndex::ISpatialIndex *mNewIndex = nullptr;
94};
95
97class QgsNearestNeighborComparator : public INearestNeighborComparator
98{
99 public:
100
101 QgsNearestNeighborComparator( const QHash< QgsFeatureId, QgsGeometry > *geometries, const QgsPointXY &point, double maxDistance )
102 : mGeometries( geometries )
103 , mGeom( QgsGeometry::fromPointXY( point ) )
104 , mMaxDistance( maxDistance )
105 {
106 }
107
108 QgsNearestNeighborComparator( const QHash< QgsFeatureId, QgsGeometry > *geometries, const QgsGeometry &geometry, double maxDistance )
109 : mGeometries( geometries )
110 , mGeom( geometry )
111 , mMaxDistance( maxDistance )
112 {
113 }
114
115 const QHash< QgsFeatureId, QgsGeometry > *mGeometries = nullptr;
116 QgsGeometry mGeom;
117 double mMaxDistance = 0;
118 QSet< QgsFeatureId > mFeaturesOutsideMaxDistance;
119
120 double getMinimumDistance( const IShape &query, const IShape &entry ) override
121 {
122 return query.getMinimumDistance( entry );
123 }
124
125 double getMinimumDistance( const IShape &query, const IData &data ) override
126 {
127 // start with the default implementation, which gives distance to bounding box only
128 IShape *pS;
129 data.getShape( &pS );
130 double dist = query.getMinimumDistance( *pS );
131 delete pS;
132
133 // if doing exact distance search, AND either no max distance specified OR the
134 // distance to the bounding box is less than the max distance, calculate the exact
135 // distance now.
136 // (note: if bounding box is already greater than the distance away then max distance, there's no
137 // point doing this more expensive calculation, since we can't possibly use this feature anyway!)
138 if ( mGeometries && ( mMaxDistance <= 0.0 || dist <= mMaxDistance ) )
139 {
140 const QgsGeometry other = mGeometries->value( data.getIdentifier() );
141 dist = other.distance( mGeom );
142 }
143
144 if ( mMaxDistance > 0 && dist > mMaxDistance )
145 {
146 // feature is outside of maximum distance threshold. Flag it,
147 // but "trick" libspatialindex into considering it as just outside
148 // our max distance region. This means if there's no other closer features (i.e.,
149 // within our actual maximum search distance), the libspatialindex
150 // nearest neighbor test will use this feature and not needlessly continue hunting
151 // through the remaining more distant features in the index.
152 // TODO: add proper API to libspatialindex to allow a maximum search distance in
153 // nearest neighbor tests
154 mFeaturesOutsideMaxDistance.insert( data.getIdentifier() );
155 return mMaxDistance + 0.00000001;
156 }
157 return dist;
158 }
159};
160
167class QgsFeatureIteratorDataStream : public IDataStream
168{
169 public:
171 explicit QgsFeatureIteratorDataStream( const QgsFeatureIterator &fi, QgsFeedback *feedback = nullptr, QgsSpatialIndex::Flags flags = QgsSpatialIndex::Flags(),
172 const std::function< bool( const QgsFeature & ) > *callback = nullptr )
173 : mFi( fi )
174 , mFeedback( feedback )
175 , mFlags( flags )
176 , mCallback( callback )
177 {
178 readNextEntry();
179 }
180
181 ~QgsFeatureIteratorDataStream() override
182 {
183 delete mNextData;
184 }
185
187 IData *getNext() override
188 {
189 if ( mFeedback && mFeedback->isCanceled() )
190 return nullptr;
191
192 RTree::Data *ret = mNextData;
193 mNextData = nullptr;
194 readNextEntry();
195 return ret;
196 }
197
199 bool hasNext() override { return nullptr != mNextData; }
200
202 uint32_t size() override { Q_ASSERT( false && "not available" ); return 0; }
203
205 void rewind() override { Q_ASSERT( false && "not available" ); }
206
207 QHash< QgsFeatureId, QgsGeometry > geometries;
208
209 protected:
210 void readNextEntry()
211 {
212 QgsFeature f;
213 SpatialIndex::Region r;
214 QgsFeatureId id;
215 while ( mFi.nextFeature( f ) )
216 {
217 if ( mCallback )
218 {
219 const bool res = ( *mCallback )( f );
220 if ( !res )
221 {
222 mNextData = nullptr;
223 return;
224 }
225 }
226 if ( QgsSpatialIndex::featureInfo( f, r, id ) )
227 {
228 mNextData = new RTree::Data( 0, nullptr, r, id );
230 geometries.insert( f.id(), f.geometry() );
231 return;
232 }
233 }
234 }
235
236 private:
237 QgsFeatureIterator mFi;
238 RTree::Data *mNextData = nullptr;
239 QgsFeedback *mFeedback = nullptr;
241 const std::function< bool( const QgsFeature & ) > *mCallback = nullptr;
242
243};
244
245
252class QgsSpatialIndexData : public QSharedData
253{
254 public:
255 QgsSpatialIndexData( QgsSpatialIndex::Flags flags )
256 : mFlags( flags )
257 {
258 initTree();
259 }
260
262
263 QHash< QgsFeatureId, QgsGeometry > mGeometries;
264
273 explicit QgsSpatialIndexData( const QgsFeatureIterator &fi, QgsFeedback *feedback = nullptr, QgsSpatialIndex::Flags flags = QgsSpatialIndex::Flags(),
274 const std::function< bool( const QgsFeature & ) > *callback = nullptr )
275 : mFlags( flags )
276 {
277 QgsFeatureIteratorDataStream fids( fi, feedback, mFlags, callback );
278 initTree( &fids );
280 mGeometries = fids.geometries;
281 }
282
283 QgsSpatialIndexData( const QgsSpatialIndexData &other )
284 : QSharedData( other )
285 , mFlags( other.mFlags )
286 , mGeometries( other.mGeometries )
287 {
288 const QMutexLocker locker( &other.mMutex );
289
290 initTree();
291
292 // copy R-tree data one by one (is there a faster way??)
293 double low[] = { std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest() };
294 double high[] = { std::numeric_limits<double>::max(), std::numeric_limits<double>::max() };
295 const SpatialIndex::Region query( low, high, 2 );
296 QgsSpatialIndexCopyVisitor visitor( mRTree );
297 other.mRTree->intersectsWithQuery( query, visitor );
298 }
299
300 ~QgsSpatialIndexData()
301 {
302 delete mRTree;
303 delete mStorage;
304 }
305
306 QgsSpatialIndexData &operator=( const QgsSpatialIndexData &rh ) = delete;
307
308 void initTree( IDataStream *inputStream = nullptr )
309 {
310 // for now only memory manager
311 mStorage = StorageManager::createNewMemoryStorageManager();
312
313 // R-Tree parameters
314 const double fillFactor = 0.7;
315 const unsigned long indexCapacity = 10;
316 const unsigned long leafCapacity = 10;
317 const unsigned long dimension = 2;
318 const RTree::RTreeVariant variant = RTree::RV_RSTAR;
319
320 // create R-tree
321 SpatialIndex::id_type indexId;
322
323 if ( inputStream && inputStream->hasNext() )
324 mRTree = RTree::createAndBulkLoadNewRTree( RTree::BLM_STR, *inputStream, *mStorage, fillFactor, indexCapacity,
325 leafCapacity, dimension, variant, indexId );
326 else
327 mRTree = RTree::createNewRTree( *mStorage, fillFactor, indexCapacity,
328 leafCapacity, dimension, variant, indexId );
329 }
330
332 SpatialIndex::IStorageManager *mStorage = nullptr;
333
335 SpatialIndex::ISpatialIndex *mRTree = nullptr;
336
337 mutable QRecursiveMutex mMutex;
338
339};
340
342
343// -------------------------------------------------------------------------
344
345
347{
348 d = new QgsSpatialIndexData( flags );
349}
350
352{
353 d = new QgsSpatialIndexData( fi, feedback, flags );
354}
355
357QgsSpatialIndex::QgsSpatialIndex( const QgsFeatureIterator &fi, const std::function< bool( const QgsFeature & )> &callback, QgsSpatialIndex::Flags flags )
358{
359 d = new QgsSpatialIndexData( fi, nullptr, flags, &callback );
360}
362
364{
365 d = new QgsSpatialIndexData( source.getFeatures( QgsFeatureRequest().setNoAttributes() ), feedback, flags );
366}
367
369 : d( other.d )
370{
371}
372
376
378{
379 if ( this != &other )
380 d = other.d;
381 return *this;
382}
383
384bool QgsSpatialIndex::featureInfo( const QgsFeature &f, SpatialIndex::Region &r, QgsFeatureId &id )
385{
386 QgsRectangle rect;
387 if ( !featureInfo( f, rect, id ) )
388 return false;
389
391 return true;
392}
393
394bool QgsSpatialIndex::featureInfo( const QgsFeature &f, QgsRectangle &rect, QgsFeatureId &id )
395{
396 if ( !f.hasGeometry() )
397 return false;
398
399 id = f.id();
400 rect = f.geometry().boundingBox();
401
402 if ( !rect.isFinite() )
403 return false;
404
405 return true;
406}
407
409{
410 QgsRectangle rect;
411 QgsFeatureId id;
412 if ( !featureInfo( feature, rect, id ) )
413 return false;
414
415 if ( addFeature( id, rect ) )
416 {
418 {
419 const QMutexLocker locker( &d->mMutex );
420 d->mGeometries.insert( feature.id(), feature.geometry() );
421 }
422 return true;
423 }
424 return false;
425}
426
428{
429 QgsFeatureList::iterator fIt = features.begin();
430 bool result = true;
431 for ( ; fIt != features.end(); ++fIt )
432 {
433 result = result && addFeature( *fIt, flags );
434 }
435 return result;
436}
437
439{
440 QgsFeature feature( f );
441 return addFeature( feature );
442}
443
445{
446 return addFeature( id, bounds );
447}
448
450{
451 const SpatialIndex::Region r( QgsSpatialIndexUtils::rectangleToRegion( bounds ) );
452
453 const QMutexLocker locker( &d->mMutex );
454
455 // TODO: handle possible exceptions correctly
456 try
457 {
458 d->mRTree->insertData( 0, nullptr, r, FID_TO_NUMBER( id ) );
459 return true;
460 }
461 catch ( Tools::Exception &e )
462 {
463 Q_UNUSED( e )
464 QgsDebugError( u"Tools::Exception caught when adding feature to QgsSpatialIndex: %1"_s.arg( e.what().c_str() ) );
465 }
466 catch ( const std::exception &e )
467 {
468 Q_UNUSED( e )
469 QgsDebugError( u"std::exception caught when adding feature to QgsSpatialIndex: %1"_s.arg( e.what() ) );
470 }
471 catch ( ... )
472 {
473 QgsDebugError( u"unknown spatial index exception caught when adding feature to QgsSpatialIndex"_s );
474 }
475
476 return false;
477}
478
480{
481 SpatialIndex::Region r;
482 QgsFeatureId id;
483 if ( !featureInfo( f, r, id ) )
484 return false;
485
486 const QMutexLocker locker( &d->mMutex );
487 // TODO: handle exceptions
489 d->mGeometries.remove( f.id() );
490 return d->mRTree->deleteData( r, FID_TO_NUMBER( id ) );
491}
492
494{
495 const SpatialIndex::Region r = QgsSpatialIndexUtils::rectangleToRegion( bounds );
496
497 const QMutexLocker locker( &d->mMutex );
498 // TODO: handle exceptions (if the comment in the other ::deleteFeature implementation is correct!)
500 d->mGeometries.remove( id );
501 return d->mRTree->deleteData( r, FID_TO_NUMBER( id ) );
502}
503
504QList<QgsFeatureId> QgsSpatialIndex::intersects( const QgsRectangle &rect ) const
505{
506 QList<QgsFeatureId> list;
507 QgisVisitor visitor( list );
508
509 const SpatialIndex::Region r = QgsSpatialIndexUtils::rectangleToRegion( rect );
510
511 const QMutexLocker locker( &d->mMutex );
512 d->mRTree->intersectsWithQuery( r, visitor );
513
514 return list;
515}
516
517QList<QgsFeatureId> QgsSpatialIndex::nearestNeighbor( const QgsPointXY &point, const int neighbors, const double maxDistance ) const
518{
519 QList<QgsFeatureId> list;
520 QgisVisitor visitor( list );
521
522 double pt[2] = { point.x(), point.y() };
523 const Point p( pt, 2 );
524
525 const QMutexLocker locker( &d->mMutex );
526 QgsNearestNeighborComparator nnc( ( d->mFlags & QgsSpatialIndex::FlagStoreFeatureGeometries ) ? &d->mGeometries : nullptr,
527 point, maxDistance );
528 d->mRTree->nearestNeighborQuery( neighbors, p, visitor, nnc );
529
530 if ( maxDistance > 0 )
531 {
532 // trim features outside of max distance
533 list.erase( std::remove_if( list.begin(), list.end(),
534 [&nnc]( QgsFeatureId id )
535 {
536 return nnc.mFeaturesOutsideMaxDistance.contains( id );
537 } ), list.end() );
538 }
539
540 return list;
541}
542
543QList<QgsFeatureId> QgsSpatialIndex::nearestNeighbor( const QgsGeometry &geometry, int neighbors, double maxDistance ) const
544{
545 QList<QgsFeatureId> list;
546 QgisVisitor visitor( list );
547
548 const SpatialIndex::Region r = QgsSpatialIndexUtils::rectangleToRegion( geometry.boundingBox() );
549
550 const QMutexLocker locker( &d->mMutex );
551 QgsNearestNeighborComparator nnc( ( d->mFlags & QgsSpatialIndex::FlagStoreFeatureGeometries ) ? &d->mGeometries : nullptr,
552 geometry, maxDistance );
553 d->mRTree->nearestNeighborQuery( neighbors, r, visitor, nnc );
554
555 if ( maxDistance > 0 )
556 {
557 // trim features outside of max distance
558 list.erase( std::remove_if( list.begin(), list.end(),
559 [&nnc]( QgsFeatureId id )
560 {
561 return nnc.mFeaturesOutsideMaxDistance.contains( id );
562 } ), list.end() );
563 }
564
565 return list;
566}
567
569{
570 const QMutexLocker locker( &d->mMutex );
571 return d->mGeometries.value( id );
572}
573
574QAtomicInt QgsSpatialIndex::refs() const
575{
576 return d->ref;
577}
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:60
QgsFeatureId id
Definition qgsfeature.h:68
QgsGeometry geometry
Definition qgsfeature.h:71
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:62
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
A rectangle specified with double values.
bool isFinite() const
Returns true if the rectangle has finite boundaries.
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.
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:59