Skip to content

Commit 362ba02

Browse files
committed
When a QgsSpatialIndex is storing feature geometry, then
nearestNeighbor search performs an EXACT nearest neighbour search, instead of just a nearest-neighbour-by-bounding-box search
1 parent 2655535 commit 362ba02

File tree

4 files changed

+61
-4
lines changed

4 files changed

+61
-4
lines changed

python/core/auto_generated/qgsspatialindex.sip.in

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,10 @@ Returns a list of features with a bounding box which intersects the specified ``
152152
Returns nearest neighbors to a ``point``. The number of neighbours returned is specified
153153
by the ``neighbours`` argument.
154154

155-
.. note::
155+
.. warning::
156156

157-
The nearest neighbour test is performed based on the feature bounding boxes only, so for non-point
157+
If this QgsSpatialIndex object was not constructed with the FlagStoreFeatureGeometries flag,
158+
then the nearest neighbour test is performed based on the feature bounding boxes ONLY, so for non-point
158159
geometry features this method is not guaranteed to return the actual closest neighbours.
159160
%End
160161

src/core/qgsspatialindex.cpp

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,31 @@ class QgsSpatialIndexCopyVisitor : public SpatialIndex::IVisitor
8787
SpatialIndex::ISpatialIndex *mNewIndex = nullptr;
8888
};
8989

90+
class QgsNearestNeighbourComparator : public INearestNeighborComparator
91+
{
92+
public:
93+
94+
QgsNearestNeighbourComparator( const QHash< QgsFeatureId, QgsGeometry > &geometries, const QgsPointXY &point )
95+
: mGeometries( geometries )
96+
, mPoint( QgsGeometry::fromPointXY( point ) )
97+
{
98+
}
99+
100+
QHash< QgsFeatureId, QgsGeometry > mGeometries;
101+
QgsGeometry mPoint;
102+
103+
double getMinimumDistance( const IShape &, const IShape & ) override
104+
{
105+
Q_ASSERT( false ); // not implemented!
106+
return 0;
107+
}
108+
109+
double getMinimumDistance( const IShape &, const IData &data ) override
110+
{
111+
QgsGeometry other = mGeometries.value( data.getIdentifier() );
112+
return other.distance( mPoint );
113+
}
114+
};
90115

91116
/**
92117
* \ingroup core
@@ -423,7 +448,16 @@ QList<QgsFeatureId> QgsSpatialIndex::nearestNeighbor( const QgsPointXY &point, i
423448
Point p( pt, 2 );
424449

425450
QMutexLocker locker( &d->mMutex );
426-
d->mRTree->nearestNeighborQuery( neighbors, p, visitor );
451+
if ( d->mFlags & QgsSpatialIndex::FlagStoreFeatureGeometries )
452+
{
453+
QgsNearestNeighbourComparator nnc( d->mGeometries, point );
454+
d->mRTree->nearestNeighborQuery( neighbors, p, visitor, nnc );
455+
}
456+
else
457+
{
458+
// simple bounding box search - meaningless for non-points
459+
d->mRTree->nearestNeighborQuery( neighbors, p, visitor );
460+
}
427461

428462
return list;
429463
}

src/core/qgsspatialindex.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,8 @@ class CORE_EXPORT QgsSpatialIndex : public QgsFeatureSink
178178
* Returns nearest neighbors to a \a point. The number of neighbours returned is specified
179179
* by the \a neighbours argument.
180180
*
181-
* \note The nearest neighbour test is performed based on the feature bounding boxes only, so for non-point
181+
* \warning If this QgsSpatialIndex object was not constructed with the FlagStoreFeatureGeometries flag,
182+
* then the nearest neighbour test is performed based on the feature bounding boxes ONLY, so for non-point
182183
* geometry features this method is not guaranteed to return the actual closest neighbours.
183184
*/
184185
QList<QgsFeatureId> nearestNeighbor( const QgsPointXY &point, int neighbors ) const;

tests/src/core/testqgsspatialindex.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,27 @@ class TestQgsSpatialIndex : public QObject
306306
QVERIFY( i6.geometry( 50 ).isNull() );
307307
}
308308

309+
void testNearestNeighbour()
310+
{
311+
QgsSpatialIndex i;
312+
QgsSpatialIndex i2( QgsSpatialIndex::FlagStoreFeatureGeometries );
313+
314+
QgsFeature f1( 1 );
315+
f1.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString(1 1, 3 1, 3 3)" ) ) );
316+
QgsFeature f2( 2 );
317+
f2.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString(0 1, 0 3)" ) ) );
318+
i.addFeature( f1 );
319+
i2.addFeature( f1 );
320+
i.addFeature( f2 );
321+
i2.addFeature( f2 );
322+
323+
// i does not store feature geometries, so nearest neighbour search uses bounding box only
324+
QCOMPARE( i.nearestNeighbor( QgsPointXY( 1, 3 ), 1 ), QList< QgsFeatureId >() << 1 );
325+
// i2 does store feature geometries, so nearest neighbour is exact
326+
QCOMPARE( i2.nearestNeighbor( QgsPointXY( 1, 3 ), 1 ), QList< QgsFeatureId >() << 2 );
327+
328+
}
329+
309330
};
310331

311332
QGSTEST_MAIN( TestQgsSpatialIndex )

0 commit comments

Comments
 (0)