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