Skip to content

Commit 1d26517

Browse files
authored
Validate polygon split geometry before committing changes (#4211)
1 parent cb585f3 commit 1d26517

File tree

6 files changed

+128
-12
lines changed

6 files changed

+128
-12
lines changed

app/maptools/splittingmaptool.cpp

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,40 @@ bool SplittingMapTool::hasValidGeometry() const
4242
return mPoints.count() >= 2;
4343
}
4444

45-
bool SplittingMapTool::commitSplit() const
45+
bool SplittingMapTool::isValidSplit() const
4646
{
47-
if ( !mFeatureToSplit.isValid() )
47+
if ( !mFeatureToSplit.isValid() || !hasValidGeometry() )
4848
return false;
4949

50-
if ( !hasValidGeometry() )
50+
const QgsGeometry featureGeom = mFeatureToSplit.feature().geometry();
51+
if ( featureGeom.isNull() || featureGeom.isEmpty() )
52+
return false;
53+
54+
// split line must intersect the feature's geometry
55+
if ( !mRecordedGeometry.intersects( featureGeom ) )
5156
return false;
5257

58+
QgsPointXY firstPoint( mPoints.first() );
59+
QgsPointXY lastPoint( mPoints.last() );
60+
61+
// start and end points of the line must be outside the feature, ensuring line properly crosses the feature
62+
if ( featureGeom.contains( &firstPoint ) || featureGeom.contains( &lastPoint ) )
63+
return false;
64+
65+
return true;
66+
}
67+
68+
SplittingMapTool::SplitResult SplittingMapTool::commitSplit() const
69+
{
70+
if ( !mFeatureToSplit.isValid() )
71+
return Failed;
72+
73+
if ( !hasValidGeometry() )
74+
return Failed;
75+
76+
if ( !isValidSplit() )
77+
return InvalidSplit;
78+
5379
// only the specified featureToSplit shall be split, so we select
5480
// it here in order to avoid other features being split
5581
mFeatureToSplit.layer()->select( mFeatureToSplit.feature().id() );
@@ -62,9 +88,9 @@ bool SplittingMapTool::commitSplit() const
6288

6389
if ( result == Qgis::GeometryOperationResult::Success )
6490
{
65-
return true;
91+
return Success;
6692
}
67-
return false;
93+
return Failed;
6894
}
6995

7096
void SplittingMapTool::rebuildGeometry()

app/maptools/splittingmaptool.h

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ class SplittingMapTool : public AbstractMapTool
2525
Q_PROPERTY( FeatureLayerPair featureToSplit READ featureToSplit WRITE setFeatureToSplit NOTIFY featureToSplitChanged )
2626

2727
public:
28+
29+
enum SplitResult
30+
{
31+
Success = 0,
32+
InvalidSplit = 1, // Split line does not properly cross the feature boundary
33+
Failed = 2
34+
};
35+
Q_ENUM( SplitResult )
36+
2837
explicit SplittingMapTool( QObject *parent = nullptr );
2938
virtual ~SplittingMapTool() override;
3039

@@ -43,11 +52,8 @@ class SplittingMapTool : public AbstractMapTool
4352
//! Returns true if the captured geometry has enought points for the specified layer
4453
Q_INVOKABLE bool hasValidGeometry() const;
4554

46-
/**
47-
* Splits the feature with recorded geometry and commits the changes to the layer
48-
* Returns true if splitting was successful, false otherwise
49-
*/
50-
Q_INVOKABLE bool commitSplit() const;
55+
//! Splits the feature with recorded geometry and commits the changes to the layer
56+
Q_INVOKABLE SplittingMapTool::SplitResult commitSplit() const;
5157

5258
// Getters/setters
5359
const QgsGeometry &recordedGeometry() const;
@@ -64,6 +70,12 @@ class SplittingMapTool : public AbstractMapTool
6470
void rebuildGeometry();
6571

6672
private:
73+
/**
74+
* Validates if the recorded geometry can successfully split the feature
75+
* Returns true if the split line properly crosses the feature boundary
76+
*/
77+
bool isValidSplit() const;
78+
6779
QVector<QgsPoint> mPoints;
6880

6981
QgsGeometry mRecordedGeometry;

