Skip to content

Commit cad3173

Browse files
committedJan 23, 2015
QgsSnappingUtils: improvements of the hybrid indexing strategy
Instead of calculating feature count (which may add extra overhead), let's directly try to build index for layers. Only when a certain limit of features is reached (currently 1 million), the indexing is cancelled and layer is marked as non-indexable.
1 parent 6d7e396 commit cad3173

File tree

5 files changed

+101
-25
lines changed

5 files changed

+101
-25
lines changed
 

‎python/core/qgspointlocator.sip

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,14 @@ class QgsPointLocator : QObject
1616

1717
enum Type { Invalid, Vertex, Edge, Area, All };
1818

19-
/** Prepare the index for queries. Does nothing if the index already exists */
20-
void init();
19+
/** Prepare the index for queries. Does nothing if the index already exists.
20+
* If the number of features is greater than the value of maxFeaturesToIndex, creation of index is stopped
21+
* to make sure we do not run out of memory. If maxFeaturesToIndex is -1, no limits are used. Returns
22+
* false if the creation of index has been prematurely stopped due to the limit of features, otherwise true */
23+
bool init( int maxFeaturesToIndex = -1 );
24+
25+
/** Indicate whether the data have been already indexed */
26+
bool hasIndex() const;
2127

2228
struct Match
2329
{

‎src/core/qgspointlocator.cpp

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -595,23 +595,27 @@ QgsPointLocator::~QgsPointLocator()
595595
}
596596

597597

598-
void QgsPointLocator::init()
598+
bool QgsPointLocator::init( int maxFeaturesToIndex )
599599
{
600-
if ( !mRTree )
601-
rebuildIndex();
600+
return hasIndex() ? true : rebuildIndex( maxFeaturesToIndex );
601+
}
602+
603+
bool QgsPointLocator::hasIndex() const
604+
{
605+
return mRTree != 0;
602606
}
603607

604608

605609

606-
void QgsPointLocator::rebuildIndex()
610+
bool QgsPointLocator::rebuildIndex( int maxFeaturesToIndex )
607611
{
608612
destroyIndex();
609613

610614
QLinkedList<RTree::Data*> dataList;
611615
QgsFeature f;
612616
QGis::GeometryType geomType = mLayer->geometryType();
613617
if ( geomType == QGis::NoGeometry )
614-
return; // nothing to index
618+
return true; // nothing to index
615619

616620
QgsFeatureRequest request;
617621
request.setSubsetOfAttributes( QgsAttributeList() );
@@ -623,6 +627,7 @@ void QgsPointLocator::rebuildIndex()
623627
request.setFilterRect( rect );
624628
}
625629
QgsFeatureIterator fi = mLayer->getFeatures( request );
630+
int indexedCount = 0;
626631
while ( fi.nextFeature( f ) )
627632
{
628633
if ( !f.geometry() )
@@ -634,6 +639,14 @@ void QgsPointLocator::rebuildIndex()
634639
SpatialIndex::Region r( rect2region( f.geometry()->boundingBox() ) );
635640
dataList << new RTree::Data( 0, 0, r, f.id() );
636641
mGeoms[f.id()] = new QgsGeometry( *f.geometry() );
642+
++indexedCount;
643+
644+
if ( maxFeaturesToIndex != -1 && indexedCount > maxFeaturesToIndex )
645+
{
646+
qDeleteAll( dataList );
647+
destroyIndex();
648+
return false;
649+
}
637650
}
638651

639652
// R-Tree parameters
@@ -645,11 +658,12 @@ void QgsPointLocator::rebuildIndex()
645658
SpatialIndex::id_type indexId;
646659

647660
if ( dataList.isEmpty() )
648-
return; // no features
661+
return true; // no features
649662

650663
QgsPointLocator_Stream stream( dataList );
651664
mRTree = RTree::createAndBulkLoadNewRTree( RTree::BLM_STR, stream, *mStorage, fillFactor, indexCapacity,
652665
leafCapacity, dimension, variant, indexId );
666+
return true;
653667
}
654668

