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