QGIS API Documentation  3.21.0-Master (5b68dc587e)
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( QgsSnappingConfig::SnappingTypeFlag 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() == QgsSnappingConfig::ActiveLayer )
276  {
277  if ( !mCurrentLayer || mSnappingConfig.typeFlag() == QgsSnappingConfig::NoSnapFlag )
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() == QgsSnappingConfig::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() == QgsSnappingConfig::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();
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() == QgsSnappingConfig::ActiveLayer )
624  {
625  if ( mSnappingConfig.mode() == QgsSnappingConfig::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() == QgsSnappingConfig::AllLayers )
634  {
635  const auto constLayers = mMapSettings.layers();
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() == QgsSnappingConfig::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<QgsSnappingConfig::SnappingTypeFlag>( i->typeFlag() ) ), i->tolerance(), i->units() ) );
710  }
711  }
712 }
This class represents a coordinate reference system (CRS).
QString authid() const
Returns the authority identifier for the CRS.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:124
QgsWkbTypes::Type wkbType() const SIP_HOLDGIL
Returns type of the geometry as a WKB type (point / linestring / polygon etc.)
static QgsGeometry unaryUnion(const QVector< QgsGeometry > &geometries)
Compute the unary union on a list of geometries.
static QgsGeometry fromPolylineXY(const QgsPolylineXY &polyline)
Creates a new LineString geometry from a list of QgsPointXY points.
QgsPolylineXY asPolyline() const
Returns the contents of the geometry as a polyline.
QgsMultiPolylineXY asMultiPolyline() const
Returns the contents of the geometry as a multi-linestring.
Base class for all map layer types.
Definition: qgsmaplayer.h:73
QString id() const
Returns the layer's unique ID, which is used to access this layer from QgsProject.
The QgsMapSettings class contains configuration for rendering of the map.
double scale() const
Returns the calculated map scale.
QgsRectangle layerExtentToOutputExtent(const QgsMapLayer *layer, QgsRectangle extent) const
transform bounding box from layer's CRS to output CRS
const QgsMapToPixel & mapToPixel() const
QgsRectangle visibleExtent() const
Returns the actual extent derived from requested extent that takes output image size into account.
QList< QgsMapLayer * > layers() const
Returns the list of layers which will be rendered in the map.
bool hasValidSettings() const
Check whether the map settings are valid and can be used for rendering.
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination coordinate reference system for the map render.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context, which stores various information regarding which datum tran...
QgsPointXY toMapCoordinates(int x, int y) const
Transforms device coordinates to map (world) coordinates.
The class defines interface for querying point location:
const QgsRectangle * extent() const
Gets extent of the area point locator covers - if nullptr then it caches the whole layer.
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 ...
void setRenderContext(const QgsRenderContext *context)
Configure render context - if not nullptr, it will use to index only visible feature.
int cachedGeometryCount() const
Returns how many geometries are cached in the index.
void setExtent(const QgsRectangle *extent)
Configure extent - if not nullptr, it will index only that area.
bool init(int maxFeaturesToIndex=-1, bool relaxed=false)
Prepare the index for queries.
class QList< QgsPointLocator::Match > MatchList
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...
QgsVectorLayer * layer() const
Gets associated layer.
bool isIndexing() const
Returns true if the point locator is currently indexing the data.
MatchList edgesInRect(const QgsRectangle &rect, QgsPointLocator::MatchFilter *filter=nullptr, bool relaxed=false)
Find edges within a specified rectangle Optional filter may discard unwanted matches.
bool hasIndex() const
Indicate whether the data have been already indexed.
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...
void waitForIndexingFinished()
If the point locator has been initialized relaxedly and is currently indexing, this methods waits for...
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 ...
void initFinished(bool ok)
Emitted whenever index has been built and initialization is finished.
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 ...
Type
The type of a snap result or the filter type for a snap request.
@ Area
Snapped to an area.
@ MiddleOfSegment
Snapped to the middle of a segment.
@ Vertex
Snapped to a vertex. Can be a vertex of the geometry or an intersection.
@ Centroid
Snapped to a centroid.
@ Edge
Snapped to an edge.
@ LineEndpoint
Start or end points of lines only (since QGIS 3.20)
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...
A class to represent a 2D point.
Definition: qgspointxy.h:59
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
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:101
A rectangle specified with double values.
Definition: qgsrectangle.h:42
bool intersects(const QgsRectangle &rect) const SIP_HOLDGIL
Returns true when rectangle intersects with other rectangle.
Definition: qgsrectangle.h:349
double height() const SIP_HOLDGIL
Returns the height of the rectangle.
Definition: qgsrectangle.h:230
double width() const SIP_HOLDGIL
Returns the width of the rectangle.
Definition: qgsrectangle.h:223
bool contains(const QgsRectangle &rect) const SIP_HOLDGIL
Returns true when rectangle contains other rectangle.
Definition: qgsrectangle.h:363
Contains information about the context of a rendering operation.
static QgsRenderContext fromMapSettings(const QgsMapSettings &mapSettings)
create initialized QgsRenderContext instance from given QgsMapSettings
This is a container of advanced configuration (per layer) of the snapping of the project.
double maximumScale() const
Returns max scale on which snapping is limited.
double minimumScale() const
Returns minimum scale on which snapping is limited.
This is a container for configuration of the snapping of the project.
@ ActiveLayer
On the active layer.
@ AdvancedConfiguration
On a per layer configuration basis.
@ AllLayers
On all vector layers.
@ NoSnapFlag
No snapping.
QgsTolerance::UnitType units() const
Returns the type of units.
bool intersectionSnapping() const
Returns if the snapping on intersection is enabled.
@ PerLayer
Scale dependency using min max range per layer.
@ Disabled
No scale dependency.
@ Global
Scale dependency using global min max range.
double minimumScale() const
Returns the min scale (i.e.
double tolerance() const
Returns the tolerance.
double maximumScale() const
Returns the max scale (i.e.
SnappingMode mode() const
Returns the mode (all layers, active layer, per layer settings)
QgsSnappingConfig::SnappingTypeFlag typeFlag() const
Returns the flags type (vertices | segments | area | centroid | middle)
QHash< QgsVectorLayer *, QgsSnappingConfig::IndividualLayerSettings > individualLayerSettings() const
Returns individual snapping settings for all layers.
ScaleDependencyMode scaleDependencyMode() const
Returns the scale dependency mode.
void setEnabled(bool enabled)
enables the snapping
bool enabled() const
Returns if snapping is enabled.
QgsPointLocator::Match snapToCurrentLayer(QPoint point, QgsPointLocator::Types type, QgsPointLocator::MatchFilter *filter=nullptr)
Snap to current layer.
void setMapSettings(const QgsMapSettings &settings)
Assign current map settings to the utils - used for conversion between screen coords to map coords.
void toggleEnabled()
Toggles the state of snapping.
@ IndexAlwaysFull
For all layers build index of full extent. Uses more memory, but queries are faster.
@ IndexHybrid
For "big" layers using IndexNeverFull, for the rest IndexAlwaysFull. Compromise between speed and mem...
@ IndexExtent
For all layer build index of extent given in map settings.
@ IndexNeverFull
For all layers only create temporary indexes of small extent. Low memory usage, slower queries.
QList< QgsSnappingUtils::LayerConfig > layers() const
Query layers used for snapping.
QgsPointLocator * locatorForLayer(QgsVectorLayer *vl)
Gets a point locator for the given layer.
virtual void prepareIndexProgress(int index)
Called when finished indexing a layer with snapToMap. When index == count the indexing is complete.
QString dump()
Gets extra information about the instance.
void configChanged(const QgsSnappingConfig &snappingConfig)
Emitted when the snapping settings object changes.
~QgsSnappingUtils() override
void clearAllLocators()
Deletes all existing locators (e.g. when destination CRS has changed and we need to reindex)
QgsSnappingConfig config
void setCurrentLayer(QgsVectorLayer *layer)
Sets current layer so that if mode is SnapCurrentLayer we know which layer to use.
QgsPointLocator::Match snapToMap(QPoint point, QgsPointLocator::MatchFilter *filter=nullptr, bool relaxed=false)
Snap to map according to the current configuration.
QgsSnappingUtils(QObject *parent=nullptr, bool enableSnappingForInvisibleFeature=true)
Constructor for QgsSnappingUtils.
void setConfig(const QgsSnappingConfig &snappingConfig)
The snapping configuration controls the behavior of this object.
void setEnableSnappingForInvisibleFeature(bool enable)
Set if invisible features must be snapped or not.
virtual void prepareIndexStarting(int count)
Called when starting to index with snapToMap - can be overridden and e.g. progress dialog can be prov...
static double vertexSearchRadius(const QgsMapSettings &mapSettings)
Static function to get vertex tolerance value.
static double toleranceInProjectUnits(double tolerance, QgsMapLayer *layer, const QgsMapSettings &mapSettings, QgsTolerance::UnitType units)
Static function to translate tolerance value into map units.
Represents a vector layer which manages a vector based data sets.
Q_INVOKABLE QgsWkbTypes::GeometryType geometryType() const
Returns point, line or polygon.
long long featureCount(const QString &legendKey) const
Number of features rendered with specified legend key.
QgsRectangle extent() const FINAL
Returns the extent of the layer.
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
QVector< QgsPointXY > QgsPolylineXY
Polyline as represented as a vector of two-dimensional points.
Definition: qgsgeometry.h:51
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QgsRectangle _areaOfInterest(const QgsPointXY &point, double tolerance)
Interface that allows rejection of some matches in intersection queries (e.g.
double distance() const
for vertex / edge match units depending on what class returns it (geom.cache: layer units,...
QgsPointLocator::Type type() const
Configures how a certain layer should be handled in a snapping operation.