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