Skip to content

Commit 0ea6a3d

Browse files
committedJan 20, 2015
Support different indexing strategies in QgsSnappingUtils
Huge layers will not use all the memory - at the expense of slow queries
1 parent 54e8493 commit 0ea6a3d

File tree

3 files changed

+97
-24
lines changed

3 files changed

+97
-24
lines changed
 

‎python/core/qgssnappingutils.sip

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ class QgsSnappingUtils : QObject
1717
/** snap to map according to the current configuration (mode). Optional filter allows to discard unwanted matches. */
1818
QgsPointLocator::Match snapToMap( const QPoint& point, QgsPointLocator::MatchFilter* filter = 0 );
1919
QgsPointLocator::Match snapToMap( const QgsPoint& pointMap, QgsPointLocator::MatchFilter* filter = 0 );
20-
// TODO: multi-variant
2120

2221
/** snap to current layer */
2322
QgsPointLocator::Match snapToCurrentLayer( const QPoint& point, int type, QgsPointLocator::MatchFilter* filter = 0 );
@@ -47,6 +46,18 @@ class QgsSnappingUtils : QObject
4746
/** Find out how the snapping to map is done */
4847
SnapToMapMode snapToMapMode() const;
4948

49+
enum IndexingStrategy
50+
{
51+
IndexAlwaysFull, //!< For all layers build index of full extent. Uses more memory, but queries are faster.
52+
IndexNeverFull, //!< For all layers only create temporary indexes of small extent. Low memory usage, slower queries.
53+
IndexHybrid //!< For "big" layers using IndexNeverFull, for the rest IndexAlwaysFull. Compromise between speed and memory usage.
54+
};
55+
56+
/** Set a strategy for indexing geometry data - determines how fast and memory consuming the data structures will be */
57+
void setIndexingStrategy( IndexingStrategy strategy );
58+
/** Find out which strategy is used for indexing - by default hybrid indexing is used */
59+
IndexingStrategy indexingStrategy() const;
60+
5061
/** configure options used when the mode is snap to current layer */
5162
void setDefaultSettings( int type, double tolerance, QgsTolerance::UnitType unit );
5263
/** query options used when the mode is snap to current layer */

‎src/core/qgssnappingutils.cpp

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ QgsSnappingUtils::QgsSnappingUtils( QObject* parent )
2525
: QObject( parent )
2626
, mCurrentLayer( 0 )
2727
, mSnapToMapMode( SnapCurrentLayer )
28+
, mStrategy( IndexHybrid )
2829
, mDefaultType( QgsPointLocator::Vertex )
2930
, mDefaultTolerance( 10 )
3031
, mDefaultUnit( QgsTolerance::Pixels )
@@ -57,6 +58,38 @@ void QgsSnappingUtils::clearAllLocators()
5758
foreach ( QgsPointLocator* vlpl, mLocators )
5859
delete vlpl;
5960
mLocators.clear();
61+
62+
foreach ( QgsPointLocator* vlpl, mTemporaryLocators )
63+
delete vlpl;
64+
mTemporaryLocators.clear();
65+
}
66+
67+
68+
QgsPointLocator* QgsSnappingUtils::locatorForLayerUsingStrategy( QgsVectorLayer* vl, const QgsPoint& pointMap, double tolerance )
69+
{
70+
if ( mStrategy == IndexAlwaysFull )
71+
return locatorForLayer( vl );
72+
else if ( mStrategy == IndexNeverFull )
73+
return temporaryLocatorForLayer( vl, pointMap, tolerance );
74+
else // Hybrid
75+
{
76+
if ( vl->pendingFeatureCount() > 100000 )
77+
return temporaryLocatorForLayer( vl, pointMap, tolerance );
78+
else
79+
return locatorForLayer( vl );
80+
}
81+
}
82+
83+
QgsPointLocator* QgsSnappingUtils::temporaryLocatorForLayer( QgsVectorLayer* vl, const QgsPoint& pointMap, double tolerance )
84+
{
85+
if ( mTemporaryLocators.contains( vl ) )
86+
delete mTemporaryLocators.take( vl );
87+
88+
QgsRectangle rect( pointMap.x() - tolerance, pointMap.y() - tolerance,
89+
pointMap.x() + tolerance, pointMap.y() + tolerance );
90+
QgsPointLocator* vlpl = new QgsPointLocator( vl, destCRS(), &rect );
91+
mTemporaryLocators.insert( vl, vlpl );
92+
return mTemporaryLocators.value( vl );
6093
}
6194

