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