From 6c65f6dcf6888efca0391dd6c9d5f789d5173e3a Mon Sep 17 00:00:00 2001 From: Joonalai Date: Tue, 14 Jan 2025 12:03:21 +0200 Subject: [PATCH 1/3] Optimize topological editing with spatial filtering --- src/app/qgsmaptooladdfeature.cpp | 7 +++++++ src/app/vertextool/qgsvertextool.cpp | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/src/app/qgsmaptooladdfeature.cpp b/src/app/qgsmaptooladdfeature.cpp index befcec9d2e99..92196c971299 100644 --- a/src/app/qgsmaptooladdfeature.cpp +++ b/src/app/qgsmaptooladdfeature.cpp @@ -119,6 +119,10 @@ void QgsMapToolAddFeature::featureDigitized( const QgsFeature &feature ) } if ( topologicalEditing ) { + QgsFeatureRequest request = QgsFeatureRequest().setFilterRect( feature.geometry().boundingBox() ).setNoAttributes().setFlags( Qgis::FeatureRequestFlag::NoGeometry ).setLimit( 1 ); + request.setDestinationCrs( vlayer->crs(), vlayer->transformContext() ); + QgsFeature f; + const QList layers = canvas()->layers( true ); for ( QgsMapLayer *layer : layers ) @@ -131,6 +135,9 @@ void QgsMapToolAddFeature::featureDigitized( const QgsFeature &feature ) if ( !( vectorLayer->geometryType() == Qgis::GeometryType::Polygon || vectorLayer->geometryType() == Qgis::GeometryType::Line ) ) continue; + if ( !vectorLayer->getFeatures( request ).nextFeature( f ) ) + continue; + vectorLayer->beginEditCommand( tr( "Topological points added by 'Add Feature'" ) ); int res = 2; diff --git a/src/app/vertextool/qgsvertextool.cpp b/src/app/vertextool/qgsvertextool.cpp index 03491256913f..efc5a440faa2 100644 --- a/src/app/vertextool/qgsvertextool.cpp +++ b/src/app/vertextool/qgsvertextool.cpp @@ -2204,6 +2204,9 @@ void QgsVertexTool::moveVertex( const QgsPointXY &mapPoint, const QgsPointLocato { // topo editing: add vertex to existing segments when moving/adding a vertex to such segment. + QgsFeatureRequest request = QgsFeatureRequest().setFilterRect( layerPoint.boundingBox() ).setDestinationCrs( dragLayer->crs(), mCanvas->mapSettings().transformContext() ).setNoAttributes().setFlags( Qgis::FeatureRequestFlag::NoGeometry ).setLimit( 1 ); + QgsFeature f; + const QList targetLayers = canvas()->layers( true ); for ( auto itLayerEdits = edits.begin(); itLayerEdits != edits.end(); ++itLayerEdits ) @@ -2218,6 +2221,9 @@ void QgsVertexTool::moveVertex( const QgsPointXY &mapPoint, const QgsPointLocato if ( !( vectorLayer->geometryType() == Qgis::GeometryType::Polygon || vectorLayer->geometryType() == Qgis::GeometryType::Line ) ) continue; + if ( !vectorLayer->getFeatures( request ).nextFeature( f ) ) + continue; + // layer's CRS need to be the the same (otherwise we would need to reproject the point and it will not be coincident) if ( vectorLayer->crs() != itLayerEdits.key()->crs() ) continue; From ee2e11194230252efff17c94bb1baf3af188dbf9 Mon Sep 17 00:00:00 2001 From: Joonalai Date: Wed, 15 Jan 2025 09:55:53 +0200 Subject: [PATCH 2/3] Check crs before filtering --- src/app/vertextool/qgsvertextool.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/vertextool/qgsvertextool.cpp b/src/app/vertextool/qgsvertextool.cpp index efc5a440faa2..a7a2dbd42a51 100644 --- a/src/app/vertextool/qgsvertextool.cpp +++ b/src/app/vertextool/qgsvertextool.cpp @@ -2221,13 +2221,13 @@ void QgsVertexTool::moveVertex( const QgsPointXY &mapPoint, const QgsPointLocato if ( !( vectorLayer->geometryType() == Qgis::GeometryType::Polygon || vectorLayer->geometryType() == Qgis::GeometryType::Line ) ) continue; - if ( !vectorLayer->getFeatures( request ).nextFeature( f ) ) - continue; - // layer's CRS need to be the the same (otherwise we would need to reproject the point and it will not be coincident) if ( vectorLayer->crs() != itLayerEdits.key()->crs() ) continue; + if ( !vectorLayer->getFeatures( request ).nextFeature( f ) ) + continue; + vectorLayer->beginEditCommand( tr( "Topological points added by 'Vertex Tool'" ) ); bool topoPointsAdded = false; From dd67b63586ef989ec0ed7ffaf63496460148ccfa Mon Sep 17 00:00:00 2001 From: Joonalai Date: Wed, 15 Jan 2025 13:56:16 +0200 Subject: [PATCH 3/3] Grow search rect based on layer precision --- .../vector/qgsvectorlayereditutils.sip.in | 1 + .../vector/qgsvectorlayereditutils.sip.in | 1 + src/app/qgsmaptooladdfeature.cpp | 26 ++++++++++--- src/app/vertextool/qgsvertextool.cpp | 13 ++++++- src/core/vector/qgsvectorlayereditutils.cpp | 38 +++++++++++-------- src/core/vector/qgsvectorlayereditutils.h | 4 ++ 6 files changed, 61 insertions(+), 22 deletions(-) diff --git a/python/PyQt6/core/auto_generated/vector/qgsvectorlayereditutils.sip.in b/python/PyQt6/core/auto_generated/vector/qgsvectorlayereditutils.sip.in index 7a158d17fd58..579235d7a486 100644 --- a/python/PyQt6/core/auto_generated/vector/qgsvectorlayereditutils.sip.in +++ b/python/PyQt6/core/auto_generated/vector/qgsvectorlayereditutils.sip.in @@ -332,6 +332,7 @@ Merge features into a single one. .. versionadded:: 3.30 %End + }; /************************************************************************ diff --git a/python/core/auto_generated/vector/qgsvectorlayereditutils.sip.in b/python/core/auto_generated/vector/qgsvectorlayereditutils.sip.in index 7a158d17fd58..579235d7a486 100644 --- a/python/core/auto_generated/vector/qgsvectorlayereditutils.sip.in +++ b/python/core/auto_generated/vector/qgsvectorlayereditutils.sip.in @@ -332,6 +332,7 @@ Merge features into a single one. .. versionadded:: 3.30 %End + }; /************************************************************************ diff --git a/src/app/qgsmaptooladdfeature.cpp b/src/app/qgsmaptooladdfeature.cpp index 92196c971299..3f9a89c776f6 100644 --- a/src/app/qgsmaptooladdfeature.cpp +++ b/src/app/qgsmaptooladdfeature.cpp @@ -26,6 +26,7 @@ #include "qgisapp.h" #include "qgsexpressioncontextutils.h" #include "qgsrubberband.h" +#include "qgsvectorlayereditutils.h" #include @@ -119,15 +120,16 @@ void QgsMapToolAddFeature::featureDigitized( const QgsFeature &feature ) } if ( topologicalEditing ) { - QgsFeatureRequest request = QgsFeatureRequest().setFilterRect( feature.geometry().boundingBox() ).setNoAttributes().setFlags( Qgis::FeatureRequestFlag::NoGeometry ).setLimit( 1 ); - request.setDestinationCrs( vlayer->crs(), vlayer->transformContext() ); - QgsFeature f; - + QgsFeatureRequest request = QgsFeatureRequest().setNoAttributes().setFlags( Qgis::FeatureRequestFlag::NoGeometry ).setLimit( 1 ); + const QgsRectangle bbox = feature.geometry().boundingBox(); const QList layers = canvas()->layers( true ); for ( QgsMapLayer *layer : layers ) { QgsVectorLayer *vectorLayer = qobject_cast( layer ); + QgsRectangle searchRect; + QgsFeature f; + QgsCoordinateTransform transform; if ( !vectorLayer || !vectorLayer->isEditable() ) continue; @@ -135,6 +137,20 @@ void QgsMapToolAddFeature::featureDigitized( const QgsFeature &feature ) if ( !( vectorLayer->geometryType() == Qgis::GeometryType::Polygon || vectorLayer->geometryType() == Qgis::GeometryType::Line ) ) continue; + if ( vectorLayer->crs() == vlayer->crs() ) + { + searchRect = QgsRectangle( bbox ); + } + else + { + transform = QgsCoordinateTransform( vlayer->crs(), vectorLayer->crs(), vectorLayer->transformContext() ); + searchRect = transform.transformBoundingBox( bbox ); + } + + searchRect.grow( QgsVectorLayerEditUtils::getTopologicalSearchRadius( vectorLayer ) ); + request.setFilterRect( searchRect ); + + // We check that there is actually at least one feature intersecting our geometry in the layer to avoid creating an empty edit command and calling costly addTopologicalPoint if ( !vectorLayer->getFeatures( request ).nextFeature( f ) ) continue; @@ -147,7 +163,7 @@ void QgsMapToolAddFeature::featureDigitized( const QgsFeature &feature ) try { // transform digitized geometry from vlayer crs to vectorLayer crs and add topological points - transformedGeom.transform( QgsCoordinateTransform( vlayer->crs(), vectorLayer->crs(), vectorLayer->transformContext() ) ); + transformedGeom.transform( transform ); res = vectorLayer->addTopologicalPoints( transformedGeom ); } catch ( QgsCsException &cse ) diff --git a/src/app/vertextool/qgsvertextool.cpp b/src/app/vertextool/qgsvertextool.cpp index a7a2dbd42a51..c1ad8120bd86 100644 --- a/src/app/vertextool/qgsvertextool.cpp +++ b/src/app/vertextool/qgsvertextool.cpp @@ -43,6 +43,7 @@ #include "qgsexpressioncontextutils.h" #include "qgsmessagebar.h" #include "qgssettingsentryimpl.h" +#include "qgsvectorlayereditutils.h" #include @@ -2204,8 +2205,9 @@ void QgsVertexTool::moveVertex( const QgsPointXY &mapPoint, const QgsPointLocato { // topo editing: add vertex to existing segments when moving/adding a vertex to such segment. - QgsFeatureRequest request = QgsFeatureRequest().setFilterRect( layerPoint.boundingBox() ).setDestinationCrs( dragLayer->crs(), mCanvas->mapSettings().transformContext() ).setNoAttributes().setFlags( Qgis::FeatureRequestFlag::NoGeometry ).setLimit( 1 ); - QgsFeature f; + QgsFeatureRequest request = QgsFeatureRequest().setNoAttributes().setFlags( Qgis::FeatureRequestFlag::NoGeometry ).setLimit( 1 ); + const QgsRectangle bbox = layerPoint.boundingBox(); + const QList targetLayers = canvas()->layers( true ); @@ -2214,6 +2216,8 @@ void QgsVertexTool::moveVertex( const QgsPointXY &mapPoint, const QgsPointLocato for ( QgsMapLayer *targetLayer : targetLayers ) { QgsVectorLayer *vectorLayer = qobject_cast( targetLayer ); + QgsRectangle searchRect; + QgsFeature f; if ( !vectorLayer || !vectorLayer->isEditable() ) continue; @@ -2225,6 +2229,11 @@ void QgsVertexTool::moveVertex( const QgsPointXY &mapPoint, const QgsPointLocato if ( vectorLayer->crs() != itLayerEdits.key()->crs() ) continue; + searchRect = QgsRectangle( bbox ); + searchRect.grow( QgsVectorLayerEditUtils::getTopologicalSearchRadius( vectorLayer ) ); + request.setFilterRect( searchRect ); + + // We check that there is actually at least one feature intersecting our geometry in the layer to avoid creating an empty edit command and calling costly addTopologicalPoint if ( !vectorLayer->getFeatures( request ).nextFeature( f ) ) continue; diff --git a/src/core/vector/qgsvectorlayereditutils.cpp b/src/core/vector/qgsvectorlayereditutils.cpp index 17df3e729fb3..0ab5a0260061 100644 --- a/src/core/vector/qgsvectorlayereditutils.cpp +++ b/src/core/vector/qgsvectorlayereditutils.cpp @@ -211,6 +211,28 @@ Qgis::GeometryOperationResult staticAddRing( QgsVectorLayer *layer, std::unique_ return success ? Qgis::GeometryOperationResult::Success : addRingReturnCode; } +///@cond PRIVATE +double QgsVectorLayerEditUtils::getTopologicalSearchRadius( const QgsVectorLayer *layer ) +{ + double threshold = layer->geometryOptions()->geometryPrecision(); + + if ( qgsDoubleNear( threshold, 0.0 ) ) + { + threshold = 1e-8; + + if ( layer->crs().mapUnits() == Qgis::DistanceUnit::Meters ) + { + threshold = 0.001; + } + else if ( layer->crs().mapUnits() == Qgis::DistanceUnit::Feet ) + { + threshold = 0.0001; + } + } + return threshold; +} +///@endcond + Qgis::GeometryOperationResult QgsVectorLayerEditUtils::addRing( const QVector &ring, const QgsFeatureIds &targetFeatureIds, QgsFeatureId *modifiedFeatureId ) { QgsPointSequence l; @@ -801,21 +823,7 @@ int QgsVectorLayerEditUtils::addTopologicalPoints( const QgsPoint &p ) double segmentSearchEpsilon = mLayer->crs().isGeographic() ? 1e-12 : 1e-8; //work with a tolerance because coordinate projection may introduce some rounding - double threshold = mLayer->geometryOptions()->geometryPrecision(); - - if ( qgsDoubleNear( threshold, 0.0 ) ) - { - threshold = 1e-8; - - if ( mLayer->crs().mapUnits() == Qgis::DistanceUnit::Meters ) - { - threshold = 0.001; - } - else if ( mLayer->crs().mapUnits() == Qgis::DistanceUnit::Feet ) - { - threshold = 0.0001; - } - } + double threshold = getTopologicalSearchRadius( mLayer ); QgsRectangle searchRect( p, p, false ); searchRect.grow( threshold ); diff --git a/src/core/vector/qgsvectorlayereditutils.h b/src/core/vector/qgsvectorlayereditutils.h index c2443c4d241e..7571042fae32 100644 --- a/src/core/vector/qgsvectorlayereditutils.h +++ b/src/core/vector/qgsvectorlayereditutils.h @@ -282,6 +282,10 @@ class CORE_EXPORT QgsVectorLayerEditUtils */ bool mergeFeatures( const QgsFeatureId &targetFeatureId, const QgsFeatureIds &mergeFeatureIds, const QgsAttributes &mergeAttributes, const QgsGeometry &unionGeometry, QString &errorMessage SIP_OUT ); + ///@cond PRIVATE + static double getTopologicalSearchRadius( const QgsVectorLayer *layer ) SIP_SKIP; + ///@endcond + private: /**