QGIS/src/core/qgssnappingutils.h
2016-10-20 12:56:35 +02:00

236 lines
10 KiB
C++

/***************************************************************************
qgssnappingutils.h
--------------------------------------
Date : November 2014
Copyright : (C) 2014 by Martin Dobias
Email : wonder dot sk at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSSNAPPINGUTILS_H
#define QGSSNAPPINGUTILS_H
#include "qgsmapsettings.h"
#include "qgstolerance.h"
#include "qgspointlocator.h"
#include "qgssnappingconfig.h"
class QgsSnappingConfig;
/** \ingroup core
* 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
*/
class CORE_EXPORT QgsSnappingUtils : public QObject
{
Q_OBJECT
Q_PROPERTY( QgsSnappingConfig config READ config WRITE setConfig NOTIFY configChanged )
public:
QgsSnappingUtils( QObject* parent = nullptr );
~QgsSnappingUtils();
// main actions
/** Get a point locator for the given layer. If such locator does not exist, it will be created */
QgsPointLocator* locatorForLayer( QgsVectorLayer* vl );
/** Snap to map according to the current configuration (mode). Optional filter allows discarding unwanted matches. */
QgsPointLocator::Match snapToMap( QPoint point, QgsPointLocator::MatchFilter* filter = nullptr );
QgsPointLocator::Match snapToMap( const QgsPoint& pointMap, QgsPointLocator::MatchFilter* filter = nullptr );
/** Snap to current layer */
QgsPointLocator::Match snapToCurrentLayer( QPoint point, int type, QgsPointLocator::MatchFilter* filter = nullptr );
// environment setup
/** Assign current map settings to the utils - used for conversion between screen coords to map coords */
void setMapSettings( const QgsMapSettings& settings );
QgsMapSettings mapSettings() const { return mMapSettings; }
/** Set current layer so that if mode is SnapCurrentLayer we know which layer to use */
void setCurrentLayer( QgsVectorLayer* layer );
/** The current layer used if mode is SnapCurrentLayer */
QgsVectorLayer* currentLayer() const { return mCurrentLayer; }
// configuration
//! modes for "snap to background"
enum SnapToMapMode
{
SnapCurrentLayer, //!< snap just to current layer (tolerance and type from defaultSettings())
SnapAllLayers, //!< snap to all rendered layers (tolerance and type from defaultSettings())
SnapAdvanced, //!< snap according to the configuration set in setLayers()
};
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; }
/**
* Configures how a certain layer should be handled in a snapping operation
*/
struct LayerConfig
{
/**
* Create a new configuration for a snapping layer.
```py
snapper = QgsMapCanvasSnappingUtils(mapCanvas)
snapping_layer1 = QgsSnappingUtils.LayerConfig(layer1, QgsPointLocator.Vertex, 10, QgsTolerance.Pixels)
snapping_layer2 = QgsSnappingUtils.LayerConfig(layer2, QgsPointLocator.Vertex and QgsPointLocator.Edge, 10, QgsTolerance.Pixels)
snapper.setLayers([snapping_layer1, snapping_layer2])
snapper.setSnapToMapMode(QgsSnappingUtils.SnapAdvanced)
```
* @param l The vector layer for which this configuration is
* @param t Which parts of the geometry should be snappable
* @param tol The tolerance radius in which the snapping will trigger
* @param u The unit in which the tolerance is specified
*/
LayerConfig( QgsVectorLayer* l, QgsPointLocator::Types t, double tol, QgsTolerance::UnitType u )
: layer( l )
, type( t )
, tolerance( tol )
, unit( u )
{}
bool operator==( const LayerConfig& other ) const
{
return layer == other.layer && type == other.type && tolerance == other.tolerance && unit == other.unit;
}
bool operator!=( const LayerConfig& other ) const
{
return !operator==( other );
}
//! The layer to configure.
QgsVectorLayer* layer;
//! To which geometry properties of this layers a snapping should happen.
QgsPointLocator::Types type;
//! The range around snapping targets in which snapping should occur.
double tolerance;
//! The units in which the tolerance is specified.
QgsTolerance::UnitType unit;
};
/** Query layers used for snapping */
QList<LayerConfig> layers() const { return mLayers; }
/** Get extra information about the instance
* @note added in QGIS 2.14
*/
QString dump();
/**
* The snapping configuration controls the behavior of this object
*/
QgsSnappingConfig config() const;
/**
* The snapping configuration controls the behavior of this object
*/
void setConfig( const QgsSnappingConfig& snappingConfig );
signals:
/**
* Emitted when the snapping settings object changes.
*/
void configChanged();
protected:
//! Called when starting to index - can be overridden and e.g. progress dialog can be provided
virtual void prepareIndexStarting( int count ) { Q_UNUSED( count ); }
//! Called when finished indexing a layer. When index == count the indexing is complete
virtual void prepareIndexProgress( int index ) { Q_UNUSED( index ); }
private:
void onIndividualLayerSettingsChanged( const QHash<QgsVectorLayer*, QgsSnappingConfig::IndividualLayerSettings> layerSettings );
//! Get destination CRS from map settings, or an invalid CRS if projections are disabled
QgsCoordinateReferenceSystem destinationCrs() const;
//! 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 );
typedef QPair< QgsVectorLayer*, QgsRectangle > LayerAndAreaOfInterest;
//! find out whether the strategy would index such layer or just use a temporary locator
bool isIndexPrepared( QgsVectorLayer* vl, const QgsRectangle& areaOfInterest );
//! initialize index for layers where it makes sense (according to the indexing strategy)
void prepareIndex( const QList<LayerAndAreaOfInterest>& layers );
private:
// environment
QgsMapSettings mMapSettings;
QgsVectorLayer* mCurrentLayer;
QgsSnappingConfig mSnappingConfig;
// configuration
IndexingStrategy mStrategy;
QList<LayerConfig> mLayers;
// internal data
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;
//! list of layer IDs that are too large to be indexed (hybrid strategy will use temporary locators for those)
QSet<QString> mHybridNonindexableLayers;
//! a record for each layer seen:
//! - value -1 == it is small layer -> fully indexed
//! - value > 0 == maximum area (in map units) for which it may make sense to build index.
//! This means that index is built in area around the point with this total area, because
//! for a larger area the number of features will likely exceed the limit. When the limit
//! is exceeded, the maximum area is lowered to prevent that from happening.
//! When requesting snap in area that is not currently indexed, layer's index is destroyed
//! and a new one is built in the different area.
QHash<QString, double> mHybridMaxAreaPerLayer;
//! if using hybrid strategy, how many features of one layer may be indexed (to limit amount of consumed memory)
int mHybridPerLayerFeatureLimit;
//! internal flag that an indexing process is going on. Prevents starting two processes in parallel.
bool mIsIndexing;
};
#endif // QGSSNAPPINGUTILS_H