Skip to content

Commit

Permalink
Support different indexing strategies in QgsSnappingUtils
Browse files Browse the repository at this point in the history
Huge layers will not use all the memory - at the expense of slow queries
  • Loading branch information
wonder-sk committed Jan 20, 2015
1 parent 54e8493 commit 0ea6a3d
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 24 deletions.
13 changes: 12 additions & 1 deletion python/core/qgssnappingutils.sip
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ class QgsSnappingUtils : QObject
/** snap to map according to the current configuration (mode). Optional filter allows to discard unwanted matches. */
QgsPointLocator::Match snapToMap( const QPoint& point, QgsPointLocator::MatchFilter* filter = 0 );
QgsPointLocator::Match snapToMap( const QgsPoint& pointMap, QgsPointLocator::MatchFilter* filter = 0 );
// TODO: multi-variant

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

enum IndexingStrategy
{
IndexAlwaysFull, //!< For all layers build index of full extent. Uses more memory, but queries are faster.
IndexNeverFull, //!< For all layers only create temporary indexes of small extent. Low memory usage, slower queries.
IndexHybrid //!< For "big" layers using IndexNeverFull, for the rest IndexAlwaysFull. Compromise between speed and memory usage.
};

/** Set a strategy for indexing geometry data - determines how fast and memory consuming the data structures will be */
void setIndexingStrategy( IndexingStrategy strategy );
/** Find out which strategy is used for indexing - by default hybrid indexing is used */
IndexingStrategy indexingStrategy() const;

/** configure options used when the mode is snap to current layer */
void setDefaultSettings( int type, double tolerance, QgsTolerance::UnitType unit );
/** query options used when the mode is snap to current layer */
Expand Down
61 changes: 52 additions & 9 deletions src/core/qgssnappingutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ QgsSnappingUtils::QgsSnappingUtils( QObject* parent )
: QObject( parent )
, mCurrentLayer( 0 )
, mSnapToMapMode( SnapCurrentLayer )
, mStrategy( IndexHybrid )
, mDefaultType( QgsPointLocator::Vertex )
, mDefaultTolerance( 10 )
, mDefaultUnit( QgsTolerance::Pixels )
Expand Down Expand Up @@ -57,6 +58,38 @@ void QgsSnappingUtils::clearAllLocators()
foreach ( QgsPointLocator* vlpl, mLocators )
delete vlpl;
mLocators.clear();

foreach ( QgsPointLocator* vlpl, mTemporaryLocators )
delete vlpl;
mTemporaryLocators.clear();
}


QgsPointLocator* QgsSnappingUtils::locatorForLayerUsingStrategy( QgsVectorLayer* vl, const QgsPoint& pointMap, double tolerance )
{
if ( mStrategy == IndexAlwaysFull )
return locatorForLayer( vl );
else if ( mStrategy == IndexNeverFull )
return temporaryLocatorForLayer( vl, pointMap, tolerance );
else // Hybrid
{
if ( vl->pendingFeatureCount() > 100000 )
return temporaryLocatorForLayer( vl, pointMap, tolerance );
else
return locatorForLayer( vl );
}
}

QgsPointLocator* QgsSnappingUtils::temporaryLocatorForLayer( QgsVectorLayer* vl, const QgsPoint& pointMap, double tolerance )
{
if ( mTemporaryLocators.contains( vl ) )
delete mTemporaryLocators.take( vl );

QgsRectangle rect( pointMap.x() - tolerance, pointMap.y() - tolerance,
pointMap.x() + tolerance, pointMap.y() + tolerance );
QgsPointLocator* vlpl = new QgsPointLocator( vl, destCRS(), &rect );
mTemporaryLocators.insert( vl, vlpl );
return mTemporaryLocators.value( vl );
}


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

// use ad-hoc locator
QgsPointLocator* loc = locatorForLayer( mCurrentLayer );
loc->init( QgsPointLocator::Vertex | QgsPointLocator::Edge );
QgsPointLocator* loc = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
if ( !loc )
return QgsPointLocator::Match();
loc->init( QgsPointLocator::Vertex | QgsPointLocator::Edge );

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