6295

@@ -174,17 +207,18 @@ QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPoint& pointMap, Qg
174207
int type = mDefaultType;
175208

176209
// use ad-hoc locator
177-
QgsPointLocator* loc = locatorForLayer( mCurrentLayer );
178-
loc->init( QgsPointLocator::Vertex | QgsPointLocator::Edge );
210+
QgsPointLocator* loc = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
179211
if ( !loc )
180212
return QgsPointLocator::Match();
213+
loc->init( QgsPointLocator::Vertex | QgsPointLocator::Edge );
181214

182215
QgsPointLocator::Match bestMatch;
183216
_updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter );
184217

185218
if ( mSnapOnIntersection )
186219
{
187-
QgsPointLocator::MatchList edges = locatorForLayer( mCurrentLayer )->edgesInTolerance( pointMap, tolerance );
220+
QgsPointLocator* locEdges = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
221+
QgsPointLocator::MatchList edges = locEdges->edgesInTolerance( pointMap, tolerance );
188222
bestMatch.replaceIfBetter( _findClosestSegmentIntersection( pointMap, edges ), tolerance );
189223
}
190224

@@ -199,7 +233,7 @@ QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPoint& pointMap, Qg
199233
foreach ( const LayerConfig& layerConfig, mLayers )
200234
{
201235
double tolerance = QgsTolerance::toleranceInMapUnits( layerConfig.tolerance, mMapSettings, layerConfig.unit );
202-
if ( QgsPointLocator* loc = locatorForLayer( layerConfig.layer ) )
236+
if ( QgsPointLocator* loc = locatorForLayerUsingStrategy( layerConfig.layer, pointMap, tolerance ) )
203237
{
204238
loc->init( layerConfig.type );
205239

@@ -228,14 +262,14 @@ QgsPointLocator::Match QgsSnappingUtils::snapToCurrentLayer( const QPoint& point
228262
if ( !mCurrentLayer )
229263
return QgsPointLocator::Match();
230264

231-
QgsPointLocator* loc = locatorForLayer( mCurrentLayer );
232-
loc->init( type );
233-
if ( !loc )
234-
return QgsPointLocator::Match();
235-
236265
QgsPoint pointMap = mMapSettings.mapToPixel().toMapCoordinates( point );
237266
double tolerance = QgsTolerance::vertexSearchRadius( mMapSettings );
238267

268+
QgsPointLocator* loc = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
269+
if ( !loc )
270+
return QgsPointLocator::Match();
271+
loc->init( type );
272+
239273
QgsPointLocator::Match bestMatch;
240274
_updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter );
241275
return bestMatch;
@@ -356,6 +390,15 @@ void QgsSnappingUtils::onLayersWillBeRemoved( QStringList layerIds )
356390
continue;
357391
}
358392
}
393+
394+
for ( LocatorsMap::const_iterator it = mTemporaryLocators.constBegin(); it != mTemporaryLocators.constEnd(); ++it )
395+
{
396+
if ( it.key()->id() == layerId )
397+
{
398+
delete mTemporaryLocators.take( it.key() );
399+
continue;
400+
}
401+
}
359402
}
360403
}
361404

‎src/core/qgssnappingutils.h

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,19 @@
2222
#include "qgspointlocator.h"
2323

2424
/**
25-
* Has all the configuration of snapping and can return answers to snapping queries.
26-
* This one will be also available from iface for map tools.
25+
* This class has all the configuration of snapping and can return answers to snapping queries.
26+
* Internally, it keeps a cache of QgsPointLocator instances for multiple layers.
27+
*
28+
* Currently it supports the following queries:
29+
* - snapToMap() - has multiple modes of operation
30+
* - snapToCurrentLayer()
31+
* For more complex queries it is possible to use locatorForLayer() method that returns
32+
* point locator instance with layer's indexed data.
33+
*
34+
* Indexing strategy determines how fast the queries will be and how much memory will be used.
35+
*
36+
* When working with map canvas, it may be useful to use derived class QgsMapCanvasSnappingUtils
37+
* which keeps the configuration in sync with map canvas (e.g. current view, active layer).
2738
*
2839
* @note added in 2.8
2940
*/
@@ -42,7 +53,6 @@ class QgsSnappingUtils : public QObject
4253
/** snap to map according to the current configuration (mode). Optional filter allows to discard unwanted matches. */
4354
QgsPointLocator::Match snapToMap( const QPoint& point, QgsPointLocator::MatchFilter* filter = 0 );
4455
QgsPointLocator::Match snapToMap( const QgsPoint& pointMap, QgsPointLocator::MatchFilter* filter = 0 );
45-
// TODO: multi-variant
4656