655669

‎src/core/qgspointlocator.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,14 @@ class CORE_EXPORT QgsPointLocator : public QObject
5959

6060
enum Type { Invalid = 0, Vertex = 1, Edge = 2, Area = 4, All = Vertex | Edge | Area };
6161

62-
/** Prepare the index for queries. Does nothing if the index already exists */
63-
void init();
62+
/** Prepare the index for queries. Does nothing if the index already exists.
63+
* If the number of features is greater than the value of maxFeaturesToIndex, creation of index is stopped
64+
* to make sure we do not run out of memory. If maxFeaturesToIndex is -1, no limits are used. Returns
65+
* false if the creation of index has been prematurely stopped due to the limit of features, otherwise true */
66+
bool init( int maxFeaturesToIndex = -1 );
67+
68+
/** Indicate whether the data have been already indexed */
69+
bool hasIndex() const;
6470

6571
struct Match
6672
{
@@ -149,7 +155,7 @@ class CORE_EXPORT QgsPointLocator : public QObject
149155

150156

151157
protected:
152-
void rebuildIndex();
158+
bool rebuildIndex( int maxFeaturesToIndex = -1 );
153159
void destroyIndex();
154160

155161
private slots:

‎src/core/qgssnappingutils.cpp

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,10 @@ void QgsSnappingUtils::clearAllLocators()
6767

6868
QgsPointLocator* QgsSnappingUtils::locatorForLayerUsingStrategy( QgsVectorLayer* vl, const QgsPoint& pointMap, double tolerance )
6969
{
70-
if ( mStrategy == IndexAlwaysFull )
70+
if ( willUseIndex( vl ) )
7171
return locatorForLayer( vl );
72-
else if ( mStrategy == IndexNeverFull )
72+
else
7373
return temporaryLocatorForLayer( vl, pointMap, tolerance );
74-
else // Hybrid
75-
{
76-
if ( vl->pendingFeatureCount() > 1000000 )
77-
return temporaryLocatorForLayer( vl, pointMap, tolerance );
78-
else
79-
return locatorForLayer( vl );
80-
}
8174
}
8275

8376
QgsPointLocator* QgsSnappingUtils::temporaryLocatorForLayer( QgsVectorLayer* vl, const QgsPoint& pointMap, double tolerance )
@@ -92,6 +85,22 @@ QgsPointLocator* QgsSnappingUtils::temporaryLocatorForLayer( QgsVectorLayer* vl,
9285
return mTemporaryLocators.value( vl );
9386
}
9487

88+
bool QgsSnappingUtils::willUseIndex( QgsVectorLayer* vl ) const
89+
{
90+
if ( mStrategy == IndexAlwaysFull )
91+
return true;
92+
else if ( mStrategy == IndexNeverFull )
93+
return false;
94+
else
95+
{
96+
if ( mHybridNonindexableLayers.contains( vl->id() ) )
97+
return false;
98+
99+
// if the layer is too big, the locator will later stop indexing it after reaching a threshold
100+
return true;
101+
}
102+
}
103+
95104

96105
static QgsPointLocator::Match _findClosestSegmentIntersection( const QgsPoint& pt, const QgsPointLocator::MatchList& segments )
97106
{
@@ -204,6 +213,8 @@ QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPoint& pointMap, Qg
204213
if ( !mCurrentLayer )
205214
return QgsPointLocator::Match();
206215

216+
prepareIndex( QList<QgsVectorLayer*>() << mCurrentLayer );
217+
207218
// data from project
208219
double tolerance = QgsTolerance::toleranceInMapUnits( mDefaultTolerance, mMapSettings, mDefaultUnit );
209220
int type = mDefaultType;
@@ -227,6 +238,11 @@ QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPoint& pointMap, Qg
227238
}
228239
else if ( mSnapToMapMode == SnapAdvanced )
229240
{
241+
QList<QgsVectorLayer*> layers;
242+
foreach ( const LayerConfig& layerConfig, mLayers )
243+
layers << layerConfig.layer;
244+
prepareIndex( layers );
245+
230246
QgsPointLocator::Match bestMatch;
231247
QgsPointLocator::MatchList edges; // for snap on intersection
232248
double maxSnapIntTolerance = 0;
@@ -257,15 +273,17 @@ QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPoint& pointMap, Qg
257273
double tolerance = QgsTolerance::toleranceInMapUnits( mDefaultTolerance, mMapSettings, mDefaultUnit );
258274
int type = mDefaultType;
259275

276+
QList<QgsVectorLayer*> layers;
277+
foreach( const QString& layerID, mMapSettings.layers() )
278+
if ( QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( layerID ) ) )
279+
layers << vl;
280+
prepareIndex( layers );
281+
260282
QgsPointLocator::MatchList edges; // for snap on intersection
261283
QgsPointLocator::Match bestMatch;
262284

263-
foreach( const QString& layerID, mMapSettings.layers() )
285+
foreach( QgsVectorLayer* vl, layers )
264286
{
265-
QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( layerID ) );
266-
if ( !vl )
267-
continue;
268-
269287
if ( QgsPointLocator* loc = locatorForLayerUsingStrategy( vl, pointMap, tolerance ) )
270288
{
271289
_updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter );
@@ -285,6 +303,31 @@ QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPoint& pointMap, Qg
285303
}
286304

