QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
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 "qgsrendercontext.h"
22
23QgsSnappingUtils::QgsSnappingUtils( QObject *parent, bool enableSnappingForInvisibleFeature )
24 : QObject( parent )
25 , mSnappingConfig( QgsProject::instance() )
26 , mEnableSnappingForInvisibleFeature( enableSnappingForInvisibleFeature )
27{
28}
29
31{
33}
34
35
37{
38 if ( !vl )
39 return nullptr;
40
41 if ( !mLocators.contains( vl ) )
42 {
43 QgsPointLocator *vlpl = new QgsPointLocator( vl, destinationCrs(), mMapSettings.transformContext(), nullptr );
44 connect( vlpl, &QgsPointLocator::initFinished, this, &QgsSnappingUtils::onInitFinished );
45 mLocators.insert( vl, vlpl );
46 }
47 return mLocators.value( vl );
48}
49
51{
52 qDeleteAll( mLocators );
53 mLocators.clear();
54
55 qDeleteAll( mTemporaryLocators );
56 mTemporaryLocators.clear();
57}
58
59
60QgsPointLocator *QgsSnappingUtils::locatorForLayerUsingStrategy( QgsVectorLayer *vl, const QgsPointXY &pointMap, double tolerance )
61{
62 if ( vl->geometryType() == Qgis::GeometryType::Null || mStrategy == IndexNeverFull )
63 return nullptr;
64
65 QgsRectangle aoi( pointMap.x() - tolerance, pointMap.y() - tolerance,
66 pointMap.x() + tolerance, pointMap.y() + tolerance );
67
69
70 if ( loc->isIndexing() || isIndexPrepared( loc, aoi ) )
71 return loc;
72 else
73 return temporaryLocatorForLayer( vl, pointMap, tolerance );
74}
75
76QgsPointLocator *QgsSnappingUtils::temporaryLocatorForLayer( QgsVectorLayer *vl, const QgsPointXY &pointMap, double tolerance )
77{
78 if ( mTemporaryLocators.contains( vl ) )
79 delete mTemporaryLocators.take( vl );
80
81 QgsRectangle rect( pointMap.x() - tolerance, pointMap.y() - tolerance,
82 pointMap.x() + tolerance, pointMap.y() + tolerance );
83
84 QgsPointLocator *vlpl = new QgsPointLocator( vl, destinationCrs(), mMapSettings.transformContext(), &rect );
85 connect( vlpl, &QgsPointLocator::initFinished, this, &QgsSnappingUtils::onInitFinished );
86
87 mTemporaryLocators.insert( vl, vlpl );
88 return mTemporaryLocators.value( vl );
89}
90
91bool QgsSnappingUtils::isIndexPrepared( QgsPointLocator *loc, const QgsRectangle &areaOfInterest )
92{
93 if ( mStrategy == IndexAlwaysFull && loc->hasIndex() )
94 return true;
95
96 if ( mStrategy == IndexExtent && loc->hasIndex() && ( !loc->extent() || loc->extent()->intersects( areaOfInterest ) ) )
97 return true;
98
99 QgsRectangle aoi( areaOfInterest );
100 aoi.scale( 0.999 );
101 return mStrategy == IndexHybrid && loc->hasIndex() && ( !loc->extent() || loc->extent()->contains( aoi ) ); // the index - even if it exists - is not suitable
102}
103
104static QgsPointLocator::Match _findClosestSegmentIntersection( const QgsPointXY &pt, const QgsPointLocator::MatchList &segments )
105{
106 if ( segments.isEmpty() )
107 return QgsPointLocator::Match();
108
109 QSet<QgsPointXY> endpoints;
110
111 // make a geometry
112 QVector<QgsGeometry> geoms;
113 const auto constSegments = segments;
114 for ( const QgsPointLocator::Match &m : constSegments )
115 {
116 if ( m.hasEdge() )
117 {
118 QgsPolylineXY pl( 2 );
119 m.edgePoints( pl[0], pl[1] );
120 geoms << QgsGeometry::fromPolylineXY( pl );
121 endpoints << pl[0] << pl[1];
122 }
123 }
124
126
127 // get intersection points
128 QList<QgsPointXY> newPoints;
130 {
131 const auto constAsPolyline = g.asPolyline();
132 for ( const QgsPointXY &p : constAsPolyline )
133 {
134 if ( !endpoints.contains( p ) )
135 newPoints << p;
136 }
137 }
139 {
140 const auto constAsMultiPolyline = g.asMultiPolyline();
141 for ( const QgsPolylineXY &pl : constAsMultiPolyline )
142 {
143 const auto constPl = pl;
144 for ( const QgsPointXY &p : constPl )
145 {
146 if ( !endpoints.contains( p ) )
147 newPoints << p;
148 }
149 }
150 }
151
152 if ( newPoints.isEmpty() )
153 return QgsPointLocator::Match();
154
155 // find the closest points
156 QgsPointXY minP;
157 double minSqrDist = 1e20; // "infinity"
158 const auto constNewPoints = newPoints;
159 for ( const QgsPointXY &p : constNewPoints )
160 {
161 double sqrDist = pt.sqrDist( p.x(), p.y() );
162 if ( sqrDist < minSqrDist )
163 {
164 minSqrDist = sqrDist;
165 minP = p;
166 }
167 }
168
169 return QgsPointLocator::Match( QgsPointLocator::Vertex, nullptr, 0, std::sqrt( minSqrDist ), minP );
170}
171
172static void _replaceIfBetter( QgsPointLocator::Match &bestMatch, const QgsPointLocator::Match &candidateMatch, double maxDistance )
173{
174 // is candidate match relevant?
175 if ( !candidateMatch.isValid() || candidateMatch.distance() > maxDistance )
176 return;
177
178 // is candidate match actually better?
179 if ( bestMatch.isValid() && bestMatch.type() == candidateMatch.type() && bestMatch.distance() - 10e-6 < candidateMatch.distance() )
180 return;
181
182 // ORDER
183 // LineEndpoint
184 // Vertex, Intersection
185 // Middle
186 // Centroid
187 // Edge
188 // Area
189
190 // first line endpoint -- these are like vertex matches, but even more strict
191 if ( ( bestMatch.type() & QgsPointLocator::LineEndpoint ) && !( candidateMatch.type() & QgsPointLocator::LineEndpoint ) )
192 return;
193 if ( candidateMatch.type() & QgsPointLocator::LineEndpoint )
194 {
195 bestMatch = candidateMatch;
196 return;
197 }
198
199 // Second Vertex, or intersection
200 if ( ( bestMatch.type() & QgsPointLocator::Vertex ) && !( candidateMatch.type() & QgsPointLocator::Vertex ) )
201 return;
202 if ( candidateMatch.type() & QgsPointLocator::Vertex )
203 {
204 bestMatch = candidateMatch;
205 return;
206 }
207
208 // prefer vertex, centroid, middle matches over edge matches (even if they are closer)
209 if ( ( bestMatch.type() & QgsPointLocator::Centroid || bestMatch.type() & QgsPointLocator::MiddleOfSegment ) && ( candidateMatch.type() & QgsPointLocator::Edge || candidateMatch.type() & QgsPointLocator::Area ) )
210 return;
211
212 // prefer middle matches over centroid matches (even if they are closer)
213 if ( ( bestMatch.type() & QgsPointLocator::MiddleOfSegment ) && ( candidateMatch.type() & QgsPointLocator::Centroid ) )
214 return;
215
216 bestMatch = candidateMatch; // the other match is better!
217}
218
219static void _updateBestMatch( QgsPointLocator::Match &bestMatch, const QgsPointXY &pointMap, QgsPointLocator *loc, QgsPointLocator::Types type, double tolerance, QgsPointLocator::MatchFilter *filter, bool relaxed )
220{
221 if ( type & QgsPointLocator::Vertex )
222 {
223 _replaceIfBetter( bestMatch, loc->nearestVertex( pointMap, tolerance, filter, relaxed ), tolerance );
224 }
225 if ( bestMatch.type() != QgsPointLocator::Vertex && ( type & QgsPointLocator::Edge ) )
226 {
227 _replaceIfBetter( bestMatch, loc->nearestEdge( pointMap, tolerance, filter, relaxed ), tolerance );
228 }
229 if ( bestMatch.type() != QgsPointLocator::Vertex && bestMatch.type() != QgsPointLocator::Edge && ( type & QgsPointLocator::Area ) )
230 {
231 // if edges were detected, set tolerance to 0 to only do pointInPolygon (and avoid redo nearestEdge)
232 if ( type & QgsPointLocator::Edge )
233 tolerance = 0;
234 _replaceIfBetter( bestMatch, loc->nearestArea( pointMap, tolerance, filter, relaxed ), tolerance );
235 }
236 if ( type & QgsPointLocator::Centroid )
237 {
238 _replaceIfBetter( bestMatch, loc->nearestCentroid( pointMap, tolerance, filter, relaxed ), tolerance );
239 }
241 {
242 _replaceIfBetter( bestMatch, loc->nearestMiddleOfSegment( pointMap, tolerance, filter, relaxed ), tolerance );
243 }
245 {
246 _replaceIfBetter( bestMatch, loc->nearestLineEndpoints( pointMap, tolerance, filter, relaxed ), tolerance );
247 }
248}
249
250
251static QgsPointLocator::Types _snappingTypeToPointLocatorType( Qgis::SnappingTypes type )
252{
253 return QgsPointLocator::Types( static_cast<int>( type ) );
254}
255
257{
258 return snapToMap( mMapSettings.mapToPixel().toMapCoordinates( point ), filter, relaxed );
259}
260
261inline QgsRectangle _areaOfInterest( const QgsPointXY &point, double tolerance )
262{
263 return QgsRectangle( point.x() - tolerance, point.y() - tolerance,
264 point.x() + tolerance, point.y() + tolerance );
265}
266
268{
269 if ( !mMapSettings.hasValidSettings() || !mSnappingConfig.enabled() )
270 {
271 return QgsPointLocator::Match();
272 }
273
274 if ( mSnappingConfig.mode() == Qgis::SnappingMode::ActiveLayer )
275 {
276 if ( !mCurrentLayer || mSnappingConfig.typeFlag().testFlag( Qgis::SnappingType::NoSnap ) )
277 return QgsPointLocator::Match();
278
279 // data from project
280 double tolerance = QgsTolerance::toleranceInProjectUnits( mSnappingConfig.tolerance(), mCurrentLayer, mMapSettings, mSnappingConfig.units() );
281 QgsPointLocator::Types type = _snappingTypeToPointLocatorType( mSnappingConfig.typeFlag() );
282
283 prepareIndex( QList<LayerAndAreaOfInterest>() << qMakePair( mCurrentLayer, _areaOfInterest( pointMap, tolerance ) ), relaxed );
284
285 // use ad-hoc locator
286 QgsPointLocator *loc = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
287 if ( !loc )
288 return QgsPointLocator::Match();
289
290 QgsPointLocator::Match bestMatch;
291 QgsPointLocator::MatchList edges; // for snap on intersection
292 _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, relaxed );
293
294 if ( mSnappingConfig.intersectionSnapping() )
295 {
296 QgsPointLocator *locEdges = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
297 if ( !locEdges )
298 return QgsPointLocator::Match();
299 edges = locEdges->edgesInRect( pointMap, tolerance );
300 }
301
302 for ( QgsVectorLayer *vl : mExtraSnapLayers )
303 {
304 QgsPointLocator *loc = locatorForLayerUsingStrategy( vl, pointMap, tolerance );
305 _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, false );
306 if ( mSnappingConfig.intersectionSnapping() )
307 edges << loc->edgesInRect( pointMap, tolerance );
308 }
309
310 if ( mSnappingConfig.intersectionSnapping() )
311 {
312 _replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), tolerance );
313 }
314
315 return bestMatch;
316 }
317 else if ( mSnappingConfig.mode() == Qgis::SnappingMode::AdvancedConfiguration )
318 {
319 QList<LayerAndAreaOfInterest> layers;
320 QList<LayerConfig> filteredConfigs;
321
322 //maximum scale is the one with smallest denominator
323 //minimum scale is the one with highest denominator
324 //So : maxscale < range on which snapping is enabled < minscale
325 bool inRangeGlobal = ( mSnappingConfig.minimumScale() <= 0.0 || mMapSettings.scale() <= mSnappingConfig.minimumScale() )
326 && ( mSnappingConfig.maximumScale() <= 0.0 || mMapSettings.scale() >= mSnappingConfig.maximumScale() );
327
328 for ( const LayerConfig &layerConfig : std::as_const( mLayers ) )
329 {
330 QgsSnappingConfig::IndividualLayerSettings layerSettings = mSnappingConfig.individualLayerSettings( layerConfig.layer );
331
332 bool inRangeLayer = ( layerSettings.minimumScale() <= 0.0 || mMapSettings.scale() <= layerSettings.minimumScale() )
333 && ( layerSettings.maximumScale() <= 0.0 || mMapSettings.scale() >= layerSettings.maximumScale() );
334
335 //If limit to scale is disabled, snapping activated on all layer
336 //If no per layer config is set use the global one, otherwise use the layer config
337 if ( mSnappingConfig.scaleDependencyMode() == QgsSnappingConfig::Disabled
338 || ( mSnappingConfig.scaleDependencyMode() == QgsSnappingConfig::Global && inRangeGlobal )
339 || ( mSnappingConfig.scaleDependencyMode() == QgsSnappingConfig::PerLayer && inRangeLayer ) )
340 {
341 double tolerance = QgsTolerance::toleranceInProjectUnits( layerConfig.tolerance, layerConfig.layer, mMapSettings, layerConfig.unit );
342 layers << qMakePair( layerConfig.layer, _areaOfInterest( pointMap, tolerance ) );
343 filteredConfigs << layerConfig;
344 }
345 }
346 prepareIndex( layers, relaxed );
347
348 QgsPointLocator::Match bestMatch;
349 QgsPointLocator::MatchList edges; // for snap on intersection
350 double maxTolerance = 0;
352
353 for ( const LayerConfig &layerConfig : std::as_const( filteredConfigs ) )
354 {
355 double tolerance = QgsTolerance::toleranceInProjectUnits( layerConfig.tolerance, layerConfig.layer, mMapSettings, layerConfig.unit );
356 if ( QgsPointLocator *loc = locatorForLayerUsingStrategy( layerConfig.layer, pointMap, tolerance ) )
357 {
358 _updateBestMatch( bestMatch, pointMap, loc, layerConfig.type, tolerance, filter, relaxed );
359
360 if ( mSnappingConfig.intersectionSnapping() )
361 {
362 edges << loc->edgesInRect( pointMap, tolerance );
363 }
364 // We keep the maximum tolerance for intersection snapping and extra snapping
365 maxTolerance = std::max( maxTolerance, tolerance );
366 // To avoid yet an additional setting, on extra snappings, we use the combination of all enabled snap types
367 maxTypes = static_cast<QgsPointLocator::Type>( maxTypes | layerConfig.type );
368 }
369 }
370
371 for ( QgsVectorLayer *vl : mExtraSnapLayers )
372 {
373 QgsPointLocator *loc = locatorForLayerUsingStrategy( vl, pointMap, maxTolerance );
374 _updateBestMatch( bestMatch, pointMap, loc, maxTypes, maxTolerance, filter, false );
375 if ( mSnappingConfig.intersectionSnapping() )
376 edges << loc->edgesInRect( pointMap, maxTolerance );
377 }
378
379 if ( mSnappingConfig.intersectionSnapping() )
380 _replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), maxTolerance );
381
382 return bestMatch;
383 }
384 else if ( mSnappingConfig.mode() == Qgis::SnappingMode::AllLayers )
385 {
386 // data from project
387 double tolerance = QgsTolerance::toleranceInProjectUnits( mSnappingConfig.tolerance(), nullptr, mMapSettings, mSnappingConfig.units() );
388 QgsPointLocator::Types type = _snappingTypeToPointLocatorType( mSnappingConfig.typeFlag() );
389 QgsRectangle aoi = _areaOfInterest( pointMap, tolerance );
390
391 QList<LayerAndAreaOfInterest> layers;
392 const auto constLayers = mMapSettings.layers( true );
393 for ( QgsMapLayer *layer : constLayers )
394 if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer ) )
395 layers << qMakePair( vl, aoi );
396 prepareIndex( layers, relaxed );
397
398 QgsPointLocator::MatchList edges; // for snap on intersection
399 QgsPointLocator::Match bestMatch;
400
401 for ( const LayerAndAreaOfInterest &entry : std::as_const( layers ) )
402 {
403 QgsVectorLayer *vl = entry.first;
404 if ( QgsPointLocator *loc = locatorForLayerUsingStrategy( vl, pointMap, tolerance ) )
405 {
406 _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, relaxed );
407
408 if ( mSnappingConfig.intersectionSnapping() )
409 edges << loc->edgesInRect( pointMap, tolerance );
410 }
411 }
412
413 for ( QgsVectorLayer *vl : mExtraSnapLayers )
414 {
415 QgsPointLocator *loc = locatorForLayerUsingStrategy( vl, pointMap, tolerance );
416 _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, false );
417 if ( mSnappingConfig.intersectionSnapping() )
418 edges << loc->edgesInRect( pointMap, tolerance );
419 }
420
421 if ( mSnappingConfig.intersectionSnapping() )
422 _replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), tolerance );
423
424 return bestMatch;
425 }
426
427 return QgsPointLocator::Match();
428}
429
430void QgsSnappingUtils::onInitFinished( bool ok )
431{
432 QgsPointLocator *loc = static_cast<QgsPointLocator *>( sender() );
433
434 // point locator init didn't work out - too many features!
435 // let's make the allowed area smaller for the next time
436 if ( !ok )
437 {
438 mHybridMaxAreaPerLayer[loc->layer()->id()] /= 4;
439 }
440}
441
442void QgsSnappingUtils::prepareIndex( const QList<LayerAndAreaOfInterest> &layers, bool relaxed )
443{
444 // check if we need to build any index
445 QList<LayerAndAreaOfInterest> layersToIndex;
446 const auto constLayers = layers;
447 for ( const LayerAndAreaOfInterest &entry : constLayers )
448 {
449 QgsVectorLayer *vl = entry.first;
450
451 if ( vl->geometryType() == Qgis::GeometryType::Null || mStrategy == IndexNeverFull )
452 continue;
453
454 QgsPointLocator *loc = locatorForLayer( vl );
455
456 if ( !loc->isIndexing() && !isIndexPrepared( loc, entry.second ) )
457 layersToIndex << entry;
458 }
459 if ( !layersToIndex.isEmpty() )
460 {
461 // build indexes
462 QElapsedTimer t;
463 int i = 0;
464
465 if ( !relaxed )
466 {
467 t.start();
468 prepareIndexStarting( layersToIndex.count() );
469 }
470
471 for ( const LayerAndAreaOfInterest &entry : layersToIndex )
472 {
473 QgsVectorLayer *vl = entry.first;
474 QgsPointLocator *loc = locatorForLayer( vl );
475
476 if ( loc->isIndexing() && !relaxed )
477 {
479 }
480
481
482 if ( !mEnableSnappingForInvisibleFeature )
483 {
485 loc->setRenderContext( &ctx );
486 }
487
488 if ( mStrategy == IndexExtent )
489 {
490 QgsRectangle rect( mMapSettings.visibleExtent() );
491 loc->setExtent( &rect );
492 loc->init( -1, relaxed );
493 }
494 else if ( mStrategy == IndexHybrid )
495 {
496 // first time the layer is used? - let's set an initial guess about indexing
497 if ( !mHybridMaxAreaPerLayer.contains( vl->id() ) )
498 {
499 long long totalFeatureCount = vl->featureCount();
500 if ( totalFeatureCount < mHybridPerLayerFeatureLimit )
501 {
502 // index the whole layer
503 mHybridMaxAreaPerLayer[vl->id()] = -1;
504 }
505 else
506 {
507 // estimate for how big area it probably makes sense to build partial index to not exceed the limit
508 // (we may change the limit later)
509 QgsRectangle layerExtent = mMapSettings.layerExtentToOutputExtent( vl, vl->extent() );
510 double totalArea = layerExtent.width() * layerExtent.height();
511 mHybridMaxAreaPerLayer[vl->id()] = totalArea * mHybridPerLayerFeatureLimit / totalFeatureCount / 4;
512 }
513 }
514
515 double indexReasonableArea = mHybridMaxAreaPerLayer[vl->id()];
516 if ( indexReasonableArea == -1 )
517 {
518 // we can safely index the whole layer
519 loc->init( -1, relaxed );
520 }
521 else
522 {
523 // use area as big as we think may fit into our limit
524 QgsPointXY c = entry.second.center();
525 double halfSide = std::sqrt( indexReasonableArea ) / 2;
526 QgsRectangle rect( c.x() - halfSide, c.y() - halfSide,
527 c.x() + halfSide, c.y() + halfSide );
528 loc->setExtent( &rect );
529
530 // see if it's possible build index for this area
531 loc->init( mHybridPerLayerFeatureLimit, relaxed );
532 }
533
534 }
535 else // full index strategy
536 loc->init( relaxed );
537
538 if ( !relaxed )
540 }
541
542 if ( !relaxed )
543 {
544 QgsDebugMsgLevel( QStringLiteral( "Prepare index total: %1 ms" ).arg( t.elapsed() ), 2 );
545 }
546 }
547}
548
550{
551 return mSnappingConfig;
552}
553
555{
556 mEnableSnappingForInvisibleFeature = enable;
557}
558
560{
561 if ( mSnappingConfig == config )
562 return;
563
564 if ( mSnappingConfig.individualLayerSettings() != config.individualLayerSettings() )
565 onIndividualLayerSettingsChanged( config.individualLayerSettings() );
566
567 mSnappingConfig = config;
568
569 emit configChanged( mSnappingConfig );
570}
571
573{
574 mSnappingConfig.setEnabled( !mSnappingConfig.enabled() );
575 emit configChanged( mSnappingConfig );
576}
577
579{
580 if ( !mCurrentLayer )
581 return QgsPointLocator::Match();
582
583 QgsPointXY pointMap = mMapSettings.mapToPixel().toMapCoordinates( point );
584 double tolerance = QgsTolerance::vertexSearchRadius( mMapSettings );
585
586 QgsPointLocator *loc = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
587 if ( !loc )
588 return QgsPointLocator::Match();
589
590 QgsPointLocator::Match bestMatch;
591 _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, false );
592 return bestMatch;
593}
594
596{
597 QString oldDestCRS = mMapSettings.destinationCrs().authid();
598 QString newDestCRS = settings.destinationCrs().authid();
599 mMapSettings = settings;
600
601 if ( newDestCRS != oldDestCRS )
603}
604
606{
607 mCurrentLayer = layer;
608}
609
611{
612 QString msg = QStringLiteral( "--- SNAPPING UTILS DUMP ---\n" );
613
614 if ( !mMapSettings.hasValidSettings() )
615 {
616 msg += QLatin1String( "invalid map settings!" );
617 return msg;
618 }
619
620 QList<LayerConfig> layers;
621
622 if ( mSnappingConfig.mode() == Qgis::SnappingMode::ActiveLayer )
623 {
624 if ( mSnappingConfig.mode() == Qgis::SnappingMode::ActiveLayer && !mCurrentLayer )
625 {
626 msg += QLatin1String( "no current layer!" );
627 return msg;
628 }
629
630 layers << LayerConfig( mCurrentLayer, _snappingTypeToPointLocatorType( mSnappingConfig.typeFlag() ), mSnappingConfig.tolerance(), mSnappingConfig.units() );
631 }
632 else if ( mSnappingConfig.mode() == Qgis::SnappingMode::AllLayers )
633 {
634 const auto constLayers = mMapSettings.layers( true );
635 for ( QgsMapLayer *layer : constLayers )
636 {
637 if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer ) )
638 layers << LayerConfig( vl, _snappingTypeToPointLocatorType( mSnappingConfig.typeFlag() ), mSnappingConfig.tolerance(), mSnappingConfig.units() );
639 }
640 }
641 else if ( mSnappingConfig.mode() == Qgis::SnappingMode::AdvancedConfiguration )
642 {
643 layers = mLayers;
644 }
645
646 const auto constLayers = layers;
647 for ( const LayerConfig &layer : constLayers )
648 {
649 msg += QString( "layer : %1\n"
650 "config: %2 tolerance %3 %4\n" )
651 .arg( layer.layer->name() )
652 .arg( layer.type ).arg( layer.tolerance ).arg( static_cast<int>( layer.unit ) );
653
654 if ( mStrategy == IndexAlwaysFull || mStrategy == IndexHybrid || mStrategy == IndexExtent )
655 {
656 if ( QgsPointLocator *loc = locatorForLayer( layer.layer ) )
657 {
658 QString extentStr, cachedGeoms, limit( QStringLiteral( "no max area" ) );
659 if ( const QgsRectangle *r = loc->extent() )
660 {
661 extentStr = QStringLiteral( " extent %1" ).arg( r->toString() );
662 }
663 else
664 extentStr = QStringLiteral( "full extent" );
665 if ( loc->hasIndex() )
666 cachedGeoms = QStringLiteral( "%1 feats" ).arg( loc->cachedGeometryCount() );
667 else
668 cachedGeoms = QStringLiteral( "not initialized" );
669 if ( mStrategy == IndexHybrid )
670 {
671 if ( mHybridMaxAreaPerLayer.contains( layer.layer->id() ) )
672 {
673 double maxArea = mStrategy == IndexHybrid ? mHybridMaxAreaPerLayer[layer.layer->id()] : -1;
674 if ( maxArea != -1 )
675 limit = QStringLiteral( "max area %1" ).arg( maxArea );
676 }
677 else
678 limit = QStringLiteral( "not evaluated" );
679 }
680 msg += QStringLiteral( "index : YES | %1 | %2 | %3\n" ).arg( cachedGeoms, extentStr, limit );
681 }
682 else
683 msg += QLatin1String( "index : ???\n" ); // should not happen
684 }
685 else
686 msg += QLatin1String( "index : NO\n" );
687 msg += QLatin1String( "-\n" );
688 }
689
690 return msg;
691}
692
693QgsCoordinateReferenceSystem QgsSnappingUtils::destinationCrs() const
694{
695 return mMapSettings.destinationCrs();
696}
697
698void QgsSnappingUtils::onIndividualLayerSettingsChanged( const QHash<QgsVectorLayer *, QgsSnappingConfig::IndividualLayerSettings> &layerSettings )
699{
700 mLayers.clear();
701
702 QHash<QgsVectorLayer *, QgsSnappingConfig::IndividualLayerSettings>::const_iterator i;
703
704 for ( i = layerSettings.constBegin(); i != layerSettings.constEnd(); ++i )
705 {
706 if ( i->enabled() )
707 {
708 mLayers.append( LayerConfig( i.key(), _snappingTypeToPointLocatorType( static_cast<Qgis::SnappingTypes>( i->typeFlag() ) ), i->tolerance(), i->units() ) );
709 }
710 }
711}
@ NoSnap
No snapping.
QFlags< SnappingType > SnappingTypes
Snapping types.
Definition: qgis.h:551
@ Null
No geometry.
@ ActiveLayer
On the active layer.
@ AdvancedConfiguration
On a per layer configuration basis.
@ AllLayers
On all vector layers.
@ LineString
LineString.
@ MultiLineString
MultiLineString.
This class represents a coordinate reference system (CRS).
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:162
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.
static QgsGeometry unaryUnion(const QVector< QgsGeometry > &geometries, const QgsGeometryParameters &parameters=QgsGeometryParameters())
Compute the unary union on a list of geometries.
Qgis::WkbType wkbType() const
Returns type of the geometry as a WKB type (point / linestring / polygon etc.)
Base class for all map layer types.
Definition: qgsmaplayer.h:75
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.
QList< QgsMapLayer * > layers(bool expandGroupLayers=false) const
Returns the list of layers which will be rendered in 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.
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:
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.
QFlags< Type > Types
QgsVectorLayer * layer() const
Gets associated layer.
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...
bool isIndexing() const
Returns true if the point locator is currently indexing the data.
const QgsRectangle * extent() const
Gets extent of the area point locator covers - if nullptr then it caches the whole layer.
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:60
double sqrDist(double x, double y) const
Returns the squared distance between this point a specified x, y coordinate.
Definition: qgspointxy.h:187
double y
Definition: qgspointxy.h:64
Q_GADGET double x
Definition: qgspointxy.h:63
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:107
A rectangle specified with double values.
Definition: qgsrectangle.h:42
bool contains(const QgsRectangle &rect) const
Returns true when rectangle contains other rectangle.
Definition: qgsrectangle.h:385
bool intersects(const QgsRectangle &rect) const
Returns true when rectangle intersects with other rectangle.
Definition: qgsrectangle.h:371
double width() const
Returns the width of the rectangle.
Definition: qgsrectangle.h:236
double height() const
Returns the height of the rectangle.
Definition: qgsrectangle.h:243
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.
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.
Qgis::SnappingTypes typeFlag() const
Returns the flags type (vertices | segments | area | centroid | middle)
double maximumScale() const
Returns the max scale (i.e.
Qgis::MapToolUnit units() const
Returns the type of units.
Qgis::SnappingMode mode
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
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.
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.
QList< QgsSnappingUtils::LayerConfig > layers() const
Query layers used for snapping.
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, Qgis::MapToolUnit units)
Static function to translate tolerance value into map units.
Represents a vector layer which manages a vector based data sets.
long long featureCount(const QString &legendKey) const
Number of features rendered with specified legend key.
Q_INVOKABLE Qgis::GeometryType geometryType() const
Returns point, line or polygon.
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:62
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
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.