QGIS API Documentation  2.14.0-Essen
qgssnappingutils.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgssnappingutils.cpp
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 #include "qgssnappingutils.h"
17 
18 #include "qgsgeometry.h"
19 #include "qgsmaplayerregistry.h"
20 #include "qgsproject.h"
21 #include "qgsvectorlayer.h"
22 
23 
25  : QObject( parent )
26  , mCurrentLayer( nullptr )
27  , mSnapToMapMode( SnapCurrentLayer )
28  , mStrategy( IndexHybrid )
29  , mDefaultType( QgsPointLocator::Vertex )
30  , mDefaultTolerance( 10 )
31  , mDefaultUnit( QgsTolerance::Pixels )
32  , mSnapOnIntersection( false )
33  , mHybridPerLayerFeatureLimit( 50000 )
34  , mIsIndexing( false )
35 {
36  connect( QgsMapLayerRegistry::instance(), SIGNAL( layersWillBeRemoved( QStringList ) ), this, SLOT( onLayersWillBeRemoved( QStringList ) ) );
37 }
38 
40 {
41  clearAllLocators();
42 }
43 
44 
46 {
47  if ( !vl )
48  return nullptr;
49 
50  if ( !mLocators.contains( vl ) )
51  {
52  QgsPointLocator* vlpl = new QgsPointLocator( vl, destCRS() );
53  mLocators.insert( vl, vlpl );
54  }
55  return mLocators.value( vl );
56 }
57 
58 void QgsSnappingUtils::clearAllLocators()
59 {
60  Q_FOREACH ( QgsPointLocator* vlpl, mLocators )
61  delete vlpl;
62  mLocators.clear();
63 
64  Q_FOREACH ( QgsPointLocator* vlpl, mTemporaryLocators )
65  delete vlpl;
66  mTemporaryLocators.clear();
67 }
68 
69 
70 QgsPointLocator* QgsSnappingUtils::locatorForLayerUsingStrategy( QgsVectorLayer* vl, const QgsPoint& pointMap, double tolerance )
71 {
72  QgsRectangle aoi( pointMap.x() - tolerance, pointMap.y() - tolerance,
73  pointMap.x() + tolerance, pointMap.y() + tolerance );
74  if ( isIndexPrepared( vl, aoi ) )
75  return locatorForLayer( vl );
76  else
77  return temporaryLocatorForLayer( vl, pointMap, tolerance );
78 }
79 
80 QgsPointLocator* QgsSnappingUtils::temporaryLocatorForLayer( QgsVectorLayer* vl, const QgsPoint& pointMap, double tolerance )
81 {
82  if ( mTemporaryLocators.contains( vl ) )
83  delete mTemporaryLocators.take( vl );
84 
85  QgsRectangle rect( pointMap.x() - tolerance, pointMap.y() - tolerance,
86  pointMap.x() + tolerance, pointMap.y() + tolerance );
87  QgsPointLocator* vlpl = new QgsPointLocator( vl, destCRS(), &rect );
88  mTemporaryLocators.insert( vl, vlpl );
89  return mTemporaryLocators.value( vl );
90 }
91 
92 bool QgsSnappingUtils::isIndexPrepared( QgsVectorLayer* vl, const QgsRectangle& areaOfInterest )
93 {
94  if ( vl->geometryType() == QGis::NoGeometry || mStrategy == IndexNeverFull )
95  return false;
96 
97  QgsPointLocator* loc = locatorForLayer( vl );
98 
99  if ( mStrategy == IndexAlwaysFull && loc->hasIndex() )
100  return true;
101 
102  if ( mStrategy == IndexHybrid && loc->hasIndex() && ( !loc->extent() || loc->extent()->contains( areaOfInterest ) ) )
103  return true;
104 
105  return false; // the index - even if it exists - is not suitable
106 }
107 
108 
110 {
111  if ( segments.isEmpty() )
112  return QgsPointLocator::Match();
113 
114  QSet<QgsPoint> endpoints;
115 
116  // make a geometry
117  QList<QgsGeometry*> geoms;
118  Q_FOREACH ( const QgsPointLocator::Match& m, segments )
119  {
120  if ( m.hasEdge() )
121  {
122  QgsPolyline pl( 2 );
123  m.edgePoints( pl[0], pl[1] );
124  geoms << QgsGeometry::fromPolyline( pl );
125  endpoints << pl[0] << pl[1];
126  }
127  }
128 
129  QgsGeometry* g = QgsGeometry::unaryUnion( geoms );
130  qDeleteAll( geoms );
131 
132  // get intersection points
133  QList<QgsPoint> newPoints;
134  if ( g->wkbType() == QGis::WKBLineString )
135  {
136  Q_FOREACH ( const QgsPoint& p, g->asPolyline() )
137  {
138  if ( !endpoints.contains( p ) )
139  newPoints << p;
140  }
141  }
142  if ( g->wkbType() == QGis::WKBMultiLineString )
143  {
144  Q_FOREACH ( const QgsPolyline& pl, g->asMultiPolyline() )
145  {
146  Q_FOREACH ( const QgsPoint& p, pl )
147  {
148  if ( !endpoints.contains( p ) )
149  newPoints << p;
150  }
151  }
152  }
153  delete g;
154 
155  if ( newPoints.isEmpty() )
156  return QgsPointLocator::Match();
157 
158  // find the closest points
159  QgsPoint minP;
160  double minSqrDist = 1e20; // "infinity"
161  Q_FOREACH ( const QgsPoint& p, newPoints )
162  {
163  double sqrDist = pt.sqrDist( p.x(), p.y() );
164  if ( sqrDist < minSqrDist )
165  {
166  minSqrDist = sqrDist;
167  minP = p;
168  }
169  }
170 
171  return QgsPointLocator::Match( QgsPointLocator::Vertex, nullptr, 0, sqrt( minSqrDist ), minP );
172 }
173 
174 
175 static void _replaceIfBetter( QgsPointLocator::Match& mBest, const QgsPointLocator::Match& mNew, double maxDistance )
176 {
177  // is other match relevant?
178  if ( !mNew.isValid() || mNew.distance() > maxDistance )
179  return;
180 
181  // is other match actually better?
182  if ( mBest.isValid() && mBest.type() == mNew.type() && mBest.distance() - 10e-6 < mNew.distance() )
183  return;
184 
185  // prefer vertex matches to edge matches (even if they are closer)
186  if ( mBest.type() == QgsPointLocator::Vertex && mNew.type() == QgsPointLocator::Edge )
187  return;
188 
189  mBest = mNew; // the other match is better!
190 }
191 
192 
193 static void _updateBestMatch( QgsPointLocator::Match& bestMatch, const QgsPoint& pointMap, QgsPointLocator* loc, int type, double tolerance, QgsPointLocator::MatchFilter* filter )
194 {
195  if ( type & QgsPointLocator::Vertex )
196  {
197  _replaceIfBetter( bestMatch, loc->nearestVertex( pointMap, tolerance, filter ), tolerance );
198  }
199  if ( bestMatch.type() != QgsPointLocator::Vertex && ( type & QgsPointLocator::Edge ) )
200  {
201  _replaceIfBetter( bestMatch, loc->nearestEdge( pointMap, tolerance, filter ), tolerance );
202  }
203 }
204 
205 
207 {
208  return snapToMap( mMapSettings.mapToPixel().toMapCoordinates( point ), filter );
209 }
210 
211 inline QgsRectangle _areaOfInterest( const QgsPoint& point, double tolerance )
212 {
213  return QgsRectangle( point.x() - tolerance, point.y() - tolerance,
214  point.x() + tolerance, point.y() + tolerance );
215 }
216 
218 {
219  if ( !mMapSettings.hasValidSettings() )
220  return QgsPointLocator::Match();
221 
222  if ( mSnapToMapMode == SnapCurrentLayer )
223  {
224  if ( !mCurrentLayer || mDefaultType == 0 )
225  return QgsPointLocator::Match();
226 
227  // data from project
228  double tolerance = QgsTolerance::toleranceInProjectUnits( mDefaultTolerance, mCurrentLayer, mMapSettings, mDefaultUnit );
229  int type = mDefaultType;
230 
231  prepareIndex( QList<LayerAndAreaOfInterest>() << qMakePair( mCurrentLayer, _areaOfInterest( pointMap, tolerance ) ) );
232 
233  // use ad-hoc locator
234  QgsPointLocator* loc = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
235  if ( !loc )
236  return QgsPointLocator::Match();
237 
238  QgsPointLocator::Match bestMatch;
239  _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter );
240 
241  if ( mSnapOnIntersection )
242  {
243  QgsPointLocator* locEdges = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
244  QgsPointLocator::MatchList edges = locEdges->edgesInRect( pointMap, tolerance );
245  _replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), tolerance );
246  }
247 
248  return bestMatch;
249  }
250  else if ( mSnapToMapMode == SnapAdvanced )
251  {
253  Q_FOREACH ( const LayerConfig& layerConfig, mLayers )
254  {
255  double tolerance = QgsTolerance::toleranceInProjectUnits( layerConfig.tolerance, layerConfig.layer, mMapSettings, layerConfig.unit );
256  layers << qMakePair( layerConfig.layer, _areaOfInterest( pointMap, tolerance ) );
257  }
258  prepareIndex( layers );
259 
260  QgsPointLocator::Match bestMatch;
261  QgsPointLocator::MatchList edges; // for snap on intersection
262  double maxSnapIntTolerance = 0;
263 
264  Q_FOREACH ( const LayerConfig& layerConfig, mLayers )
265  {
266  double tolerance = QgsTolerance::toleranceInProjectUnits( layerConfig.tolerance, layerConfig.layer, mMapSettings, layerConfig.unit );
267  if ( QgsPointLocator* loc = locatorForLayerUsingStrategy( layerConfig.layer, pointMap, tolerance ) )
268  {
269  _updateBestMatch( bestMatch, pointMap, loc, layerConfig.type, tolerance, filter );
270 
271  if ( mSnapOnIntersection )
272  {
273  edges << loc->edgesInRect( pointMap, tolerance );
274  maxSnapIntTolerance = qMax( maxSnapIntTolerance, tolerance );
275  }
276  }
277  }
278 
279  if ( mSnapOnIntersection )
280  _replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), maxSnapIntTolerance );
281 
282  return bestMatch;
283  }
284  else if ( mSnapToMapMode == SnapAllLayers )
285  {
286  // data from project
287  double tolerance = QgsTolerance::toleranceInProjectUnits( mDefaultTolerance, nullptr, mMapSettings, mDefaultUnit );
288  int type = mDefaultType;
289  QgsRectangle aoi = _areaOfInterest( pointMap, tolerance );
290 
292  Q_FOREACH ( const QString& layerID, mMapSettings.layers() )
293  if ( QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( layerID ) ) )
294  layers << qMakePair( vl, aoi );
295  prepareIndex( layers );
296 
297  QgsPointLocator::MatchList edges; // for snap on intersection
298  QgsPointLocator::Match bestMatch;
299 
300  Q_FOREACH ( const LayerAndAreaOfInterest& entry, layers )
301  {
302  QgsVectorLayer* vl = entry.first;
303  if ( QgsPointLocator* loc = locatorForLayerUsingStrategy( vl, pointMap, tolerance ) )
304  {
305  _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter );
306 
307  if ( mSnapOnIntersection )
308  edges << loc->edgesInRect( pointMap, tolerance );
309  }
310  }
311 
312  if ( mSnapOnIntersection )
313  _replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), tolerance );
314 
315  return bestMatch;
316  }
317 
318  return QgsPointLocator::Match();
319 }
320 
321 
322 void QgsSnappingUtils::prepareIndex( const QList<LayerAndAreaOfInterest>& layers )
323 {
324  if ( mIsIndexing )
325  return;
326  mIsIndexing = true;
327 
328  // check if we need to build any index
329  QList<LayerAndAreaOfInterest> layersToIndex;
330  Q_FOREACH ( const LayerAndAreaOfInterest& entry, layers )
331  {
332  QgsVectorLayer* vl = entry.first;
333  if ( vl->geometryType() == QGis::NoGeometry || mStrategy == IndexNeverFull )
334  continue;
335 
336  if ( !isIndexPrepared( vl, entry.second ) )
337  layersToIndex << entry;
338  }
339  if ( !layersToIndex.isEmpty() )
340  {
341  // build indexes
342  QTime t;
343  t.start();
344  int i = 0;
345  prepareIndexStarting( layersToIndex.count() );
346  Q_FOREACH ( const LayerAndAreaOfInterest& entry, layersToIndex )
347  {
348  QgsVectorLayer* vl = entry.first;
349  QTime tt;
350  tt.start();
351  QgsPointLocator* loc = locatorForLayer( vl );
352  if ( mStrategy == IndexHybrid )
353  {
354  // first time the layer is used? - let's set an initial guess about indexing
355  if ( !mHybridMaxAreaPerLayer.contains( vl->id() ) )
356  {
357  int totalFeatureCount = vl->pendingFeatureCount();
358  if ( totalFeatureCount < mHybridPerLayerFeatureLimit )
359  {
360  // index the whole layer
361  mHybridMaxAreaPerLayer[vl->id()] = -1;
362  }
363  else
364  {
365  // estimate for how big area it probably makes sense to build partial index to not exceed the limit
366  // (we may change the limit later)
367  QgsRectangle layerExtent = mMapSettings.layerExtentToOutputExtent( vl, vl->extent() );
368  double totalArea = layerExtent.width() * layerExtent.height();
369  mHybridMaxAreaPerLayer[vl->id()] = totalArea * mHybridPerLayerFeatureLimit / totalFeatureCount / 4;
370  }
371  }
372 
373  double indexReasonableArea = mHybridMaxAreaPerLayer[vl->id()];
374  if ( indexReasonableArea == -1 )
375  {
376  // we can safely index the whole layer
377  loc->init();
378  }
379  else
380  {
381  // use area as big as we think may fit into our limit
382  QgsPoint c = entry.second.center();
383  double halfSide = sqrt( indexReasonableArea ) / 2;
384  QgsRectangle rect( c.x() - halfSide, c.y() - halfSide,
385  c.x() + halfSide, c.y() + halfSide );
386  loc->setExtent( &rect );
387 
388  // see if it's possible build index for this area
389  if ( !loc->init( mHybridPerLayerFeatureLimit ) )
390  {
391  // hmm that didn't work out - too many features!
392  // let's make the allowed area smaller for the next time
393  mHybridMaxAreaPerLayer[vl->id()] /= 4;
394  }
395  }
396 
397  }
398  else // full index strategy
399  loc->init();
400 
401  QgsDebugMsg( QString( "Index init: %1 ms (%2)" ).arg( tt.elapsed() ).arg( vl->id() ) );
402  prepareIndexProgress( ++i );
403  }
404  QgsDebugMsg( QString( "Prepare index total: %1 ms" ).arg( t.elapsed() ) );
405  }
406  mIsIndexing = false;
407 }
408 
409 
411 {
412  if ( !mCurrentLayer )
413  return QgsPointLocator::Match();
414 
415  QgsPoint pointMap = mMapSettings.mapToPixel().toMapCoordinates( point );
416  double tolerance = QgsTolerance::vertexSearchRadius( mMapSettings );
417 
418  QgsPointLocator* loc = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
419  if ( !loc )
420  return QgsPointLocator::Match();
421 
422  QgsPointLocator::Match bestMatch;
423  _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter );
424  return bestMatch;
425 }
426 
428 {
429  QString oldDestCRS = mMapSettings.hasCrsTransformEnabled() ? mMapSettings.destinationCrs().authid() : QString();
430  QString newDestCRS = settings.hasCrsTransformEnabled() ? settings.destinationCrs().authid() : QString();
431  mMapSettings = settings;
432 
433  if ( newDestCRS != oldDestCRS )
434  clearAllLocators();
435 }
436 
438 {
439  mCurrentLayer = layer;
440 }
441 
443 {
444  if ( mSnapToMapMode == mode )
445  return;
446 
447  mSnapToMapMode = mode;
448  emit configChanged();
449 }
450 
451 void QgsSnappingUtils::setDefaultSettings( int type, double tolerance, QgsTolerance::UnitType unit )
452 {
453  // force map units - can't use layer units for just any layer
454  if ( unit == QgsTolerance::LayerUnits )
456 
457  if ( mDefaultType == type && mDefaultTolerance == tolerance && mDefaultUnit == unit )
458  return;
459 
460  mDefaultType = type;
461  mDefaultTolerance = tolerance;
462  mDefaultUnit = unit;
463 
464  if ( mSnapToMapMode != SnapAdvanced ) // does not affect advanced mode
465  emit configChanged();
466 }
467 
468 void QgsSnappingUtils::defaultSettings( int& type, double& tolerance, QgsTolerance::UnitType& unit )
469 {
470  type = mDefaultType;
471  tolerance = mDefaultTolerance;
472  unit = mDefaultUnit;
473 }
474 
476 {
477  if ( mLayers == layers )
478  return;
479 
480  mLayers = layers;
481  if ( mSnapToMapMode == SnapAdvanced ) // only affects advanced mode
482  emit configChanged();
483 }
484 
486 {
487  if ( mSnapOnIntersection == enabled )
488  return;
489 
490  mSnapOnIntersection = enabled;
491  emit configChanged();
492 }
493 
495 {
496  QString msg = "--- SNAPPING UTILS DUMP ---\n";
497 
498  if ( !mMapSettings.hasValidSettings() )
499  {
500  msg += "invalid map settings!";
501  return msg;
502  }
503 
505 
506  if ( mSnapToMapMode == SnapCurrentLayer )
507  {
508  if ( mSnapToMapMode == SnapCurrentLayer && !mCurrentLayer )
509  {
510  msg += "no current layer!";
511  return msg;
512  }
513 
514  layers << LayerConfig( mCurrentLayer, QgsPointLocator::Types( mDefaultType ), mDefaultTolerance, mDefaultUnit );
515  }
516  else if ( mSnapToMapMode == SnapAllLayers )
517  {
518  Q_FOREACH ( const QString& layerID, mMapSettings.layers() )
519  {
520  if ( QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( layerID ) ) )
521  layers << LayerConfig( vl, QgsPointLocator::Types( mDefaultType ), mDefaultTolerance, mDefaultUnit );
522  }
523  }
524  else if ( mSnapToMapMode == SnapAdvanced )
525  {
526  layers = mLayers;
527  }
528 
529  Q_FOREACH ( const LayerConfig& layer, layers )
530  {
531  msg += QString( "layer : %1\n"
532  "config: %2 tolerance %3 %4\n" )
533  .arg( layer.layer->name() )
534  .arg( layer.type ).arg( layer.tolerance ).arg( layer.unit );
535 
536  if ( mStrategy == IndexAlwaysFull || mStrategy == IndexHybrid )
537  {
538  if ( QgsPointLocator* loc = locatorForLayer( layer.layer ) )
539  {
540  QString extentStr, cachedGeoms, limit( "no max area" );
541  if ( const QgsRectangle* r = loc->extent() )
542  {
543  extentStr = QString( " extent %1" ).arg( r->toString() );
544  }
545  else
546  extentStr = "full extent";
547  if ( loc->hasIndex() )
548  cachedGeoms = QString( "%1 feats" ).arg( loc->cachedGeometryCount() );
549  else
550  cachedGeoms = "not initialized";
551  if ( mStrategy == IndexHybrid )
552  {
553  if ( mHybridMaxAreaPerLayer.contains( layer.layer->id() ) )
554  {
555  double maxArea = mStrategy == IndexHybrid ? mHybridMaxAreaPerLayer[layer.layer->id()] : -1;
556  if ( maxArea != -1 )
557  limit = QString( "max area %1" ).arg( maxArea );
558  }
559  else
560  limit = "not evaluated";
561  }
562  msg += QString( "index : YES | %1 | %2 | %3\n" ).arg( cachedGeoms ).arg( extentStr ).arg( limit );
563  }
564  else
565  msg += QString( "index : ???\n" ); // should not happen
566  }
567  else
568  msg += "index : NO\n";
569  msg += "-\n";
570  }
571 
572  return msg;
573 }
574 
575 const QgsCoordinateReferenceSystem* QgsSnappingUtils::destCRS()
576 {
577  return mMapSettings.hasCrsTransformEnabled() ? &mMapSettings.destinationCrs() : nullptr;
578 }
579 
580 
582 {
583  mSnapToMapMode = SnapCurrentLayer;
584  mLayers.clear();
585 
586  QString snapMode = QgsProject::instance()->readEntry( "Digitizing", "/SnappingMode" );
587 
588  int type = 0;
589  QString snapType = QgsProject::instance()->readEntry( "Digitizing", "/DefaultSnapType", QString( "off" ) );
590  if ( snapType == "to segment" )
591  type = QgsPointLocator::Edge;
592  else if ( snapType == "to vertex and segment" )
594  else if ( snapType == "to vertex" )
596  double tolerance = QgsProject::instance()->readDoubleEntry( "Digitizing", "/DefaultSnapTolerance", 0 );
597  QgsTolerance::UnitType unit = static_cast< QgsTolerance::UnitType >( QgsProject::instance()->readNumEntry( "Digitizing", "/DefaultSnapToleranceUnit", QgsTolerance::ProjectUnits ) );
598  setDefaultSettings( type, tolerance, unit );
599 
600  //snapping on intersection on?
601  setSnapOnIntersections( QgsProject::instance()->readNumEntry( "Digitizing", "/IntersectionSnapping", 0 ) );
602 
603  //read snapping settings from project
604  bool snappingDefinedInProject, ok;
605  QStringList layerIdList = QgsProject::instance()->readListEntry( "Digitizing", "/LayerSnappingList", QStringList(), &snappingDefinedInProject );
606  QStringList enabledList = QgsProject::instance()->readListEntry( "Digitizing", "/LayerSnappingEnabledList", QStringList(), &ok );
607  QStringList toleranceList = QgsProject::instance()->readListEntry( "Digitizing", "/LayerSnappingToleranceList", QStringList(), &ok );
608  QStringList toleranceUnitList = QgsProject::instance()->readListEntry( "Digitizing", "/LayerSnappingToleranceUnitList", QStringList(), &ok );
609  QStringList snapToList = QgsProject::instance()->readListEntry( "Digitizing", "/LayerSnapToList", QStringList(), &ok );
610 
611  // lists must have the same size, otherwise something is wrong
612  if ( layerIdList.size() != enabledList.size() ||
613  layerIdList.size() != toleranceList.size() ||
614  layerIdList.size() != toleranceUnitList.size() ||
615  layerIdList.size() != snapToList.size() )
616  return;
617 
618  if ( !snappingDefinedInProject )
619  return; // nothing defined in project - use current layer
620 
621  // Use snapping information from the project
622  if ( snapMode == "current_layer" )
623  mSnapToMapMode = SnapCurrentLayer;
624  else if ( snapMode == "all_layers" )
625  mSnapToMapMode = SnapAllLayers;
626  else // either "advanced" or empty (for background compatibility)
627  mSnapToMapMode = SnapAdvanced;
628 
629 
630 
631  // load layers, tolerances, snap type
632  QStringList::const_iterator layerIt( layerIdList.constBegin() );
633  QStringList::const_iterator tolIt( toleranceList.constBegin() );
634  QStringList::const_iterator tolUnitIt( toleranceUnitList.constBegin() );
635  QStringList::const_iterator snapIt( snapToList.constBegin() );
636  QStringList::const_iterator enabledIt( enabledList.constBegin() );
637  for ( ; layerIt != layerIdList.constEnd(); ++layerIt, ++tolIt, ++tolUnitIt, ++snapIt, ++enabledIt )
638  {
639  // skip layer if snapping is not enabled
640  if ( *enabledIt != "enabled" )
641  continue;
642 
643  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( QgsMapLayerRegistry::instance()->mapLayer( *layerIt ) );
644  if ( !vlayer || !vlayer->hasGeometryType() )
645  continue;
646 
647  QgsPointLocator::Types t( *snapIt == "to_vertex" ? QgsPointLocator::Vertex :
648  ( *snapIt == "to_segment" ? QgsPointLocator::Edge :
650  )
651  );
652  mLayers.append( LayerConfig( vlayer, t, tolIt->toDouble(), static_cast< QgsTolerance::UnitType >( tolUnitIt->toInt() ) ) );
653  }
654 
655  emit configChanged();
656 }
657 
658 void QgsSnappingUtils::onLayersWillBeRemoved( const QStringList& layerIds )
659 {
660  // remove locators for layers that are going to be deleted
661  Q_FOREACH ( const QString& layerId, layerIds )
662  {
663  if ( mHybridMaxAreaPerLayer.contains( layerId ) )
664  mHybridMaxAreaPerLayer.remove( layerId );
665 
666  for ( LocatorsMap::iterator it = mLocators.begin(); it != mLocators.end(); )
667  {
668  if ( it.key()->id() == layerId )
669  {
670  delete it.value();
671  it = mLocators.erase( it );
672  }
673  else
674  {
675  ++it;
676  }
677  }
678 
679  for ( LocatorsMap::iterator it = mTemporaryLocators.begin(); it != mTemporaryLocators.end(); )
680  {
681  if ( it.key()->id() == layerId )
682  {
683  delete it.value();
684  it = mTemporaryLocators.erase( it );
685  }
686  else
687  {
688  ++it;
689  }
690  }
691  }
692 }
693 
The class defines interface for querying point location:
A rectangle specified with double values.
Definition: qgsrectangle.h:35
void setSnapOnIntersections(bool enabled)
Set whether to consider intersections of nearby segments for snapping.
iterator erase(iterator pos)
bool contains(const Key &key) const
void setCurrentLayer(QgsVectorLayer *layer)
Set current layer so that if mode is SnapCurrentLayer we know which layer to use. ...
QString name() const
Get the display name of the layer.
virtual void prepareIndexProgress(int index)
Called when finished indexing a layer. When index == count the indexing is complete.
snap to all rendered layers (tolerance and type from defaultSettings())
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
QgsMultiPolyline asMultiPolyline() const
Return contents of the geometry as a multi linestring if wkbType is WKBMultiLineString, otherwise an empty list.
bool contains(const QgsRectangle &rect) const
return true when rectangle contains other rectangle
UnitType
Type of unit of tolerance value from settings.
Definition: qgstolerance.h:33
bool hasCrsTransformEnabled() const
returns true if projections are enabled for this layer set
QgsRectangle layerExtentToOutputExtent(QgsMapLayer *theLayer, QgsRectangle extent) const
transform bounding box from layer&#39;s CRS to output CRS
QStringList readListEntry(const QString &scope, const QString &key, const QStringList &def=QStringList(), bool *ok=nullptr) const
Key value accessors.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:76
const QgsMapToPixel & mapToPixel() const
Interface that allows rejection of some matches in intersection queries (e.g.
void clear()
double sqrDist(double x, double y) const
Returns the squared distance between this point and x,y.
Definition: qgspoint.cpp:345
double x() const
Get the x value of the point.
Definition: qgspoint.h:128
void readConfigFromProject()
Read snapping configuration from the project.
int size() const
QgsMapLayer * mapLayer(const QString &theLayerId)
Retrieve a pointer to a loaded layer by id.
The QgsMapSettings class contains configuration for rendering of the map.
QList< LayerConfig > layers() const
Query layers used for snapping.
const QgsRectangle * extent() const
Get extent of the area point locator covers - if null then it caches the whole layer.
int elapsed() const
QString readEntry(const QString &scope, const QString &key, const QString &def=QString::null, bool *ok=nullptr) const
For all layers build index of full extent. Uses more memory, but queries are faster.
int count(const T &value) const
QgsRectangle extent() override
Return the extent of the layer.
QString dump()
Get extra information about the instance.
For "big" layers using IndexNeverFull, for the rest IndexAlwaysFull. Compromise between speed and mem...
void setSnapToMapMode(SnapToMapMode mode)
Set how the snapping to map is done.
static void _updateBestMatch(QgsPointLocator::Match &bestMatch, const QgsPoint &pointMap, QgsPointLocator *loc, int type, double tolerance, QgsPointLocator::MatchFilter *filter)
bool hasValidSettings() const
Check whether the map settings are valid and can be used for rendering.
Match nearestEdge(const QgsPoint &point, double tolerance, MatchFilter *filter=nullptr)
Find nearest edges to the specified point - up to distance specified by tolerance Optional filter may...
const QgsCoordinateReferenceSystem & destinationCrs() const
returns CRS of destination coordinate reference system
bool isEmpty() const
This is the class is providing tolerance value in map unit values.
Definition: qgstolerance.h:26
QgsSnappingUtils(QObject *parent=nullptr)
static void _replaceIfBetter(QgsPointLocator::Match &mBest, const QgsPointLocator::Match &mNew, double maxDistance)
QString id() const
Get this layer&#39;s unique ID, this ID is used to access this layer from map layer registry.
QGis::WkbType wkbType() const
Returns type of the geometry as a WKB type (point / linestring / polygon etc.)
void setExtent(const QgsRectangle *extent)
Configure extent - if not null, it will index only that area.
QGis::GeometryType geometryType() const
Returns point, line or polygon.
QgsTolerance::UnitType unit
The units in which the tolerance is specified.
iterator end()
int remove(const Key &key)
snap according to the configuration set in setLayers()
void configChanged()
Emitted when snapping configuration has been changed.
double distance() const
for vertex / edge match units depending on what class returns it (geom.cache: layer units...
void setMapSettings(const QgsMapSettings &settings)
Assign current map settings to the utils - used for conversion between screen coords to map coords...
class QList< Match > MatchList
A class to represent a point.
Definition: qgspoint.h:65
iterator begin()
double tolerance
The range around snapping targets in which snapping should occur.
For all layers only create temporary indexes of small extent. Low memory usage, slower queries...
int readNumEntry(const QString &scope, const QString &key, int def=0, bool *ok=nullptr) const
bool contains(const T &value) const
static QgsPointLocator::Match _findClosestSegmentIntersection(const QgsPoint &pt, const QgsPointLocator::MatchList &segments)
Layer unit value.
Definition: qgstolerance.h:38
QgsPoint toMapCoordinates(int x, int y) const
static double vertexSearchRadius(const QgsMapSettings &mapSettings)
Static function to get vertex tolerance value.
double readDoubleEntry(const QString &scope, const QString &key, double def=0, bool *ok=nullptr) const
QgsPolyline asPolyline() const
Return contents of the geometry as a polyline if wkbType is WKBLineString, otherwise an empty list...
long pendingFeatureCount() const
Returns feature count including changes which have not yet been committed Alias for featureCount()...
virtual void prepareIndexStarting(int count)
Called when starting to index - can be overridden and e.g. progress dialog can be provided...
bool hasGeometryType() const
Returns true if this is a geometry layer and false in case of NoGeometry (table only) or UnknownGeome...
QgsPointLocator::Match snapToCurrentLayer(QPoint point, int type, QgsPointLocator::MatchFilter *filter=nullptr)
Snap to current layer.
QgsPointLocator * locatorForLayer(QgsVectorLayer *vl)
Get a point locator for the given layer.
static QgsMapLayerRegistry * instance()
Returns the instance pointer, creating the object on the first call.
Map (project) units.
Definition: qgstolerance.h:42
static double toleranceInProjectUnits(double tolerance, QgsMapLayer *layer, const QgsMapSettings &mapSettings, QgsTolerance::UnitType units)
Static function to translate tolerance value into map units.
QgsVectorLayer * layer
The layer to configure.
Configures how a certain layer should be handled in a snapping operation.
bool init(int maxFeaturesToIndex=-1)
Prepare the index for queries.
MatchList edgesInRect(const QgsRectangle &rect, MatchFilter *filter=nullptr)
Find edges within a specified recangle Optional filter may discard unwanted matches.
static QgsProject * instance()
access to canonical QgsProject instance
Definition: qgsproject.cpp:381
Class for storing a coordinate reference system (CRS)
QString authid() const
Returns the authority identifier for the CRS, which includes both the authority (eg EPSG) and the CRS...
void edgePoints(QgsPoint &pt1, QgsPoint &pt2) const
Only for a valid edge match - obtain endpoints of the edge.
static QgsGeometry * fromPolyline(const QgsPolyline &polyline)
Creates a new geometry from a QgsPolyline object.
QgsPointLocator::Types type
To which geometry properties of this layers a snapping should happen.
double y() const
Get the y value of the point.
Definition: qgspoint.h:136
QStringList layers() const
Get list of layer IDs for map rendering The layers are stored in the reverse order of how they are re...
QgsRectangle _areaOfInterest(const QgsPoint &point, double tolerance)
void start()
static QgsGeometry * unaryUnion(const QList< QgsGeometry * > &geometryList)
Compute the unary union on a list of geometries.
iterator insert(const Key &key, const T &value)
bool contains(const Key &key) const
const_iterator constEnd() const
const_iterator constBegin() const
QgsPointLocator::Match snapToMap(QPoint point, QgsPointLocator::MatchFilter *filter=nullptr)
Snap to map according to the current configuration (mode).
bool hasIndex() const
Indicate whether the data have been already indexed.
double width() const
Width of the rectangle.
Definition: qgsrectangle.h:207
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
snap just to current layer (tolerance and type from defaultSettings())
Represents a vector layer which manages a vector based data sets.
void setDefaultSettings(int type, double tolerance, QgsTolerance::UnitType unit)
Configure options used when the mode is snap to current layer or to all layers.
QString arg(qlonglong a, int fieldWidth, int base, const QChar &fillChar) const
Match nearestVertex(const QgsPoint &point, double tolerance, MatchFilter *filter=nullptr)
Find nearest vertex to the specified point - up to distance specified by tolerance Optional filter ma...
T take(const Key &key)
void setLayers(const QList< LayerConfig > &layers)
Set layers which will be used for snapping.
double height() const
Height of the rectangle.
Definition: qgsrectangle.h:212
const T value(const Key &key) const
void defaultSettings(int &type, double &tolerance, QgsTolerance::UnitType &unit)
Query options used when the mode is snap to current layer or to all layers.
SnapToMapMode
modes for "snap to background"