287305

306+
void QgsSnappingUtils::prepareIndex( const QList<QgsVectorLayer*>& layers )
307+
{
308+
// check if we need to build any index
309+
QList<QgsVectorLayer*> layersToIndex;
310+
foreach( QgsVectorLayer* vl, layers )
311+
{
312+
if ( willUseIndex( vl ) && !locatorForLayer( vl )->hasIndex() )
313+
layersToIndex << vl;
314+
}
315+
if ( layersToIndex.isEmpty() )
316+
return;
317+
318+
// build indexes
319+
QTime t; t.start();
320+
foreach ( QgsVectorLayer* vl, layersToIndex )
321+
{
322+
QTime tt; tt.start();
323+
if ( !locatorForLayer( vl )->init( mStrategy == IndexHybrid ? 1000000 : -1 ) )
324+
mHybridNonindexableLayers.insert( vl->id() );
325+
QgsDebugMsg( QString( "Index init: %1 ms (%2)" ).arg( tt.elapsed() ).arg( vl->id() ) );
326+
}
327+
QgsDebugMsg( QString( "Prepare index total: %1 ms" ).arg( t.elapsed() ) );
328+
}
329+
330+
288331
QgsPointLocator::Match QgsSnappingUtils::snapToCurrentLayer( const QPoint& point, int type, QgsPointLocator::MatchFilter* filter )
289332
{
290333
if ( !mCurrentLayer )

‎src/core/qgssnappingutils.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,11 @@ class CORE_EXPORT QgsSnappingUtils : public QObject
139139
//! return a temporary locator with index only for a small area (will be replaced by another one on next request)
140140
QgsPointLocator* temporaryLocatorForLayer( QgsVectorLayer* vl, const QgsPoint& pointMap, double tolerance );
141141

142+
//! find out whether the strategy would index such layer or just use a temporary locator
143+
bool willUseIndex( QgsVectorLayer* vl ) const;
144+
//! initialize index for layers where it makes sense (according to the indexing strategy)
145+
void prepareIndex( const QList<QgsVectorLayer*>& layers );
146+
142147
private:
143148
// environment
144149
QgsMapSettings mMapSettings;
@@ -159,6 +164,8 @@ class CORE_EXPORT QgsSnappingUtils : public QObject
159164
LocatorsMap mLocators;
160165
//! temporary locators (indexing just a part of layers). owned by the instance
161166
LocatorsMap mTemporaryLocators;
167+
//! list of layer IDs that are too large to be indexed (hybrid strategy will use temporary locators for those)
168+
QSet<QString> mHybridNonindexableLayers;
162169
};
163170

164171

0 commit comments

Comments
 (0)