if ( mSnapOnIntersection )
{
QgsPointLocator::MatchList edges = locatorForLayer( mCurrentLayer )->edgesInTolerance( pointMap, tolerance );
QgsPointLocator* locEdges = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
QgsPointLocator::MatchList edges = locEdges->edgesInTolerance( pointMap, tolerance );
bestMatch.replaceIfBetter( _findClosestSegmentIntersection( pointMap, edges ), tolerance );
}

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

Expand Down Expand Up @@ -228,14 +262,14 @@ QgsPointLocator::Match QgsSnappingUtils::snapToCurrentLayer( const QPoint& point
if ( !mCurrentLayer )
return QgsPointLocator::Match();

QgsPointLocator* loc = locatorForLayer( mCurrentLayer );
loc->init( type );
if ( !loc )
return QgsPointLocator::Match();

QgsPoint pointMap = mMapSettings.mapToPixel().toMapCoordinates( point );
double tolerance = QgsTolerance::vertexSearchRadius( mMapSettings );

QgsPointLocator* loc = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
if ( !loc )
return QgsPointLocator::Match();
loc->init( type );

QgsPointLocator::Match bestMatch;
_updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter );
return bestMatch;
Expand Down Expand Up @@ -356,6 +390,15 @@ void QgsSnappingUtils::onLayersWillBeRemoved( QStringList layerIds )
continue;
}
}

for ( LocatorsMap::const_iterator it = mTemporaryLocators.constBegin(); it != mTemporaryLocators.constEnd(); ++it )
{
if ( it.key()->id() == layerId )
{
delete mTemporaryLocators.take( it.key() );
continue;
}
}
}
}

47 changes: 33 additions & 14 deletions src/core/qgssnappingutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,19 @@
#include "qgspointlocator.h"

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

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

enum IndexingStrategy
{
IndexAlwaysFull, //!< For all layers build index of full extent. Uses more memory, but queries are faster.
IndexNeverFull, //!< For all layers only create temporary indexes of small extent. Low memory usage, slower queries.
IndexHybrid //!< For "big" layers using IndexNeverFull, for the rest IndexAlwaysFull. Compromise between speed and memory usage.
};

/** Set a strategy for indexing geometry data - determines how fast and memory consuming the data structures will be */
void setIndexingStrategy( IndexingStrategy strategy ) { mStrategy = strategy; }
/** Find out which strategy is used for indexing - by default hybrid indexing is used */
IndexingStrategy indexingStrategy() const { return mStrategy; }

/** configure options used when the mode is snap to current layer */
void setDefaultSettings( int type, double tolerance, QgsTolerance::UnitType unit );
/** query options used when the mode is snap to current layer */
Expand All @@ -97,21 +119,10 @@ class QgsSnappingUtils : public QObject
/** Query whether to consider intersections of nearby segments for snapping */
bool snapOnIntersections() const { return mSnapOnIntersection; }

#if 0
/** Set topological editing status (used by some map tools) */
void setTopologicalEditing( bool enabled );
/** Query topological editing status (used by some map tools) */
bool topologicalEditing() const;
#endif

public slots:
/** Read snapping configuration from the project */
void readConfigFromProject();

// requirements:
// - support existing configurations
// - handle updates from QgsProject::setSnapSettingsForLayer()

private slots:
void onLayersWillBeRemoved( QStringList layerIds );

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

//! return a locator (temporary or not) according to the indexing strategy
QgsPointLocator* locatorForLayerUsingStrategy( QgsVectorLayer* vl, const QgsPoint& pointMap, double tolerance );
//! return a temporary locator with index only for a small area (will be replaced by another one on next request)
QgsPointLocator* temporaryLocatorForLayer( QgsVectorLayer* vl, const QgsPoint& pointMap, double tolerance );

private:
// environment
QgsMapSettings mMapSettings;
QgsVectorLayer* mCurrentLayer;

// configuration
SnapToMapMode mSnapToMapMode;
IndexingStrategy mStrategy;
int mDefaultType;
double mDefaultTolerance;
QgsTolerance::UnitType mDefaultUnit;
Expand All @@ -139,6 +156,8 @@ class QgsSnappingUtils : public QObject
typedef QMap<QgsVectorLayer*, QgsPointLocator*> LocatorsMap;
//! on-demand locators used (locators are owned)
LocatorsMap mLocators;
//! temporary locators (indexing just a part of layers). owned by the instance
LocatorsMap mTemporaryLocators;
};


Expand Down

0 comments on commit 0ea6a3d

Please sign in to comment.