27 , mEnableSnappingForInvisibleFeature( enableSnappingForInvisibleFeature )
42 if ( !mLocators.contains( vl ) )
46 mLocators.insert( vl, vlpl );
48 return mLocators.value( vl );
53 qDeleteAll( mLocators );
56 qDeleteAll( mTemporaryLocators );
57 mTemporaryLocators.clear();
66 QgsRectangle aoi( pointMap.
x() - tolerance, pointMap.
y() - tolerance,
67 pointMap.
x() + tolerance, pointMap.
y() + tolerance );
71 if ( loc->
isIndexing() || isIndexPrepared( loc, aoi ) )
74 return temporaryLocatorForLayer( vl, pointMap, tolerance );
79 if ( mTemporaryLocators.contains( vl ) )
80 delete mTemporaryLocators.take( vl );
82 QgsRectangle rect( pointMap.
x() - tolerance, pointMap.
y() - tolerance,
83 pointMap.
x() + tolerance, pointMap.
y() + tolerance );
88 mTemporaryLocators.insert( vl, vlpl );
89 return mTemporaryLocators.value( vl );
107 if ( segments.isEmpty() )
110 QSet<QgsPointXY> endpoints;
113 QVector<QgsGeometry> geoms;
114 const auto constSegments = segments;
120 m.edgePoints( pl[0], pl[1] );
122 endpoints << pl[0] << pl[1];
129 QList<QgsPointXY> newPoints;
135 if ( !endpoints.contains( p ) )
144 const auto constPl = pl;
147 if ( !endpoints.contains( p ) )
153 if ( newPoints.isEmpty() )
158 double minSqrDist = 1e20;
159 const auto constNewPoints = newPoints;
162 double sqrDist = pt.
sqrDist( p.x(), p.y() );
163 if ( sqrDist < minSqrDist )
165 minSqrDist = sqrDist;
176 if ( !candidateMatch.
isValid() || candidateMatch.
distance() > maxDistance )
195 bestMatch = candidateMatch;
207 bestMatch = candidateMatch;
214 _replaceIfBetter( bestMatch, loc->
nearestVertex( pointMap, tolerance, filter, relaxed ), tolerance );
218 _replaceIfBetter( bestMatch, loc->
nearestEdge( pointMap, tolerance, filter, relaxed ), tolerance );
225 _replaceIfBetter( bestMatch, loc->
nearestArea( pointMap, tolerance, filter, relaxed ), tolerance );
229 _replaceIfBetter( bestMatch, loc->
nearestCentroid( pointMap, tolerance, filter ), tolerance );
238 static QgsPointLocator::Types _snappingTypeToPointLocatorType( QgsSnappingConfig::SnappingTypeFlag type )
240 return QgsPointLocator::Types(
static_cast<int>( type ) );
250 return QgsRectangle( point.
x() - tolerance, point.
y() - tolerance,
251 point.
x() + tolerance, point.
y() + tolerance );
268 QgsPointLocator::Types type = _snappingTypeToPointLocatorType( mSnappingConfig.
typeFlag() );
270 prepareIndex( QList<LayerAndAreaOfInterest>() << qMakePair( mCurrentLayer,
_areaOfInterest( pointMap, tolerance ) ), relaxed );
273 QgsPointLocator *loc = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
279 _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, relaxed );
283 QgsPointLocator *locEdges = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
286 edges = locEdges->
edgesInRect( pointMap, tolerance );
292 _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter,
false );
299 _replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), tolerance );
306 QList<LayerAndAreaOfInterest>
layers;
307 QList<LayerConfig> filteredConfigs;
315 for (
const LayerConfig &layerConfig : qgis::as_const( mLayers ) )
330 filteredConfigs << layerConfig;
333 prepareIndex(
layers, relaxed );
337 double maxTolerance = 0;
340 for (
const LayerConfig &layerConfig : qgis::as_const( filteredConfigs ) )
343 if (
QgsPointLocator *loc = locatorForLayerUsingStrategy( layerConfig.layer, pointMap, tolerance ) )
345 _updateBestMatch( bestMatch, pointMap, loc, layerConfig.type, tolerance, filter, relaxed );
352 maxTolerance = std::max( maxTolerance, tolerance );
361 _updateBestMatch( bestMatch, pointMap, loc, maxTypes, maxTolerance, filter,
false );
363 edges << loc->
edgesInRect( pointMap, maxTolerance );
367 _replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), maxTolerance );
375 QgsPointLocator::Types type = _snappingTypeToPointLocatorType( mSnappingConfig.
typeFlag() );
378 QList<LayerAndAreaOfInterest>
layers;
379 const auto constLayers = mMapSettings.
layers();
381 if (
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer ) )
382 layers << qMakePair( vl, aoi );
383 prepareIndex(
layers, relaxed );
388 for (
const LayerAndAreaOfInterest &entry : qgis::as_const(
layers ) )
391 if (
QgsPointLocator *loc = locatorForLayerUsingStrategy( vl, pointMap, tolerance ) )
393 _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, relaxed );
403 _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter,
false );
409 _replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), tolerance );
417 void QgsSnappingUtils::onInitFinished(
bool ok )
425 mHybridMaxAreaPerLayer[loc->
layer()->
id()] /= 4;
429 void QgsSnappingUtils::prepareIndex(
const QList<LayerAndAreaOfInterest> &layers,
bool relaxed )
432 QList<LayerAndAreaOfInterest> layersToIndex;
433 const auto constLayers =
layers;
434 for (
const LayerAndAreaOfInterest &entry : constLayers )
443 if ( !loc->
isIndexing() && !isIndexPrepared( loc, entry.second ) )
444 layersToIndex << entry;
446 if ( !layersToIndex.isEmpty() )
458 for (
const LayerAndAreaOfInterest &entry : layersToIndex )
469 if ( !mEnableSnappingForInvisibleFeature )
479 loc->
init( -1, relaxed );
484 if ( !mHybridMaxAreaPerLayer.contains( vl->
id() ) )
487 if ( totalFeatureCount < mHybridPerLayerFeatureLimit )
490 mHybridMaxAreaPerLayer[vl->
id()] = -1;
497 double totalArea = layerExtent.
width() * layerExtent.
height();
498 mHybridMaxAreaPerLayer[vl->
id()] = totalArea * mHybridPerLayerFeatureLimit / totalFeatureCount / 4;
502 double indexReasonableArea = mHybridMaxAreaPerLayer[vl->
id()];
503 if ( indexReasonableArea == -1 )
506 loc->
init( -1, relaxed );
512 double halfSide = std::sqrt( indexReasonableArea ) / 2;
514 c.x() + halfSide,
c.y() + halfSide );
518 loc->
init( mHybridPerLayerFeatureLimit, relaxed );
523 loc->
init( relaxed );
531 QgsDebugMsg( QStringLiteral(
"Prepare index total: %1 ms" ).arg( t.elapsed() ) );
538 return mSnappingConfig;
543 mEnableSnappingForInvisibleFeature = enable;
548 if ( mSnappingConfig ==
config )
567 if ( !mCurrentLayer )
573 QgsPointLocator *loc = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
578 _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter,
false );
586 mMapSettings = settings;
588 if ( newDestCRS != oldDestCRS )
594 mCurrentLayer = layer;
599 QString msg = QStringLiteral(
"--- SNAPPING UTILS DUMP ---\n" );
603 msg += QLatin1String(
"invalid map settings!" );
607 QList<LayerConfig>
layers;
613 msg += QLatin1String(
"no current layer!" );
621 const auto constLayers = mMapSettings.
layers();
624 if (
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer ) )
633 const auto constLayers =
layers;
636 msg += QString(
"layer : %1\n"
637 "config: %2 tolerance %3 %4\n" )
638 .arg( layer.layer->name() )
639 .arg( layer.type ).arg( layer.tolerance ).arg( layer.unit );
645 QString extentStr, cachedGeoms, limit( QStringLiteral(
"no max area" ) );
648 extentStr = QStringLiteral(
" extent %1" ).arg( r->toString() );
651 extentStr = QStringLiteral(
"full extent" );
655 cachedGeoms = QStringLiteral(
"not initialized" );
658 if ( mHybridMaxAreaPerLayer.contains( layer.layer->id() ) )
660 double maxArea = mStrategy ==
IndexHybrid ? mHybridMaxAreaPerLayer[layer.layer->id()] : -1;
662 limit = QStringLiteral(
"max area %1" ).arg( maxArea );
665 limit = QStringLiteral(
"not evaluated" );
667 msg += QStringLiteral(
"index : YES | %1 | %2 | %3\n" ).arg( cachedGeoms, extentStr, limit );
670 msg += QStringLiteral(
"index : ???\n" );
673 msg += QLatin1String(
"index : NO\n" );
674 msg += QLatin1String(
"-\n" );
685 void QgsSnappingUtils::onIndividualLayerSettingsChanged(
const QHash<QgsVectorLayer *, QgsSnappingConfig::IndividualLayerSettings> &layerSettings )
689 QHash<QgsVectorLayer *, QgsSnappingConfig::IndividualLayerSettings>::const_iterator i;
691 for ( i = layerSettings.constBegin(); i != layerSettings.constEnd(); ++i )
695 mLayers.append( LayerConfig( i.key(), _snappingTypeToPointLocatorType(
static_cast<QgsSnappingConfig::SnappingTypeFlag
>( i->typeFlag() ) ), i->tolerance(), i->units() ) );