Skip to content
Permalink
Browse files

[FEATURE][API] Add method to QgsSpatialIndex to perform

nearest neighbor search based on QgsGeometry to QgsGeometries

Previously only point - geometry was possible. But with this
change, you can safely and accurately use QgsSpatialIndex
to determine the nearest neighbours between any types of
geometries.
  • Loading branch information
nyalldawson committed Mar 30, 2019
1 parent 2ef99da commit 8d6e3dc911e3e8391770f07bd8725b3792d68779
@@ -167,6 +167,28 @@ number of returned feature IDs may exceed ``neighbors``.
geometry features this method is not guaranteed to return the actual closest neighbors.
%End

QList<QgsFeatureId> nearestNeighbor( const QgsGeometry &geometry, int neighbors = 1, double maxDistance = 0 ) const;
%Docstring
Returns nearest neighbors to a ``geometry``. The number of neighbors returned is specified
by the ``neighbors`` argument.

If the ``maxDistance`` argument is greater than 0, then only features within the specified
distance of ``point`` will be considered.

Note that in some cases the number of returned features may differ from the requested
number of ``neighbors``. E.g. if not enough features exist within the ``maxDistance`` of the
search point. If multiple features are equidistant from the search ``point`` then the
number of returned feature IDs may exceed ``neighbors``.

.. warning::

If this QgsSpatialIndex object was not constructed with the FlagStoreFeatureGeometries flag,
then the nearest neighbor test is performed based on the feature bounding boxes ONLY, so for non-point
geometry features this method is not guaranteed to return the actual closest neighbors.

.. versionadded:: 3.8
%End


SIP_PYOBJECT geometry( QgsFeatureId id ) const /TypeHint="QgsGeometry"/;
%Docstring
@@ -94,13 +94,20 @@ class QgsNearestNeighborComparator : public INearestNeighborComparator

QgsNearestNeighborComparator( const QHash< QgsFeatureId, QgsGeometry > *geometries, const QgsPointXY &point, double maxDistance )
: mGeometries( geometries )
, mPoint( QgsGeometry::fromPointXY( point ) )
, mGeom( QgsGeometry::fromPointXY( point ) )
, mMaxDistance( maxDistance )
{
}

QgsNearestNeighborComparator( const QHash< QgsFeatureId, QgsGeometry > *geometries, const QgsGeometry &geometry, double maxDistance )
: mGeometries( geometries )
, mGeom( geometry )
, mMaxDistance( maxDistance )
{
}

const QHash< QgsFeatureId, QgsGeometry > *mGeometries = nullptr;
QgsGeometry mPoint;
QgsGeometry mGeom;
double mMaxDistance = 0;
QSet< QgsFeatureId > mFeaturesOutsideMaxDistance;

@@ -125,7 +132,7 @@ class QgsNearestNeighborComparator : public INearestNeighborComparator
if ( mGeometries && ( mMaxDistance <= 0.0 || dist <= mMaxDistance ) )
{
QgsGeometry other = mGeometries->value( data.getIdentifier() );
dist = other.distance( mPoint );
dist = other.distance( mGeom );
}

if ( mMaxDistance > 0 && dist > mMaxDistance )
@@ -499,6 +506,31 @@ QList<QgsFeatureId> QgsSpatialIndex::nearestNeighbor( const QgsPointXY &point, c
return list;
}

QList<QgsFeatureId> QgsSpatialIndex::nearestNeighbor( const QgsGeometry &geometry, int neighbors, double maxDistance ) const
{
QList<QgsFeatureId> list;
QgisVisitor visitor( list );

SpatialIndex::Region r = rectToRegion( geometry.boundingBox() );

QMutexLocker locker( &d->mMutex );
QgsNearestNeighborComparator nnc( d->mFlags & QgsSpatialIndex::FlagStoreFeatureGeometries ? &d->mGeometries : nullptr,
geometry, maxDistance );
d->mRTree->nearestNeighborQuery( neighbors, r, visitor, nnc );

if ( maxDistance > 0 )
{
// trim features outside of max distance
list.erase( std::remove_if( list.begin(), list.end(),
[&nnc]( QgsFeatureId id )
{
return nnc.mFeaturesOutsideMaxDistance.contains( id );
} ), list.end() );
}

return list;
}

