QGIS API Documentation  3.26.3-Buenos Aires (65e4edfdad)
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 #include "qgsgeometry.h"
18 #include "qgsproject.h"
19 #include "qgsvectorlayer.h"
20 #include "qgslogger.h"
21 #include "qgsrenderer.h"
22 #include "qgsrendercontext.h"
23 
24 QgsSnappingUtils::QgsSnappingUtils( QObject *parent, bool enableSnappingForInvisibleFeature )
25  : QObject( parent )
26  , mSnappingConfig( QgsProject::instance() )
27  , mEnableSnappingForInvisibleFeature( enableSnappingForInvisibleFeature )
28 {
29 }
30 
32 {
34 }
35 
36 
38 {
39  if ( !vl )
40  return nullptr;
41 
42  if ( !mLocators.contains( vl ) )
43  {
44  QgsPointLocator *vlpl = new QgsPointLocator( vl, destinationCrs(), mMapSettings.transformContext(), nullptr );
45  connect( vlpl, &QgsPointLocator::initFinished, this, &QgsSnappingUtils::onInitFinished );
46  mLocators.insert( vl, vlpl );
47  }
48  return mLocators.value( vl );
49 }
50 
52 {
53  qDeleteAll( mLocators );
54  mLocators.clear();
55 
56  qDeleteAll( mTemporaryLocators );
57  mTemporaryLocators.clear();
58 }
59 
60 
61 QgsPointLocator *QgsSnappingUtils::locatorForLayerUsingStrategy( QgsVectorLayer *vl, const QgsPointXY &pointMap, double tolerance )
62 {
63  if ( vl->geometryType() == QgsWkbTypes::NullGeometry || mStrategy == IndexNeverFull )
64  return nullptr;
65 
66  QgsRectangle aoi( pointMap.x() - tolerance, pointMap.y() - tolerance,
67  pointMap.x() + tolerance, pointMap.y() + tolerance );
68 
69  QgsPointLocator *loc = locatorForLayer( vl );
70 
71  if ( loc->isIndexing() || isIndexPrepared( loc, aoi ) )
72  return loc;
73  else
74  return temporaryLocatorForLayer( vl, pointMap, tolerance );
75 }
76 
77 QgsPointLocator *QgsSnappingUtils::temporaryLocatorForLayer( QgsVectorLayer *vl, const QgsPointXY &pointMap, double tolerance )
78 {
79  if ( mTemporaryLocators.contains( vl ) )
80  delete mTemporaryLocators.take( vl );
81 
82  QgsRectangle rect( pointMap.x() - tolerance, pointMap.y() - tolerance,
83  pointMap.x() + tolerance, pointMap.y() + tolerance );
84 
85  QgsPointLocator *vlpl = new QgsPointLocator( vl, destinationCrs(), mMapSettings.transformContext(), &rect );
86  connect( vlpl, &QgsPointLocator::initFinished, this, &QgsSnappingUtils::onInitFinished );
87 
88  mTemporaryLocators.insert( vl, vlpl );
89  return mTemporaryLocators.value( vl );
90 }
91 
92 bool QgsSnappingUtils::isIndexPrepared( QgsPointLocator *loc, const QgsRectangle &areaOfInterest )
93 {
94  if ( mStrategy == IndexAlwaysFull && loc->hasIndex() )
95  return true;
96 
97  if ( mStrategy == IndexExtent && loc->hasIndex() && ( !loc->extent() || loc->extent()->intersects( areaOfInterest ) ) )
98  return true;
99 
100  QgsRectangle aoi( areaOfInterest );
101  aoi.scale( 0.999 );
102  return mStrategy == IndexHybrid && loc->hasIndex() && ( !loc->extent() || loc->extent()->contains( aoi ) ); // the index - even if it exists - is not suitable
103 }
104 
105 static QgsPointLocator::Match _findClosestSegmentIntersection( const QgsPointXY &pt, const QgsPointLocator::MatchList &segments )
106 {
107  if ( segments.isEmpty() )
108  return QgsPointLocator::Match();
109 
110  QSet<QgsPointXY> endpoints;
111 
112  // make a geometry
113  QVector<QgsGeometry> geoms;
114  const auto constSegments = segments;
115  for ( const QgsPointLocator::Match &m : constSegments )
116  {
117  if ( m.hasEdge() )
118  {
119  QgsPolylineXY pl( 2 );
120  m.edgePoints( pl[0], pl[1] );
121  geoms << QgsGeometry::fromPolylineXY( pl );
122  endpoints << pl[0] << pl[1];
123  }
124  }
125 
127 
128  // get intersection points
129  QList<QgsPointXY> newPoints;
130  if ( g.wkbType() == QgsWkbTypes::LineString )
131  {
132  const auto constAsPolyline = g.asPolyline();
133  for ( const QgsPointXY &p : constAsPolyline )
134  {
135  if ( !endpoints.contains( p ) )
136  newPoints << p;
137  }
138  }
140  {
141  const auto constAsMultiPolyline = g.asMultiPolyline();
142  for ( const QgsPolylineXY &pl : constAsMultiPolyline )
143  {
144  const auto constPl = pl;
145  for ( const QgsPointXY &p : constPl )
146  {
147  if ( !endpoints.contains( p ) )
148  newPoints << p;
149  }
150  }
151  }
152 
153  if ( newPoints.isEmpty() )
154  return QgsPointLocator::Match();
155 
156  // find the closest points
157  QgsPointXY minP;
158  double minSqrDist = 1e20; // "infinity"
159  const auto constNewPoints = newPoints;
160  for ( const QgsPointXY &p : constNewPoints )
161  {
162  double sqrDist = pt.sqrDist( p.x(), p.y() );
163  if ( sqrDist < minSqrDist )
164  {
165  minSqrDist = sqrDist;
166  minP = p;
167  }
168  }
169 
170  return QgsPointLocator::Match( QgsPointLocator::Vertex, nullptr, 0, std::sqrt( minSqrDist ), minP );
171 }
172 
173 static void _replaceIfBetter( QgsPointLocator::Match &bestMatch, const QgsPointLocator::Match &candidateMatch, double maxDistance )
174 {
175  // is candidate match relevant?
176  if ( !candidateMatch.isValid() || candidateMatch.distance() > maxDistance )
177  return;
178 
179  // is candidate match actually better?
180  if ( bestMatch.isValid() && bestMatch.type() == candidateMatch.type() && bestMatch.distance() - 10e-6 < candidateMatch.distance() )
181  return;
182 
183  // ORDER
184  // LineEndpoint
185  // Vertex, Intersection
186  // Middle
187  // Centroid
188  // Edge
189  // Area
190 
191  // first line endpoint -- these are like vertex matches, but even more strict
192  if ( ( bestMatch.type() & QgsPointLocator::LineEndpoint ) && !( candidateMatch.type() & QgsPointLocator::LineEndpoint ) )
193  return;
194  if ( candidateMatch.type() & QgsPointLocator::LineEndpoint )
195  {
196  bestMatch = candidateMatch;
197  return;
198  }
199 
200  // Second Vertex, or intersection
201  if ( ( bestMatch.type() & QgsPointLocator::Vertex ) && !( candidateMatch.type() & QgsPointLocator::Vertex ) )
202  return;
203  if ( candidateMatch.type() & QgsPointLocator::Vertex )
204  {
205  bestMatch = candidateMatch;
206  return;
207  }
208 
209  // prefer vertex, centroid, middle matches over edge matches (even if they are closer)
210  if ( ( bestMatch.type() & QgsPointLocator::Centroid || bestMatch.type() & QgsPointLocator::MiddleOfSegment ) && ( candidateMatch.type() & QgsPointLocator::Edge || candidateMatch.type() & QgsPointLocator::Area ) )
211  return;
212 
213  // prefer middle matches over centroid matches (even if they are closer)
214  if ( ( bestMatch.type() & QgsPointLocator::MiddleOfSegment ) && ( candidateMatch.type() & QgsPointLocator::Centroid ) )
215  return;
216 
217  bestMatch = candidateMatch; // the other match is better!
218 }
219 
220 static void _updateBestMatch( QgsPointLocator::Match &bestMatch, const QgsPointXY &pointMap, QgsPointLocator *loc, QgsPointLocator::Types type, double tolerance, QgsPointLocator::MatchFilter *filter, bool relaxed )
221 {
222  if ( type & QgsPointLocator::Vertex )
223  {
224  _replaceIfBetter( bestMatch, loc->nearestVertex( pointMap, tolerance, filter, relaxed ), tolerance );
225  }
226  if ( bestMatch.type() != QgsPointLocator::Vertex && ( type & QgsPointLocator::Edge ) )
227  {
228  _replaceIfBetter( bestMatch, loc->nearestEdge( pointMap, tolerance, filter, relaxed ), tolerance );
229  }
230  if ( bestMatch.type() != QgsPointLocator::Vertex && bestMatch.type() != QgsPointLocator::Edge && ( type & QgsPointLocator::Area ) )
231  {
232  // if edges were detected, set tolerance to 0 to only do pointInPolygon (and avoid redo nearestEdge)
233  if ( type & QgsPointLocator::Edge )
234  tolerance = 0;
235  _replaceIfBetter( bestMatch, loc->nearestArea( pointMap, tolerance, filter, relaxed ), tolerance );
236  }
237  if ( type & QgsPointLocator::Centroid )
238  {
239  _replaceIfBetter( bestMatch, loc->nearestCentroid( pointMap, tolerance, filter ), tolerance );
240  }
242  {
243  _replaceIfBetter( bestMatch, loc->nearestMiddleOfSegment( pointMap, tolerance, filter ), tolerance );
244  }
245  if ( type & QgsPointLocator::LineEndpoint )
246  {
247  _replaceIfBetter( bestMatch, loc->nearestLineEndpoints( pointMap, tolerance, filter ), tolerance );
248  }
249 }
250 
251 
252 static QgsPointLocator::Types _snappingTypeToPointLocatorType( Qgis::SnappingTypes type )
253 {
254  return QgsPointLocator::Types( static_cast<int>( type ) );
255 }
256 
258 {
259  return snapToMap( mMapSettings.mapToPixel().toMapCoordinates( point ), filter, relaxed );
260 }
261 
262 inline QgsRectangle _areaOfInterest( const QgsPointXY &point, double tolerance )
263 {
264  return QgsRectangle( point.x() - tolerance, point.y() - tolerance,
265  point.x() + tolerance, point.y() + tolerance );
266 }
267 
269 {
270  if ( !mMapSettings.hasValidSettings() || !mSnappingConfig.enabled() )
271  {
272  return QgsPointLocator::Match();
273  }
274 
275  if ( mSnappingConfig.mode() == Qgis::SnappingMode::ActiveLayer )
276  {
277  if ( !mCurrentLayer || mSnappingConfig.typeFlag().testFlag( Qgis::SnappingType::NoSnap ) )
278  return QgsPointLocator::Match();
279 
280  // data from project
281  double tolerance = QgsTolerance::toleranceInProjectUnits( mSnappingConfig.tolerance(), mCurrentLayer, mMapSettings, mSnappingConfig.units() );
282  QgsPointLocator::Types type = _snappingTypeToPointLocatorType( mSnappingConfig.typeFlag() );
283 
284  prepareIndex( QList<LayerAndAreaOfInterest>() << qMakePair( mCurrentLayer, _areaOfInterest( pointMap, tolerance ) ), relaxed );
285 
286  // use ad-hoc locator
287  QgsPointLocator *loc = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
288  if ( !loc )
289  return QgsPointLocator::Match();
290 
291  QgsPointLocator::Match bestMatch;
292  QgsPointLocator::MatchList edges; // for snap on intersection
293  _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, relaxed );
294 
295  if ( mSnappingConfig.intersectionSnapping() )
296  {
297  QgsPointLocator *locEdges = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
298  if ( !locEdges )
299  return QgsPointLocator::Match();
300  edges = locEdges->edgesInRect( pointMap, tolerance );
301  }
302 
303  for ( QgsVectorLayer *vl : mExtraSnapLayers )
304  {
305  QgsPointLocator *loc = locatorForLayerUsingStrategy( vl, pointMap, tolerance );
306  _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, false );
307  if ( mSnappingConfig.intersectionSnapping() )
308  edges << loc->edgesInRect( pointMap, tolerance );
309  }
310 
311  if ( mSnappingConfig.intersectionSnapping() )
312  {
313  _replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), tolerance );
314  }
315 
316  return bestMatch;
317  }
318  else if ( mSnappingConfig.mode() == Qgis::SnappingMode::AdvancedConfiguration )
319  {
320  QList<LayerAndAreaOfInterest> layers;
321  QList<LayerConfig> filteredConfigs;
322 
323  //maximum scale is the one with smallest denominator
324  //minimum scale is the one with highest denominator
325  //So : maxscale < range on which snapping is enabled < minscale
326  bool inRangeGlobal = ( mSnappingConfig.minimumScale() <= 0.0 || mMapSettings.scale() <= mSnappingConfig.minimumScale() )
327  && ( mSnappingConfig.maximumScale() <= 0.0 || mMapSettings.scale() >= mSnappingConfig.maximumScale() );
328 
329  for ( const LayerConfig &layerConfig : std::as_const( mLayers ) )
330  {
331  QgsSnappingConfig::IndividualLayerSettings layerSettings = mSnappingConfig.individualLayerSettings( layerConfig.layer );
332 
333  bool inRangeLayer = ( layerSettings.minimumScale() <= 0.0 || mMapSettings.scale() <= layerSettings.minimumScale() )
334  && ( layerSettings.maximumScale() <= 0.0 || mMapSettings.scale() >= layerSettings.maximumScale() );
335 
336  //If limit to scale is disabled, snapping activated on all layer
337  //If no per layer config is set use the global one, otherwise use the layer config
338  if ( mSnappingConfig.scaleDependencyMode() == QgsSnappingConfig::Disabled
339  || ( mSnappingConfig.scaleDependencyMode() == QgsSnappingConfig::Global && inRangeGlobal )
340  || ( mSnappingConfig.scaleDependencyMode() == QgsSnappingConfig::PerLayer && inRangeLayer ) )
341  {
342  double tolerance = QgsTolerance::toleranceInProjectUnits( layerConfig.tolerance, layerConfig.layer, mMapSettings, layerConfig.unit );
343  layers << qMakePair( layerConfig.layer, _areaOfInterest( pointMap, tolerance ) );
344  filteredConfigs << layerConfig;
345  }
346  }
347  prepareIndex( layers, relaxed );
348 
349  QgsPointLocator::Match bestMatch;
350  QgsPointLocator::MatchList edges; // for snap on intersection
351  double maxTolerance = 0;
353 
354  for ( const LayerConfig &layerConfig : std::as_const( filteredConfigs ) )
355  {
356  double tolerance = QgsTolerance::toleranceInProjectUnits( layerConfig.tolerance, layerConfig.layer, mMapSettings, layerConfig.unit );
357  if ( QgsPointLocator *loc = locatorForLayerUsingStrategy( layerConfig.layer, pointMap, tolerance ) )
358  {
359  _updateBestMatch( bestMatch, pointMap, loc, layerConfig.type, tolerance, filter, relaxed );
360 
361  if ( mSnappingConfig.intersectionSnapping() )
362  {
363  edges << loc->edgesInRect( pointMap, tolerance );
364  }
365  // We keep the maximum tolerance for intersection snapping and extra snapping
366  maxTolerance = std::max( maxTolerance, tolerance );
367  // To avoid yet an additional setting, on extra snappings, we use the combination of all enabled snap types
368  maxTypes = static_cast<QgsPointLocator::Type>( maxTypes | layerConfig.type );
369  }
370  }
371 
372  for ( QgsVectorLayer *vl : mExtraSnapLayers )
373  {
374  QgsPointLocator *loc = locatorForLayerUsingStrategy( vl, pointMap, maxTolerance );
375  _updateBestMatch( bestMatch, pointMap, loc, maxTypes, maxTolerance, filter, false );
376  if ( mSnappingConfig.intersectionSnapping() )
377  edges << loc->edgesInRect( pointMap, maxTolerance );
378  }
379 
380  if ( mSnappingConfig.intersectionSnapping() )
381  _replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), maxTolerance );
382 
383  return bestMatch;
384  }
385  else if ( mSnappingConfig.mode() == Qgis::SnappingMode::AllLayers )
386  {
387  // data from project
388  double tolerance = QgsTolerance::toleranceInProjectUnits( mSnappingConfig.tolerance(), nullptr, mMapSettings, mSnappingConfig.units() );
389  QgsPointLocator::Types type = _snappingTypeToPointLocatorType( mSnappingConfig.typeFlag() );
390  QgsRectangle aoi = _areaOfInterest( pointMap, tolerance );
391 
392  QList<LayerAndAreaOfInterest> layers;
393  const auto constLayers = mMapSettings.layers( true );
394  for ( QgsMapLayer *layer : constLayers )
395  if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer ) )
396  layers << qMakePair( vl, aoi );
397  prepareIndex( layers, relaxed );
398 
399  QgsPointLocator::MatchList edges; // for snap on intersection
400  QgsPointLocator::Match bestMatch;
401 
402  for ( const LayerAndAreaOfInterest &entry : std::as_const( layers ) )
403  {
404  QgsVectorLayer *vl = entry.first;
405  if ( QgsPointLocator *loc = locatorForLayerUsingStrategy( vl, pointMap, tolerance ) )
406  {
407  _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, relaxed );
408 
409  if ( mSnappingConfig.intersectionSnapping() )
410  edges << loc->edgesInRect( pointMap, tolerance );
411  }
412  }
413 
414  for ( QgsVectorLayer *vl : mExtraSnapLayers )
415  {
416  QgsPointLocator *loc = locatorForLayerUsingStrategy( vl, pointMap, tolerance );
417  _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, false );
418  if ( mSnappingConfig.intersectionSnapping() )
419  edges << loc->edgesInRect( pointMap, tolerance );
420  }
421 
422  if ( mSnappingConfig.intersectionSnapping() )
423  _replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), tolerance );
424 
425  return bestMatch;
426  }
427 
428  return QgsPointLocator::Match();
429 }
430 
431 void QgsSnappingUtils::onInitFinished( bool ok )
432 {
433  QgsPointLocator *loc = static_cast<QgsPointLocator *>( sender() );
434 
435  // point locator init didn't work out - too many features!
436  // let's make the allowed area smaller for the next time
437  if ( !ok )
438  {
439  mHybridMaxAreaPerLayer[loc->layer()->id()] /= 4;
440  }
441 }
442 
443 void QgsSnappingUtils::prepareIndex( const QList<LayerAndAreaOfInterest> &layers, bool relaxed )
444 {
445  // check if we need to build any index
446  QList<LayerAndAreaOfInterest> layersToIndex;
447  const auto constLayers = layers;
448  for ( const LayerAndAreaOfInterest &entry : constLayers )
449  {
450  QgsVectorLayer *vl = entry.first;
451 
452  if ( vl->geometryType() == QgsWkbTypes::NullGeometry || mStrategy == IndexNeverFull )
453  continue;
454 
455  QgsPointLocator *loc = locatorForLayer( vl );
456 
457  if ( !loc->isIndexing() && !isIndexPrepared( loc, entry.second ) )
458  layersToIndex << entry;
459  }
460  if ( !layersToIndex.isEmpty() )
461  {
462  // build indexes
463  QElapsedTimer t;
464  int i = 0;
465 
466  if ( !relaxed )
467  {
468  t.start();
469  prepareIndexStarting( layersToIndex.count() );
470  }
471 
472  for ( const LayerAndAreaOfInterest &entry : layersToIndex )
473  {
474  QgsVectorLayer *vl = entry.first;
475  QgsPointLocator *loc = locatorForLayer( vl );
476 
477  if ( loc->isIndexing() && !relaxed )
478  {
480  }
481 
482 
483  if ( !mEnableSnappingForInvisibleFeature )
484  {
486  loc->setRenderContext( &ctx );
487  }
488 
489  if ( mStrategy == IndexExtent )
490  {
491  QgsRectangle rect( mMapSettings.visibleExtent() );
492  loc->setExtent( &rect );
493  loc->init( -1, relaxed );
494  }
495  else if ( mStrategy == IndexHybrid )
496  {
497  // first time the layer is used? - let's set an initial guess about indexing
498  if ( !mHybridMaxAreaPerLayer.contains( vl->id() ) )
499  {
500  long long totalFeatureCount = vl->featureCount();
501  if ( totalFeatureCount < mHybridPerLayerFeatureLimit )
502  {
503  // index the whole layer
504  mHybridMaxAreaPerLayer[vl->id()] = -1;
505  }
506  else
507  {
508  // estimate for how big area it probably makes sense to build partial index to not exceed the limit
509  // (we may change the limit later)
510  QgsRectangle layerExtent = mMapSettings.layerExtentToOutputExtent( vl, vl->extent() );
511  double totalArea = layerExtent.width() * layerExtent.height();
512  mHybridMaxAreaPerLayer[vl->id()] = totalArea * mHybridPerLayerFeatureLimit / totalFeatureCount / 4;
513  }
514  }
515 
516  double indexReasonableArea = mHybridMaxAreaPerLayer[vl->id()];
517  if ( indexReasonableArea == -1 )
518  {
519  // we can safely index the whole layer
520  loc->init( -1, relaxed );
521  }
522  else
523  {
524  // use area as big as we think may fit into our limit
525  QgsPointXY c = entry.second.center();
526  double halfSide = std::sqrt( indexReasonableArea ) / 2;
527  QgsRectangle rect( c.x() - halfSide, c.y() - halfSide,
528  c.x() + halfSide, c.y() + halfSide );
529  loc->setExtent( &rect );
530 
531  // see if it's possible build index for this area
532  loc->init( mHybridPerLayerFeatureLimit, relaxed );
533  }
534 
535  }
536  else // full index strategy
537  loc->init( relaxed );
538 
539  if ( !relaxed )
540  prepareIndexProgress( ++i );
541  }
542 
543  if ( !relaxed )
544  {
545  QgsDebugMsg( QStringLiteral( "Prepare index total: %1 ms" ).arg( t.elapsed() ) );
546  }
547  }
548 }
549 
551 {
552  return mSnappingConfig;
553 }
554 
556 {
557  mEnableSnappingForInvisibleFeature = enable;
558 }
559 
561 {
562  if ( mSnappingConfig == config )
563  return;
564 
565  if ( mSnappingConfig.individualLayerSettings() != config.individualLayerSettings() )
566  onIndividualLayerSettingsChanged( config.individualLayerSettings() );
567 
568  mSnappingConfig = config;
569 
570  emit configChanged( mSnappingConfig );
571 }
572 
574 {
575  mSnappingConfig.setEnabled( !mSnappingConfig.enabled() );
576  emit configChanged( mSnappingConfig );
577 }
578 
580 {
581  if ( !mCurrentLayer )
582  return QgsPointLocator::Match();
583 
584  QgsPointXY pointMap = mMapSettings.mapToPixel().toMapCoordinates( point );
585  double tolerance = QgsTolerance::vertexSearchRadius( mMapSettings );
586 
587  QgsPointLocator *loc = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
588  if ( !loc )
589  return QgsPointLocator::Match();
590 
591  QgsPointLocator::Match bestMatch;
592  _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, false );
593  return bestMatch;
594 }
595 
597 {
598  QString oldDestCRS = mMapSettings.destinationCrs().authid();
599  QString newDestCRS = settings.destinationCrs().authid();
600  mMapSettings = settings;
601 
602  if ( newDestCRS != oldDestCRS )
604 }
605 
607 {
608  mCurrentLayer = layer;
609 }
610 
612 {
613  QString msg = QStringLiteral( "--- SNAPPING UTILS DUMP ---\n" );
614 
615  if ( !mMapSettings.hasValidSettings() )
616  {
617  msg += QLatin1String( "invalid map settings!" );
618  return msg;
619  }
620 
621  QList<LayerConfig> layers;
622 
623  if ( mSnappingConfig.mode() == Qgis::SnappingMode::ActiveLayer )
624  {
625  if ( mSnappingConfig.mode() == Qgis::SnappingMode::ActiveLayer && !mCurrentLayer )
626  {
627  msg += QLatin1String( "no current layer!" );
628  return msg;
629  }
630 
631  layers << LayerConfig( mCurrentLayer, _snappingTypeToPointLocatorType( mSnappingConfig.typeFlag() ), mSnappingConfig.tolerance(), mSnappingConfig.units() );
632  }
633  else if ( mSnappingConfig.mode() == Qgis::SnappingMode::AllLayers )
634  {
635  const auto constLayers = mMapSettings.layers( true );
636  for ( QgsMapLayer *layer : constLayers )
637  {
638  if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer ) )
639  layers << LayerConfig( vl, _snappingTypeToPointLocatorType( mSnappingConfig.typeFlag() ), mSnappingConfig.tolerance(), mSnappingConfig.units() );
640  }
641  }
642  else if ( mSnappingConfig.mode() == Qgis::SnappingMode::AdvancedConfiguration )
643  {
644  layers = mLayers;
645  }
646 
647  const auto constLayers = layers;
648  for ( const LayerConfig &layer : constLayers )
649  {
650  msg += QString( "layer : %1\n"
651  "config: %2 tolerance %3 %4\n" )
652  .arg( layer.layer->name() )
653  .arg( layer.type ).arg( layer.tolerance ).arg( layer.unit );
654 
655  if ( mStrategy == IndexAlwaysFull || mStrategy == IndexHybrid || mStrategy == IndexExtent )
656  {
657  if ( QgsPointLocator *loc = locatorForLayer( layer.layer ) )
658  {
659  QString extentStr, cachedGeoms, limit( QStringLiteral( "no max area" ) );
660  if ( const QgsRectangle *r = loc->extent() )
661  {
662  extentStr = QStringLiteral( " extent %1" ).arg( r->toString() );
663  }
664  else
665  extentStr = QStringLiteral( "full extent" );
666  if ( loc->hasIndex() )
667  cachedGeoms = QStringLiteral( "%1 feats" ).arg( loc->cachedGeometryCount() );
668  else
669  cachedGeoms = QStringLiteral( "not initialized" );
670  if ( mStrategy == IndexHybrid )
671  {
672  if ( mHybridMaxAreaPerLayer.contains( layer.layer->id() ) )
673  {
674  double maxArea = mStrategy == IndexHybrid ? mHybridMaxAreaPerLayer[layer.layer->id()] : -1;
675  if ( maxArea != -1 )
676  limit = QStringLiteral( "max area %1" ).arg( maxArea );
677  }
678  else
679  limit = QStringLiteral( "not evaluated" );
680  }
681  msg += QStringLiteral( "index : YES | %1 | %2 | %3\n" ).arg( cachedGeoms, extentStr, limit );
682  }
683  else
684  msg += QLatin1String( "index : ???\n" ); // should not happen
685  }
686  else
687  msg += QLatin1String( "index : NO\n" );
688  msg += QLatin1String( "-\n" );
689  }
690 
691  return msg;
692 }
693 
694 QgsCoordinateReferenceSystem QgsSnappingUtils::destinationCrs() const
695 {
696  return mMapSettings.destinationCrs();
697 }
698 
699 void QgsSnappingUtils::onIndividualLayerSettingsChanged( const QHash<QgsVectorLayer *, QgsSnappingConfig::IndividualLayerSettings> &layerSettings )
700 {
701  mLayers.clear();
702 
703  QHash<QgsVectorLayer *, QgsSnappingConfig::IndividualLayerSettings>::const_iterator i;
704 
705  for ( i = layerSettings.constBegin(); i != layerSettings.constEnd(); ++i )
706  {
707  if ( i->enabled() )
708  {
709  mLayers.append( LayerConfig( i.key(), _snappingTypeToPointLocatorType( static_cast<Qgis::SnappingTypes>( i->typeFlag() ) ), i->tolerance(), i->units() ) );
710  }
711  }
712 }
QgsRectangle::intersects
bool intersects(const QgsRectangle &rect) const SIP_HOLDGIL
Returns true when rectangle intersects with other rectangle.
Definition: qgsrectangle.h:349
Qgis::SnappingMode::AdvancedConfiguration
@ AdvancedConfiguration
On a per layer configuration basis.
QgsRectangle::height
double height() const SIP_HOLDGIL
Returns the height of the rectangle.
Definition: qgsrectangle.h:230
QgsPointXY::y
double y
Definition: qgspointxy.h:63
QgsSnappingUtils::~QgsSnappingUtils
~QgsSnappingUtils() override
Definition: qgssnappingutils.cpp:31
QgsWkbTypes::NullGeometry
@ NullGeometry
Definition: qgswkbtypes.h:146
QgsRenderContext::fromMapSettings
static QgsRenderContext fromMapSettings(const QgsMapSettings &mapSettings)
create initialized QgsRenderContext instance from given QgsMapSettings
Definition: qgsrendercontext.cpp:234
QgsGeometry::fromPolylineXY
static QgsGeometry fromPolylineXY(const QgsPolylineXY &polyline)
Creates a new LineString geometry from a list of QgsPointXY points.
Definition: qgsgeometry.cpp:186
QgsSnappingConfig::Global
@ Global
Scale dependency using global min max range.
Definition: qgssnappingconfig.h:66
QgsSnappingUtils::IndexExtent
@ IndexExtent
For all layer build index of extent given in map settings.
Definition: qgssnappingutils.h:111
QgsPointLocator::isIndexing
bool isIndexing() const
Returns true if the point locator is currently indexing the data.
Definition: qgspointlocator.h:456
QgsPointLocator::setRenderContext
void setRenderContext(const QgsRenderContext *context)
Configure render context - if not nullptr, it will use to index only visible feature.
Definition: qgspointlocator.cpp:914
QgsVectorLayer::featureCount
long long featureCount(const QString &legendKey) const
Number of features rendered with specified legend key.
Definition: qgsvectorlayer.cpp:812
QgsPointLocator::Invalid
@ Invalid
Invalid.
Definition: qgspointlocator.h:157
QgsPointLocator::nearestArea
Match nearestArea(const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter=nullptr, bool relaxed=false)
Find nearest area to the specified point - up to distance specified by tolerance Optional filter may ...
Definition: qgspointlocator.cpp:1398
QgsSnappingUtils::snapToCurrentLayer
QgsPointLocator::Match snapToCurrentLayer(QPoint point, QgsPointLocator::Types type, QgsPointLocator::MatchFilter *filter=nullptr)
Snap to current layer.
Definition: qgssnappingutils.cpp:579
QgsPolylineXY
QVector< QgsPointXY > QgsPolylineXY
Polyline as represented as a vector of two-dimensional points.
Definition: qgsgeometry.h:52
QgsWkbTypes::LineString
@ LineString
Definition: qgswkbtypes.h:73
QgsSnappingUtils::config
QgsSnappingConfig config
Definition: qgssnappingutils.h:54
QgsMapSettings::hasValidSettings
bool hasValidSettings() const
Check whether the map settings are valid and can be used for rendering.
Definition: qgsmapsettings.cpp:406
QgsRenderContext
Contains information about the context of a rendering operation.
Definition: qgsrendercontext.h:59
QgsMapSettings::layerExtentToOutputExtent
QgsRectangle layerExtentToOutputExtent(const QgsMapLayer *layer, QgsRectangle extent) const
transform bounding box from layer's CRS to output CRS
Definition: qgsmapsettings.cpp:528
QgsPointLocator::cachedGeometryCount
int cachedGeometryCount() const
Returns how many geometries are cached in the index.
Definition: qgspointlocator.h:448
QgsSnappingUtils::setCurrentLayer
void setCurrentLayer(QgsVectorLayer *layer)
Sets current layer so that if mode is SnapCurrentLayer we know which layer to use.
Definition: qgssnappingutils.cpp:606
QgsDebugMsg
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QgsMapToPixel::toMapCoordinates
QgsPointXY toMapCoordinates(int x, int y) const
Transforms device coordinates to map (world) coordinates.
Definition: qgsmaptopixel.h:173
QgsPointLocator::edgesInRect
MatchList edgesInRect(const QgsRectangle &rect, QgsPointLocator::MatchFilter *filter=nullptr, bool relaxed=false)
Find edges within a specified rectangle Optional filter may discard unwanted matches.
Definition: qgspointlocator.cpp:1428
QgsRectangle
A rectangle specified with double values.
Definition: qgsrectangle.h:41
QgsGeometry::asMultiPolyline
QgsMultiPolylineXY asMultiPolyline() const
Returns the contents of the geometry as a multi-linestring.
Definition: qgsgeometry.cpp:1783
QgsProject
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:103
QgsSnappingUtils::clearAllLocators
void clearAllLocators()
Deletes all existing locators (e.g. when destination CRS has changed and we need to reindex)
Definition: qgssnappingutils.cpp:51
QgsPointLocator::init
bool init(int maxFeaturesToIndex=-1, bool relaxed=false)
Prepare the index for queries.
Definition: qgspointlocator.cpp:961
QgsPointLocator::nearestVertex
Match nearestVertex(const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter=nullptr, bool relaxed=false)
Find nearest vertex to the specified point - up to distance specified by tolerance Optional filter ma...
Definition: qgspointlocator.cpp:1321
QgsPointXY::sqrDist
double sqrDist(double x, double y) const SIP_HOLDGIL
Returns the squared distance between this point a specified x, y coordinate.
Definition: qgspointxy.h:190
QgsSnappingUtils::IndexHybrid
@ IndexHybrid
For "big" layers using IndexNeverFull, for the rest IndexAlwaysFull. Compromise between speed and mem...
Definition: qgssnappingutils.h:110
QgsSnappingUtils::snapToMap
QgsPointLocator::Match snapToMap(QPoint point, QgsPointLocator::MatchFilter *filter=nullptr, bool relaxed=false)
Snap to map according to the current configuration.
Definition: qgssnappingutils.cpp:257
QgsSnappingUtils::QgsSnappingUtils
QgsSnappingUtils(QObject *parent=nullptr, bool enableSnappingForInvisibleFeature=true)
Constructor for QgsSnappingUtils.
Definition: qgssnappingutils.cpp:24
QgsWkbTypes::MultiLineString
@ MultiLineString
Definition: qgswkbtypes.h:77
QgsSnappingConfig::minimumScale
double minimumScale() const
Returns the min scale (i.e.
Definition: qgssnappingconfig.cpp:710
QgsSnappingConfig::tolerance
double tolerance() const
Returns the tolerance.
Definition: qgssnappingconfig.cpp:348
QgsMapSettings::transformContext
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context, which stores various information regarding which datum tran...
Definition: qgsmapsettings.cpp:463
QgsSnappingConfig::PerLayer
@ PerLayer
Scale dependency using min max range per layer.
Definition: qgssnappingconfig.h:67
QgsSnappingUtils::toggleEnabled
void toggleEnabled()
Toggles the state of snapping.
Definition: qgssnappingutils.cpp:573
QgsSnappingUtils::configChanged
void configChanged(const QgsSnappingConfig &snappingConfig)
Emitted when the snapping settings object changes.
QgsPointLocator::MatchList
class QList< QgsPointLocator::Match > MatchList
Definition: qgspointlocator.h:339
QgsSnappingUtils::dump
QString dump()
Gets extra information about the instance.
Definition: qgssnappingutils.cpp:611
QgsSnappingUtils::prepareIndexStarting
virtual void prepareIndexStarting(int count)
Called when starting to index with snapToMap - can be overridden and e.g. progress dialog can be prov...
Definition: qgssnappingutils.h:258
_areaOfInterest
QgsRectangle _areaOfInterest(const QgsPointXY &point, double tolerance)
Definition: qgssnappingutils.cpp:262
QgsSnappingConfig::scaleDependencyMode
ScaleDependencyMode scaleDependencyMode() const
Returns the scale dependency mode.
Definition: qgssnappingconfig.cpp:735
QgsSnappingUtils::IndexNeverFull
@ IndexNeverFull
For all layers only create temporary indexes of small extent. Low memory usage, slower queries.
Definition: qgssnappingutils.h:109
QgsPointLocator::nearestLineEndpoints
Match nearestLineEndpoints(const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter=nullptr, bool relaxed=false)
Find nearest line endpoint (start or end vertex) to the specified point - up to distance specified by...
Definition: qgspointlocator.cpp:1365
QgsPointLocator::nearestMiddleOfSegment
Match nearestMiddleOfSegment(const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter=nullptr, bool relaxed=false)
Find nearest middle of segment to the specified point - up to distance specified by tolerance Optiona...
Definition: qgspointlocator.cpp:1350
QgsPointLocator::hasIndex
bool hasIndex() const
Indicate whether the data have been already indexed.
Definition: qgspointlocator.cpp:1010
QgsRectangle::contains
bool contains(const QgsRectangle &rect) const SIP_HOLDGIL
Returns true when rectangle contains other rectangle.
Definition: qgsrectangle.h:363
QgsTolerance::vertexSearchRadius
static double vertexSearchRadius(const QgsMapSettings &mapSettings)
Static function to get vertex tolerance value.
Definition: qgstolerance.cpp:73
QgsPointLocator::LineEndpoint
@ LineEndpoint
Start or end points of lines only (since QGIS 3.20)
Definition: qgspointlocator.h:163
QgsSnappingConfig::typeFlag
Qgis::SnappingTypes typeFlag() const
Returns the flags type (vertices | segments | area | centroid | middle)
Definition: qgssnappingconfig.cpp:262
qgsrendercontext.h
QgsPointLocator::Match::isValid
bool isValid() const
Definition: qgspointlocator.h:209
QgsPointLocator::setExtent
void setExtent(const QgsRectangle *extent)
Configure extent - if not nullptr, it will index only that area.
Definition: qgspointlocator.cpp:903
QgsPointLocator::Centroid
@ Centroid
Snapped to a centroid.
Definition: qgspointlocator.h:161
QgsMapLayer::id
QString id() const
Returns the layer's unique ID, which is used to access this layer from QgsProject.
Definition: qgsmaplayer.cpp:169
QgsSnappingConfig::setEnabled
void setEnabled(bool enabled)
enables the snapping
Definition: qgssnappingconfig.cpp:239
QgsSnappingConfig
This is a container for configuration of the snapping of the project.
Definition: qgssnappingconfig.h:37
QgsMapSettings::scale
double scale() const
Returns the calculated map scale.
Definition: qgsmapsettings.cpp:458
QgsPointLocator::extent
const QgsRectangle * extent() const
Gets extent of the area point locator covers - if nullptr then it caches the whole layer.
Definition: qgspointlocator.h:138
qgsrenderer.h
Qgis::SnappingMode::AllLayers
@ AllLayers
On all vector layers.
QgsSnappingUtils::IndexAlwaysFull
@ IndexAlwaysFull
For all layers build index of full extent. Uses more memory, but queries are faster.
Definition: qgssnappingutils.h:108
QgsSnappingConfig::IndividualLayerSettings
This is a container of advanced configuration (per layer) of the snapping of the project.
Definition: qgssnappingconfig.h:107
QgsCoordinateReferenceSystem
This class represents a coordinate reference system (CRS).
Definition: qgscoordinatereferencesystem.h:211
QgsPointLocator::Vertex
@ Vertex
Snapped to a vertex. Can be a vertex of the geometry or an intersection.
Definition: qgspointlocator.h:158
QgsVectorLayer::extent
QgsRectangle extent() const FINAL
Returns the extent of the layer.
Definition: qgsvectorlayer.cpp:894
QgsPointLocator::Match
Definition: qgspointlocator.h:187
qgsvectorlayer.h
QgsPointXY
A class to represent a 2D point.
Definition: qgspointxy.h:58
QgsMapSettings::destinationCrs
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination coordinate reference system for the map render.
Definition: qgsmapsettings.cpp:358
QgsSnappingConfig::units
QgsTolerance::UnitType units() const
Returns the type of units.
Definition: qgssnappingconfig.cpp:362
QgsSnappingUtils::layers
QList< QgsSnappingUtils::LayerConfig > layers() const
Query layers used for snapping.
Definition: qgssnappingutils.h:170
qgsgeometry.h
c
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
Definition: porting_processing.dox:1
QgsGeometry
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:124
QgsPointLocator::Edge
@ Edge
Snapped to an edge.
Definition: qgspointlocator.h:159
QgsSnappingConfig::individualLayerSettings
QHash< QgsVectorLayer *, QgsSnappingConfig::IndividualLayerSettings > individualLayerSettings() const
Returns individual snapping settings for all layers.
Definition: qgssnappingconfig.cpp:396
QgsVectorLayer
Represents a vector layer which manages a vector based data sets.
Definition: qgsvectorlayer.h:391
QgsRectangle::width
double width() const SIP_HOLDGIL
Returns the width of the rectangle.
Definition: qgsrectangle.h:223
QgsMapLayer
Base class for all map layer types. This is the base class for all map layer types (vector,...
Definition: qgsmaplayer.h:72
QgsSnappingUtils::setEnableSnappingForInvisibleFeature
void setEnableSnappingForInvisibleFeature(bool enable)
Set if invisible features must be snapped or not.
Definition: qgssnappingutils.cpp:555
QgsPointLocator::Match::type
QgsPointLocator::Type type() const
Definition: qgspointlocator.h:207
QgsPointXY::x
double x
Definition: qgspointxy.h:62
QgsSnappingUtils::locatorForLayer
QgsPointLocator * locatorForLayer(QgsVectorLayer *vl)
Gets a point locator for the given layer.
Definition: qgssnappingutils.cpp:37
QgsSnappingConfig::mode
Qgis::SnappingMode mode() const
Returns the mode (all layers, active layer, per layer settings)
Definition: qgssnappingconfig.cpp:248
QgsSnappingConfig::intersectionSnapping
bool intersectionSnapping() const
Returns if the snapping on intersection is enabled.
Definition: qgssnappingconfig.cpp:376
QgsPointLocator
The class defines interface for querying point location:
Definition: qgspointlocator.h:101
QgsSnappingConfig::maximumScale
double maximumScale() const
Returns the max scale (i.e.
Definition: qgssnappingconfig.cpp:720
QgsSnappingConfig::IndividualLayerSettings::minimumScale
double minimumScale() const
Returns minimum scale on which snapping is limited.
Definition: qgssnappingconfig.cpp:125
QgsGeometry::asPolyline
QgsPolylineXY asPolyline() const
Returns the contents of the geometry as a polyline.
Definition: qgsgeometry.cpp:1678
QgsPointLocator::layer
QgsVectorLayer * layer() const
Gets associated layer.
Definition: qgspointlocator.h:126
QgsPointLocator::nearestCentroid
Match nearestCentroid(const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter=nullptr, bool relaxed=false)
Find nearest centroid to the specified point - up to distance specified by tolerance Optional filter ...
Definition: qgspointlocator.cpp:1335
QgsCoordinateReferenceSystem::authid
QString authid
Definition: qgscoordinatereferencesystem.h:217
QgsPointLocator::MatchFilter
Interface that allows rejection of some matches in intersection queries (e.g.
Definition: qgspointlocator.h:349
QgsSnappingUtils::setMapSettings
void setMapSettings(const QgsMapSettings &settings)
Assign current map settings to the utils - used for conversion between screen coords to map coords.
Definition: qgssnappingutils.cpp:596
QgsPointLocator::nearestEdge
Match nearestEdge(const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter=nullptr, bool relaxed=false)
Find nearest edge to the specified point - up to distance specified by tolerance Optional filter may ...
Definition: qgspointlocator.cpp:1380
QgsSnappingConfig::Disabled
@ Disabled
No scale dependency.
Definition: qgssnappingconfig.h:65
QgsPointLocator::MiddleOfSegment
@ MiddleOfSegment
Snapped to the middle of a segment.
Definition: qgspointlocator.h:162
qgslogger.h
Qgis::SnappingMode::ActiveLayer
@ ActiveLayer
On the active layer.
QgsPointLocator::Type
Type
The type of a snap result or the filter type for a snap request.
Definition: qgspointlocator.h:155
QgsTolerance::toleranceInProjectUnits
static double toleranceInProjectUnits(double tolerance, QgsMapLayer *layer, const QgsMapSettings &mapSettings, QgsTolerance::UnitType units)
Static function to translate tolerance value into map units.
Definition: qgstolerance.cpp:39
QgsMapSettings
The QgsMapSettings class contains configuration for rendering of the map. The rendering itself is don...
Definition: qgsmapsettings.h:88
QgsMapSettings::visibleExtent
QgsRectangle visibleExtent() const
Returns the actual extent derived from requested extent that takes output image size into account.
Definition: qgsmapsettings.cpp:411
QgsSnappingConfig::IndividualLayerSettings::maximumScale
double maximumScale() const
Returns max scale on which snapping is limited.
Definition: qgssnappingconfig.cpp:135
QgsPointLocator::initFinished
void initFinished(bool ok)
Emitted whenever index has been built and initialization is finished.
QgsVectorLayer::geometryType
Q_INVOKABLE QgsWkbTypes::GeometryType geometryType() const
Returns point, line or polygon.
Definition: qgsvectorlayer.cpp:720
QgsSnappingUtils::LayerConfig
Configures how a certain layer should be handled in a snapping operation.
Definition: qgssnappingutils.h:122
QgsMapSettings::mapToPixel
const QgsMapToPixel & mapToPixel() const
Definition: qgsmapsettings.h:527
qgsproject.h
QgsPointLocator::waitForIndexingFinished
void waitForIndexingFinished()
If the point locator has been initialized relaxedly and is currently indexing, this methods waits for...
Definition: qgspointlocator.cpp:1000
QgsMapSettings::layers
QList< QgsMapLayer * > layers(bool expandGroupLayers=false) const
Returns the list of layers which will be rendered in the map.
Definition: qgsmapsettings.cpp:299
QgsGeometry::wkbType
QgsWkbTypes::Type wkbType() const SIP_HOLDGIL
Returns type of the geometry as a WKB type (point / linestring / polygon etc.)
Definition: qgsgeometry.cpp:357
QgsSnappingUtils::setConfig
void setConfig(const QgsSnappingConfig &snappingConfig)
The snapping configuration controls the behavior of this object.
Definition: qgssnappingutils.cpp:560
QgsSnappingConfig::enabled
bool enabled() const
Returns if snapping is enabled.
Definition: qgssnappingconfig.cpp:234
QgsGeometry::unaryUnion
static QgsGeometry unaryUnion(const QVector< QgsGeometry > &geometries)
Compute the unary union on a list of geometries.
Definition: qgsgeometry.cpp:3079
qgssnappingutils.h
QgsPointLocator::Area
@ Area
Snapped to an area.
Definition: qgspointlocator.h:160
QgsPointLocator::Match::distance
double distance() const
for vertex / edge match units depending on what class returns it (geom.cache: layer units,...
Definition: qgspointlocator.h:232
QgsSnappingUtils::prepareIndexProgress
virtual void prepareIndexProgress(int index)
Called when finished indexing a layer with snapToMap. When index == count the indexing is complete.
Definition: qgssnappingutils.h:260