Branch data Line data Source code
1 : : /*************************************************************************** 2 : : qgssnappingutils.h 3 : : -------------------------------------- 4 : : Date : November 2014 5 : : Copyright : (C) 2014 by Martin Dobias 6 : : Email : wonder dot sk at gmail dot com 7 : : *************************************************************************** 8 : : * * 9 : : * This program is free software; you can redistribute it and/or modify * 10 : : * it under the terms of the GNU General Public License as published by * 11 : : * the Free Software Foundation; either version 2 of the License, or * 12 : : * (at your option) any later version. * 13 : : * * 14 : : ***************************************************************************/ 15 : : 16 : : #ifndef QGSSNAPPINGUTILS_H 17 : : #define QGSSNAPPINGUTILS_H 18 : : 19 : : 20 : : #include "qgis_core.h" 21 : : #include "qgis_sip.h" 22 : : #include "qgsmapsettings.h" 23 : : #include "qgstolerance.h" 24 : : #include "qgspointlocator.h" 25 : : #include "qgssnappingconfig.h" 26 : : 27 : : class QgsSnappingConfig; 28 : : 29 : : /** 30 : : * \ingroup core 31 : : * \brief This class has all the configuration of snapping and can return answers to snapping queries. 32 : : * 33 : : * Internally, it keeps a cache of QgsPointLocator instances for multiple layers. 34 : : * 35 : : * Currently it supports the following queries: 36 : : * 37 : : * - snapToMap() - has multiple modes of operation 38 : : * - snapToCurrentLayer() 39 : : * 40 : : * For more complex queries it is possible to use locatorForLayer() method that returns 41 : : * point locator instance with layer's indexed data. 42 : : * 43 : : * Indexing strategy determines how fast the queries will be and how much memory will be used. 44 : : * 45 : : * When working with map canvas, it may be useful to use derived class QgsMapCanvasSnappingUtils 46 : : * which keeps the configuration in sync with map canvas (e.g. current view, active layer). 47 : : * 48 : : * \since QGIS 2.8 49 : : */ 50 : : class CORE_EXPORT QgsSnappingUtils : public QObject 51 : : { 52 : : Q_OBJECT 53 : : 54 : : Q_PROPERTY( QgsSnappingConfig config READ config WRITE setConfig NOTIFY configChanged ) 55 : : 56 : : public: 57 : : 58 : : /** 59 : : * Constructor for QgsSnappingUtils 60 : : * \param parent parent object 61 : : * \param enableSnappingForInvisibleFeature TRUE if we want to snap feature even if there are not visible 62 : : */ 63 : : QgsSnappingUtils( QObject *parent SIP_TRANSFERTHIS = nullptr, bool enableSnappingForInvisibleFeature = true ); 64 : : ~QgsSnappingUtils() override; 65 : : 66 : : // main actions 67 : : 68 : : /** 69 : : * Gets a point locator for the given layer. If such locator does not exist, it will be created 70 : : * \param vl the vector layer 71 : : */ 72 : : QgsPointLocator *locatorForLayer( QgsVectorLayer *vl ); 73 : : 74 : : /** 75 : : * Snap to map according to the current configuration. 76 : : * \param point point in canvas coordinates 77 : : * \param filter allows discarding unwanted matches. 78 : : * \param relaxed TRUE if this method is non blocking and the matching result can be invalid while indexing 79 : : */ 80 : : QgsPointLocator::Match snapToMap( QPoint point, QgsPointLocator::MatchFilter *filter = nullptr, bool relaxed = false ); 81 : : 82 : : /** 83 : : * Snap to map according to the current configuration. 84 : : * \param pointMap point in map coordinates 85 : : * \param filter allows discarding unwanted matches. 86 : : * \param relaxed TRUE if this method is non blocking and the matching result can be invalid while indexing 87 : : */ 88 : : QgsPointLocator::Match snapToMap( const QgsPointXY &pointMap, QgsPointLocator::MatchFilter *filter = nullptr, bool relaxed = false ); 89 : : 90 : : //! Snap to current layer 91 : : QgsPointLocator::Match snapToCurrentLayer( QPoint point, QgsPointLocator::Types type, QgsPointLocator::MatchFilter *filter = nullptr ); 92 : : 93 : : // environment setup 94 : : 95 : : //! Assign current map settings to the utils - used for conversion between screen coords to map coords 96 : : void setMapSettings( const QgsMapSettings &settings ); 97 : : QgsMapSettings mapSettings() const { return mMapSettings; } 98 : : 99 : : //! Sets current layer so that if mode is SnapCurrentLayer we know which layer to use 100 : : void setCurrentLayer( QgsVectorLayer *layer ); 101 : : //! The current layer used if mode is SnapCurrentLayer 102 : : QgsVectorLayer *currentLayer() const { return mCurrentLayer; } 103 : : 104 : : // configuration 105 : : 106 : : enum IndexingStrategy 107 : : { 108 : : IndexAlwaysFull, //!< For all layers build index of full extent. Uses more memory, but queries are faster. 109 : : IndexNeverFull, //!< For all layers only create temporary indexes of small extent. Low memory usage, slower queries. 110 : : IndexHybrid, //!< For "big" layers using IndexNeverFull, for the rest IndexAlwaysFull. Compromise between speed and memory usage. 111 : : IndexExtent //!< For all layer build index of extent given in map settings 112 : : }; 113 : : 114 : : //! Sets a strategy for indexing geometry data - determines how fast and memory consuming the data structures will be 115 : : void setIndexingStrategy( IndexingStrategy strategy ) { mStrategy = strategy; } 116 : : //! Find out which strategy is used for indexing - by default hybrid indexing is used 117 : : IndexingStrategy indexingStrategy() const { return mStrategy; } 118 : : 119 : : /** 120 : : * Configures how a certain layer should be handled in a snapping operation 121 : : */ 122 : : struct LayerConfig 123 : : { 124 : : 125 : : /** 126 : : * Create a new configuration for a snapping layer. 127 : : * 128 : : * \code{.py} 129 : : * snapper = QgsMapCanvasSnappingUtils(mapCanvas) 130 : : * 131 : : * snapping_layer1 = QgsSnappingUtils.LayerConfig(layer1, QgsPointLocator.Vertex, 10, QgsTolerance.Pixels) 132 : : * snapping_layer2 = QgsSnappingUtils.LayerConfig(layer2, QgsPointLocator.Vertex and QgsPointLocator.Edge, 10, QgsTolerance.Pixels) 133 : : * 134 : : * snapper.setLayers([snapping_layer1, snapping_layer2]) 135 : : * \endcode 136 : : * 137 : : * \param l The vector layer for which this configuration is 138 : : * \param t Which parts of the geometry should be snappable 139 : : * \param tol The tolerance radius in which the snapping will trigger 140 : : * \param u The unit in which the tolerance is specified 141 : : */ 142 : 0 : LayerConfig( QgsVectorLayer *l, QgsPointLocator::Types t, double tol, QgsTolerance::UnitType u ) 143 : 0 : : layer( l ) 144 : 0 : , type( t ) 145 : 0 : , tolerance( tol ) 146 : 0 : , unit( u ) 147 : 0 : {} 148 : : 149 : : bool operator==( const QgsSnappingUtils::LayerConfig &other ) const 150 : : { 151 : : return layer == other.layer && type == other.type && tolerance == other.tolerance && unit == other.unit; 152 : : } 153 : : bool operator!=( const QgsSnappingUtils::LayerConfig &other ) const 154 : : { 155 : : return !operator==( other ); 156 : : } 157 : : 158 : : //! The layer to configure. 159 : : QgsVectorLayer *layer = nullptr; 160 : : //! To which geometry properties of this layers a snapping should happen. 161 : : QgsPointLocator::Types type; 162 : : //! The range around snapping targets in which snapping should occur. 163 : : double tolerance; 164 : : //! The units in which the tolerance is specified. 165 : : QgsTolerance::UnitType unit; 166 : : }; 167 : : 168 : : //! Query layers used for snapping 169 : : QList<QgsSnappingUtils::LayerConfig> layers() const { return mLayers; } 170 : : 171 : : /** 172 : : * Gets extra information about the instance 173 : : * \since QGIS 2.14 174 : : */ 175 : : QString dump(); 176 : : 177 : : /** 178 : : * The snapping configuration controls the behavior of this object 179 : : */ 180 : : QgsSnappingConfig config() const; 181 : : 182 : : /** 183 : : * Set if invisible features must be snapped or not. 184 : : * 185 : : * \param enable Enable or not this feature 186 : : * 187 : : * \since QGIS 3.2 188 : : */ 189 : : void setEnableSnappingForInvisibleFeature( bool enable ); 190 : : 191 : : /** 192 : : * Supply an extra snapping layer (typically a memory layer). 193 : : * This can be used by map tools to provide additional 194 : : * snappings points. 195 : : * 196 : : * \see removeExtraSnapLayer() 197 : : * \see getExtraSnapLayers() 198 : : * 199 : : * \since QGIS 3.14 200 : : */ 201 : : void addExtraSnapLayer( QgsVectorLayer *vl ) 202 : : { 203 : : mExtraSnapLayers.insert( vl ); 204 : : } 205 : : 206 : : /** 207 : : * Removes an extra snapping layer 208 : : * 209 : : * \see addExtraSnapLayer() 210 : : * \see getExtraSnapLayers() 211 : : * 212 : : * \since QGIS 3.14 213 : : */ 214 : : void removeExtraSnapLayer( QgsVectorLayer *vl ) 215 : : { 216 : : mExtraSnapLayers.remove( vl ); 217 : : } 218 : : 219 : : /** 220 : : * Returns the list of extra snapping layers 221 : : * 222 : : * \see addExtraSnapLayer() 223 : : * \see removeExtraSnapLayer() 224 : : * 225 : : * \since QGIS 3.14 226 : : */ 227 : : QSet<QgsVectorLayer *> getExtraSnapLayers() 228 : : { 229 : : return mExtraSnapLayers; 230 : : } 231 : : 232 : : 233 : : public slots: 234 : : 235 : : /** 236 : : * The snapping configuration controls the behavior of this object 237 : : */ 238 : : void setConfig( const QgsSnappingConfig &snappingConfig ); 239 : : 240 : : /** 241 : : * Toggles the state of snapping 242 : : * 243 : : * \since QGIS 3.0 244 : : */ 245 : : void toggleEnabled(); 246 : : 247 : : signals: 248 : : 249 : : /** 250 : : * Emitted when the snapping settings object changes. 251 : : */ 252 : : void configChanged( const QgsSnappingConfig &snappingConfig ); 253 : : 254 : : protected: 255 : : 256 : : //! Called when starting to index with snapToMap - can be overridden and e.g. progress dialog can be provided 257 : : virtual void prepareIndexStarting( int count ) { Q_UNUSED( count ); } 258 : : //! Called when finished indexing a layer with snapToMap. When index == count the indexing is complete 259 : : virtual void prepareIndexProgress( int index ) { Q_UNUSED( index ); } 260 : : 261 : : //! Deletes all existing locators (e.g. when destination CRS has changed and we need to reindex) 262 : : void clearAllLocators(); 263 : : 264 : : private slots: 265 : : 266 : : //! called whenever a point locator has finished 267 : : void onInitFinished( bool ok ); 268 : : 269 : : private: 270 : : void onIndividualLayerSettingsChanged( const QHash<QgsVectorLayer *, QgsSnappingConfig::IndividualLayerSettings> &layerSettings ); 271 : : //! Gets destination CRS from map settings, or an invalid CRS if projections are disabled 272 : : QgsCoordinateReferenceSystem destinationCrs() const; 273 : : 274 : : //! Returns a locator (temporary or not) according to the indexing strategy 275 : : QgsPointLocator *locatorForLayerUsingStrategy( QgsVectorLayer *vl, const QgsPointXY &pointMap, double tolerance ); 276 : : //! Returns a temporary locator with index only for a small area (will be replaced by another one on next request) 277 : : QgsPointLocator *temporaryLocatorForLayer( QgsVectorLayer *vl, const QgsPointXY &pointMap, double tolerance ); 278 : : 279 : : typedef QPair< QgsVectorLayer *, QgsRectangle > LayerAndAreaOfInterest; 280 : : 281 : : //! Returns TRUE if \a loc index is ready to be used in the area of interest \a areaOfInterest 282 : : bool isIndexPrepared( QgsPointLocator *loc, const QgsRectangle &areaOfInterest ); 283 : : //! initialize index for layers where it makes sense (according to the indexing strategy) 284 : : void prepareIndex( const QList<LayerAndAreaOfInterest> &layers, bool relaxed ); 285 : : 286 : : private: 287 : : // environment 288 : : QgsMapSettings mMapSettings; 289 : : QgsVectorLayer *mCurrentLayer = nullptr; 290 : : 291 : : QgsSnappingConfig mSnappingConfig; 292 : : 293 : : // configuration 294 : : IndexingStrategy mStrategy = IndexHybrid; 295 : : QList<LayerConfig> mLayers; 296 : : 297 : : // internal data 298 : : typedef QMap<QgsVectorLayer *, QgsPointLocator *> LocatorsMap; 299 : : //! on-demand locators used (locators are owned) 300 : : LocatorsMap mLocators; 301 : : //! temporary locators (indexing just a part of layers). owned by the instance 302 : : LocatorsMap mTemporaryLocators; 303 : : //! list of layer IDs that are too large to be indexed (hybrid strategy will use temporary locators for those) 304 : : QSet<QString> mHybridNonindexableLayers; 305 : : //! list of additional snapping layers 306 : : QSet<QgsVectorLayer *> mExtraSnapLayers; 307 : : 308 : : /** 309 : : * a record for each layer seen: 310 : : * 311 : : * - value -1 == it is small layer -> fully indexed 312 : : * - value > 0 == maximum area (in map units) for which it may make sense to build index. 313 : : * 314 : : * This means that index is built in area around the point with this total area, because 315 : : * for a larger area the number of features will likely exceed the limit. When the limit 316 : : * is exceeded, the maximum area is lowered to prevent that from happening. 317 : : * When requesting snap in area that is not currently indexed, layer's index is destroyed 318 : : * and a new one is built in the different area. 319 : : */ 320 : : QHash<QString, double> mHybridMaxAreaPerLayer; 321 : : //! if using hybrid strategy, how many features of one layer may be indexed (to limit amount of consumed memory) 322 : : int mHybridPerLayerFeatureLimit = 50000; 323 : : 324 : : //! Disable or not the snapping on all features. By default is always TRUE except for non visible features on map canvas. 325 : : bool mEnableSnappingForInvisibleFeature = true; 326 : : }; 327 : : 328 : : 329 : : #endif // QGSSNAPPINGUTILS_H