app/qml/dialogs/MMSplittingFailedDialog.qml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ MMDrawerDialog {
1616

1717
imageSource: __style.negativeMMSymbolImage
1818
title: qsTr( "We could not split the feature" )
19-
description: qsTr( "Please make sure that the split line crosses your feature. The feature needs to have a valid geometry in order to split it." )
19+
description: qsTr( "Ensure that your split line intersects the feature properly and that the feature’s geometry is valid." )
2020
primaryButton.text: qsTr( "Ok, I understand" )
2121

2222
onPrimaryButtonClicked: close()

app/qml/map/MMSplittingTools.qml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,14 @@ Item {
106106
if ( mapTool.hasValidGeometry() )
107107
{
108108
let result = mapTool.commitSplit()
109-
root.done( result )
109+
110+
if ( result === MM.SplittingMapTool.InvalidSplit )
111+
{
112+
__notificationModel.addWarning( qsTr( "The split line does not properly cross the feature. Please adjust the line to cross the feature boundary." ) )
113+
return
114+
}
115+
116+
root.done( result === MM.SplittingMapTool.Success )
110117
}
111118
else
112119
{

app/test/testmaptools.cpp

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,74 @@ void TestMapTools::testSplitting()
195195
delete splitTool;
196196
}
197197

198+
void TestMapTools::testValidSplit()
199+
{
200+
// splitting environment setup
201+
SplittingMapTool *splitTool = new SplittingMapTool();
202+
203+
QString projectDir = TestUtils::testDataDir() + "/planes";
204+
QString projectTempDir = QDir::tempPath() + "/" + QUuid::createUuid().toString();
205+
QString projectName = "quickapp_project.qgs";
206+
207+
QVERIFY( InputUtils::cpDir( projectDir, projectTempDir ) );
208+
209+
QgsProject *project = new QgsProject();
210+
211+
QVERIFY( project->read( projectTempDir + "/" + projectName ) );
212+
213+
QgsMapLayer *sectorL = project->mapLayersByName( QStringLiteral( "FlySector" ) ).at( 0 );
214+
QgsVectorLayer *flySectorLayer = static_cast<QgsVectorLayer *>( sectorL );
215+
216+
QVERIFY( flySectorLayer && flySectorLayer->isValid() );
217+
218+
InputMapCanvasMap canvas;
219+
InputMapSettings *ms = canvas.mapSettings();
220+
ms->setProject( project );
221+
ms->setDestinationCrs( project->crs() );
222+
ms->setOutputSize( QSize( 600, 1096 ) );
223+
ms->setLayers( project->layers<QgsMapLayer *>().toList() );
224+
225+
QgsRectangle extent = QgsRectangle( -107.54331499504026226, 21.62302175066136556, -72.73224633912816728, 51.49933451998575151 );
226+
ms->setExtent( extent );
227+
228+
splitTool->setMapSettings( ms );
229+
230+
// set feature to split
231+
int fidToSplit = 1;
232+
QgsFeature featureToSplit = flySectorLayer->getFeature( fidToSplit );
233+
FeatureLayerPair pairToSplit( featureToSplit, flySectorLayer );
234+
splitTool->setFeatureToSplit( pairToSplit );
235+
236+
// not enough points
237+
QCOMPARE( splitTool->commitSplit(), SplittingMapTool::Failed );
238+
239+
// line doesnt intersect feature
240+
splitTool->addPoint( QgsPoint( -104.751, 32.448 ) );
241+
QCOMPARE( splitTool->commitSplit(), SplittingMapTool::Failed );
242+
243+
// valid split line, endpoints outside and line intersects feature
244+
splitTool->addPoint( QgsPoint( -120.844, 32.592 ) );
245+
QCOMPARE( splitTool->commitSplit(), SplittingMapTool::Success );
246+
247+
// line doesnt intersect feature
248+
splitTool->removePoint();
249+
splitTool->removePoint();
250+
splitTool->addPoint( QgsPoint( -130.0, 10.0 ) );
251+
splitTool->addPoint( QgsPoint( -140.0, 15.0 ) );
252+
QCOMPARE( splitTool->commitSplit(), SplittingMapTool::InvalidSplit );
253+
254+
// endpoint inside feature boundary
255+
QgsPointXY centerPoint = featureToSplit.geometry().centroid().asPoint();
256+
splitTool->removePoint();
257+
splitTool->removePoint();
258+
splitTool->addPoint( QgsPoint( centerPoint.x(), centerPoint.y() ) );
259+
splitTool->addPoint( QgsPoint( -120.844, 32.592 ) );
260+
QCOMPARE( splitTool->commitSplit(), SplittingMapTool::InvalidSplit );
261+
262+
delete project;
263+
delete splitTool;
264+
}
265+
198266
void TestMapTools::testRecording()
199267
{
200268
RecordingMapTool *recordTool = new RecordingMapTool();

app/test/testmaptools.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ class TestMapTools : public QObject
2626
void cleanup();
2727

2828
void testSnapping();
29+
2930
void testSplitting();
31+
void testValidSplit();
32+
3033
void testRecording();
3134
void testMeasuring();
3235

0 commit comments

Comments
 (0)