diff --git a/python/core/qgssnappingutils.sip b/python/core/qgssnappingutils.sip index 34cff781378..83bd64798b8 100644 --- a/python/core/qgssnappingutils.sip +++ b/python/core/qgssnappingutils.sip @@ -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 ); @@ -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 */ diff --git a/src/core/qgssnappingutils.cpp b/src/core/qgssnappingutils.cpp index 75383b057f1..38ef629ca33 100644 --- a/src/core/qgssnappingutils.cpp +++ b/src/core/qgssnappingutils.cpp @@ -25,6 +25,7 @@ QgsSnappingUtils::QgsSnappingUtils( QObject* parent ) : QObject( parent ) , mCurrentLayer( 0 ) , mSnapToMapMode( SnapCurrentLayer ) + , mStrategy( IndexHybrid ) , mDefaultType( QgsPointLocator::Vertex ) , mDefaultTolerance( 10 ) , mDefaultUnit( QgsTolerance::Pixels ) @@ -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 ); } @@ -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 ); } @@ -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 ); @@ -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; @@ -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; + } + } } } diff --git a/src/core/qgssnappingutils.h b/src/core/qgssnappingutils.h index 677150cf399..965cdb04a45 100644 --- a/src/core/qgssnappingutils.h +++ b/src/core/qgssnappingutils.h @@ -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 */ @@ -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 ); @@ -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 */ @@ -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 ); @@ -122,6 +133,11 @@ 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; @@ -129,6 +145,7 @@ class QgsSnappingUtils : public QObject // configuration SnapToMapMode mSnapToMapMode; + IndexingStrategy mStrategy; int mDefaultType; double mDefaultTolerance; QgsTolerance::UnitType mDefaultUnit; @@ -139,6 +156,8 @@ class QgsSnappingUtils : public QObject typedef QMap LocatorsMap; //! on-demand locators used (locators are owned) LocatorsMap mLocators; + //! temporary locators (indexing just a part of layers). owned by the instance + LocatorsMap mTemporaryLocators; };