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