[api] Add constructor for QgsSpatialIndex which allows for a callback

function during bulk index load from a feature iterator

Allows single-iteration of a source for dual purposes simultaneously,
e.g. doing other feature-based operations while still gaining the full
advantage of the bulk loaded spatial index without having to do multiple
feature iterations
This commit is contained in:
Nyall Dawson 2020-01-01 10:54:33 +10:00
parent a25f8b970f
commit fe6abba251
4 changed files with 84 additions and 3 deletions

View File

@ -63,6 +63,7 @@ that of the spatial index construction.
.. versionadded:: 2.8
%End
explicit QgsSpatialIndex( const QgsFeatureSource &source, QgsFeedback *feedback = 0, QgsSpatialIndex::Flags flags = 0 );
%Docstring
Constructor - creates R-tree and bulk loads it with features from the source.

View File

@ -163,10 +163,12 @@ class QgsFeatureIteratorDataStream : public IDataStream
{
public:
//! constructor - needs to load all data to a vector for later access when bulk loading
explicit QgsFeatureIteratorDataStream( const QgsFeatureIterator &fi, QgsFeedback *feedback = nullptr, QgsSpatialIndex::Flags flags = nullptr )
explicit QgsFeatureIteratorDataStream( const QgsFeatureIterator &fi, QgsFeedback *feedback = nullptr, QgsSpatialIndex::Flags flags = nullptr,
const std::function< bool( const QgsFeature & ) > *callback = nullptr )
: mFi( fi )
, mFeedback( feedback )
, mFlags( flags )
, mCallback( callback )
{
readNextEntry();
}
@ -207,6 +209,15 @@ class QgsFeatureIteratorDataStream : public IDataStream
QgsFeatureId id;
while ( mFi.nextFeature( f ) )
{
if ( mCallback )
{
bool res = ( *mCallback )( f );
if ( !res )
{
mNextData = nullptr;
return;
}
}
if ( QgsSpatialIndex::featureInfo( f, r, id ) )
{
mNextData = new RTree::Data( 0, nullptr, r, id );
@ -222,6 +233,7 @@ class QgsFeatureIteratorDataStream : public IDataStream
RTree::Data *mNextData = nullptr;
QgsFeedback *mFeedback = nullptr;
QgsSpatialIndex::Flags mFlags = nullptr;
const std::function< bool( const QgsFeature & ) > *mCallback = nullptr;
};
@ -253,10 +265,11 @@ class QgsSpatialIndexData : public QSharedData
* of \a feedback is not transferred, and callers must take care that the lifetime of feedback exceeds
* that of the spatial index construction.
*/
explicit QgsSpatialIndexData( const QgsFeatureIterator &fi, QgsFeedback *feedback = nullptr, QgsSpatialIndex::Flags flags = nullptr )
explicit QgsSpatialIndexData( const QgsFeatureIterator &fi, QgsFeedback *feedback = nullptr, QgsSpatialIndex::Flags flags = nullptr,
const std::function< bool( const QgsFeature & ) > *callback = nullptr )
: mFlags( flags )
{
QgsFeatureIteratorDataStream fids( fi, feedback, mFlags );
QgsFeatureIteratorDataStream fids( fi, feedback, mFlags, callback );
initTree( &fids );
if ( flags & QgsSpatialIndex::FlagStoreFeatureGeometries )
mGeometries = fids.geometries;
@ -335,6 +348,11 @@ QgsSpatialIndex::QgsSpatialIndex( const QgsFeatureIterator &fi, QgsFeedback *fee
d = new QgsSpatialIndexData( fi, feedback, flags );
}
QgsSpatialIndex::QgsSpatialIndex( const QgsFeatureIterator &fi, const std::function< bool( const QgsFeature & )> &callback, QgsSpatialIndex::Flags flags )
{
d = new QgsSpatialIndexData( fi, nullptr, flags, &callback );
}
QgsSpatialIndex::QgsSpatialIndex( const QgsFeatureSource &source, QgsFeedback *feedback, QgsSpatialIndex::Flags flags )
{
d = new QgsSpatialIndexData( source.getFeatures( QgsFeatureRequest().setNoAttributes() ), feedback, flags );

View File

@ -95,6 +95,23 @@ class CORE_EXPORT QgsSpatialIndex : public QgsFeatureSink
*/
explicit QgsSpatialIndex( const QgsFeatureIterator &fi, QgsFeedback *feedback = nullptr, QgsSpatialIndex::Flags flags = nullptr );
#ifndef SIP_RUN
/**
* Constructor - creates R-tree and bulk loads it with features from the iterator.
* This is much faster approach than creating an empty index and then inserting features one by one.
*
* This construct and bulk load variant allows for a \a callback function to be specified, which is
* called for each added feature in turn. It allows for bulk spatial index load along with other feature
* based operations on a single iteration through a feature source. If \a callback returns FALSE, the
* load and iteration is canceled.
*
* \note Not available in Python bindings
* \since QGIS 2.12
*/
explicit QgsSpatialIndex( const QgsFeatureIterator &fi, const std::function< bool( const QgsFeature & ) > &callback, QgsSpatialIndex::Flags flags = nullptr );
#endif
/**
* Constructor - creates R-tree and bulk loads it with features from the source.
* This is much faster approach than creating an empty index and then inserting features one by one.

View File

@ -234,6 +234,51 @@ class TestQgsSpatialIndex : public QObject
delete indexInsert;
}
void bulkLoadWithCallback()
{
std::unique_ptr< QgsVectorLayer > vl = qgis::make_unique< QgsVectorLayer >( QStringLiteral( "Point" ), QStringLiteral( "x" ), QStringLiteral( "memory" ) );
QList< QgsFeatureId > addedIds;
for ( int i = 0; i < 10; ++i )
{
QgsFeature f( i );
QgsGeometry g = QgsGeometry::fromPointXY( QgsPointXY( i, 1 ) );
f.setGeometry( g );
vl->dataProvider()->addFeature( f );
addedIds.append( f.id() );
}
QCOMPARE( vl->featureCount(), 10L );
QgsFeatureIds ids;
QgsSpatialIndex i( vl->getFeatures(), [ & ]( const QgsFeature & f )->bool
{
ids.insert( f.id() );
return true;
} );
QCOMPARE( ids.size(), 10 );
for ( int i = 0; i < 10; ++i )
QVERIFY( ids.contains( addedIds.at( i ) ) );
QList<QgsFeatureId> res = i.intersects( QgsRectangle( 1.5, 0, 3.5, 10 ) );
QCOMPARE( res.size(), 2 );
QVERIFY( res.contains( addedIds.at( 2 ) ) );
QVERIFY( res.contains( addedIds.at( 3 ) ) );
// try canceling
ids.clear();
QgsSpatialIndex i2( vl->getFeatures(), [ & ]( const QgsFeature & f )->bool
{
ids.insert( f.id() );
return false;
} );
QCOMPARE( ids.size(), 1 );
QVERIFY( ids.contains( addedIds.at( 0 ) ) );
res = i2.intersects( QgsRectangle( 1.5, 0, 3.5, 10 ) );
QVERIFY( res.isEmpty() );
}
void testRetrieveGeometries()
{
QgsVectorLayer *vl = new QgsVectorLayer( QStringLiteral( "LineString" ), QStringLiteral( "x" ), QStringLiteral( "memory" ) );