4757
/** snap to current layer */
4858
QgsPointLocator::Match snapToCurrentLayer( const QPoint& point, int type, QgsPointLocator::MatchFilter* filter = 0 );
@@ -72,6 +82,18 @@ class QgsSnappingUtils : public QObject
7282
/** Find out how the snapping to map is done */
7383
SnapToMapMode snapToMapMode() const { return mSnapToMapMode; }
7484

85+
enum IndexingStrategy
86+
{
87+
IndexAlwaysFull, //!< For all layers build index of full extent. Uses more memory, but queries are faster.
88+
IndexNeverFull, //!< For all layers only create temporary indexes of small extent. Low memory usage, slower queries.
89+
IndexHybrid //!< For "big" layers using IndexNeverFull, for the rest IndexAlwaysFull. Compromise between speed and memory usage.
90+
};
91+
92+
/** Set a strategy for indexing geometry data - determines how fast and memory consuming the data structures will be */
93+
void setIndexingStrategy( IndexingStrategy strategy ) { mStrategy = strategy; }
94+
/** Find out which strategy is used for indexing - by default hybrid indexing is used */
95+
IndexingStrategy indexingStrategy() const { return mStrategy; }
96+
7597
/** configure options used when the mode is snap to current layer */
7698
void setDefaultSettings( int type, double tolerance, QgsTolerance::UnitType unit );
7799
/** query options used when the mode is snap to current layer */
@@ -97,21 +119,10 @@ class QgsSnappingUtils : public QObject
97119
/** Query whether to consider intersections of nearby segments for snapping */
98120
bool snapOnIntersections() const { return mSnapOnIntersection; }
99121

100-
#if 0
101-
/** Set topological editing status (used by some map tools) */
102-
void setTopologicalEditing( bool enabled );
103-
/** Query topological editing status (used by some map tools) */
104-
bool topologicalEditing() const;
105-
#endif
106-
107122
public slots:
108123
/** Read snapping configuration from the project */
109124
void readConfigFromProject();
110125

111-
// requirements:
112-
// - support existing configurations
113-
// - handle updates from QgsProject::setSnapSettingsForLayer()
114-
115126
private slots:
116127
void onLayersWillBeRemoved( QStringList layerIds );
117128

@@ -122,13 +133,19 @@ class QgsSnappingUtils : public QObject
122133
//! delete all existing locators (e.g. when destination CRS has changed and we need to reindex)
123134
void clearAllLocators();
124135

136+
//! return a locator (temporary or not) according to the indexing strategy
137+
QgsPointLocator* locatorForLayerUsingStrategy( QgsVectorLayer* vl, const QgsPoint& pointMap, double tolerance );
138+
//! return a temporary locator with index only for a small area (will be replaced by another one on next request)
139+
QgsPointLocator* temporaryLocatorForLayer( QgsVectorLayer* vl, const QgsPoint& pointMap, double tolerance );
140+
125141
private:
126142
// environment
127143
QgsMapSettings mMapSettings;
128144
QgsVectorLayer* mCurrentLayer;
129145

130146
// configuration
131147
SnapToMapMode mSnapToMapMode;
148+
IndexingStrategy mStrategy;
132149
int mDefaultType;
133150
double mDefaultTolerance;
134151
QgsTolerance::UnitType mDefaultUnit;
@@ -139,6 +156,8 @@ class QgsSnappingUtils : public QObject
139156
typedef QMap<QgsVectorLayer*, QgsPointLocator*> LocatorsMap;
140157
//! on-demand locators used (locators are owned)
141158
LocatorsMap mLocators;
159+
//! temporary locators (indexing just a part of layers). owned by the instance
160+
LocatorsMap mTemporaryLocators;
142161
};
143162

144163

0 commit comments

Comments
 (0)
Please sign in to comment.