QgsGeometry QgsSpatialIndex::geometry( QgsFeatureId id ) const
{
QMutexLocker locker( &d->mMutex );
@@ -192,6 +192,26 @@ class CORE_EXPORT QgsSpatialIndex : public QgsFeatureSink
*/
QList<QgsFeatureId> nearestNeighbor( const QgsPointXY &point, int neighbors = 1, double maxDistance = 0 ) const;

/**
* Returns nearest neighbors to a \a geometry. The number of neighbors returned is specified
* by the \a neighbors argument.
*
* If the \a maxDistance argument is greater than 0, then only features within the specified
* distance of \a point will be considered.
*
* Note that in some cases the number of returned features may differ from the requested
* number of \a neighbors. E.g. if not enough features exist within the \a maxDistance of the
* search point. If multiple features are equidistant from the search \a point then the
* number of returned feature IDs may exceed \a neighbors.
*
* \warning If this QgsSpatialIndex object was not constructed with the FlagStoreFeatureGeometries flag,
* then the nearest neighbor test is performed based on the feature bounding boxes ONLY, so for non-point
* geometry features this method is not guaranteed to return the actual closest neighbors.
*
* \since QGIS 3.8
*/
QList<QgsFeatureId> nearestNeighbor( const QgsGeometry &geometry, int neighbors = 1, double maxDistance = 0 ) const;

#ifndef SIP_RUN

/**
@@ -343,6 +343,42 @@ class TestQgsSpatialIndex : public QObject
QCOMPARE( i.nearestNeighbor( QgsPointXY( 1, 2.9 ), 2, 2 ), QList< QgsFeatureId >() << 1 << 3 );
QCOMPARE( i2.nearestNeighbor( QgsPointXY( 1, 2.9 ), 2, 2 ), QList< QgsFeatureId >() << 2 << 3 );

// using geometries as input, not points
QgsGeometry g = QgsGeometry::fromWkt( QStringLiteral( "LineString (1 0, 1 -1, -2 -1, -2 7, 5 4, 5 0)" ) );
QCOMPARE( i2.nearestNeighbor( g, 1 ), QList< QgsFeatureId >() << 3 );
QCOMPARE( i2.nearestNeighbor( g, 2 ), QList< QgsFeatureId >() << 3 << 1 );
QCOMPARE( i2.nearestNeighbor( g, 2, 1.1 ), QList< QgsFeatureId >() << 3 << 1 );
QCOMPARE( i2.nearestNeighbor( g, 2, 0.2 ), QList< QgsFeatureId >() );

g = QgsGeometry::fromWkt( QStringLiteral( "LineString (3 7, 3 6, 5 6, 4 2)" ) );
QCOMPARE( i.nearestNeighbor( g, 1 ), QList< QgsFeatureId >() << 1 << 3 ); // bounding box search only
QCOMPARE( i.nearestNeighbor( g, 2 ), QList< QgsFeatureId >() << 1 << 3 );
QCOMPARE( i.nearestNeighbor( g, 2, 1.1 ), QList< QgsFeatureId >() << 1 << 3 );
QCOMPARE( i.nearestNeighbor( g, 2, 0.2 ), QList< QgsFeatureId >() << 1 << 3 );
QCOMPARE( i2.nearestNeighbor( g, 1 ), QList< QgsFeatureId >() << 1 );
QCOMPARE( i2.nearestNeighbor( g, 2 ), QList< QgsFeatureId >() << 1 << 3 );
QCOMPARE( i2.nearestNeighbor( g, 2, 1.1 ), QList< QgsFeatureId >() << 1 );
QCOMPARE( i2.nearestNeighbor( g, 2, 0.2 ), QList< QgsFeatureId >() );

g = QgsGeometry::fromWkt( QStringLiteral( "Polygon ((2 3, -3 4, 1 7, 6 6, 6 1, 3 4, 2 3))" ) );
QCOMPARE( i.nearestNeighbor( g, 1 ), QList< QgsFeatureId >() << 1 << 2 << 3 ); // bounding box search only
QCOMPARE( i.nearestNeighbor( g, 2 ), QList< QgsFeatureId >() << 1 << 2 << 3 );
QCOMPARE( i.nearestNeighbor( g, 2, 1.1 ), QList< QgsFeatureId >() << 1 << 2 << 3 );
QCOMPARE( i.nearestNeighbor( g, 2, 0.2 ), QList< QgsFeatureId >() << 1 << 2 << 3 );
QCOMPARE( i2.nearestNeighbor( g, 1 ), QList< QgsFeatureId >() << 3 );
QCOMPARE( i2.nearestNeighbor( g, 2 ), QList< QgsFeatureId >() << 3 << 2 );
QCOMPARE( i2.nearestNeighbor( g, 2, 1.1 ), QList< QgsFeatureId >() << 3 << 2 );
QCOMPARE( i2.nearestNeighbor( g, 2, 0.2 ), QList< QgsFeatureId >() << 3 );

g = QgsGeometry::fromWkt( QStringLiteral( "MultiPoint (1.5 2.5, 3 4.5)" ) );
QCOMPARE( i.nearestNeighbor( g, 1 ), QList< QgsFeatureId >() << 1 << 3 ); // bounding box search only
QCOMPARE( i.nearestNeighbor( g, 2 ), QList< QgsFeatureId >() << 1 << 3 );
QCOMPARE( i.nearestNeighbor( g, 2, 1.1 ), QList< QgsFeatureId >() << 1 << 3 );
QCOMPARE( i.nearestNeighbor( g, 2, 0.2 ), QList< QgsFeatureId >() << 1 << 3 );
QCOMPARE( i2.nearestNeighbor( g, 1 ), QList< QgsFeatureId >() << 3 );
QCOMPARE( i2.nearestNeighbor( g, 2 ), QList< QgsFeatureId >() << 3 << 2 << 1 );
QCOMPARE( i2.nearestNeighbor( g, 2, 1.1 ), QList< QgsFeatureId >() << 3 );
QCOMPARE( i2.nearestNeighbor( g, 2, 0.2 ), QList< QgsFeatureId >() );
}

};

0 comments on commit 8d6e3dc

Please sign in to comment.
You can’t perform that action at this time.