Compare commits

...

4 Commits

Author SHA1 Message Date
qgis-bot
901cc0e98a
Merge c19416bb78a666ea85e8d897e8973dc4a52627e5 into 810c1a07eb729f4fccb5acd2ae4bb57d820a2914 2025-09-30 06:38:35 +01:00
Nyall Dawson
c19416bb78
[vertextool] Interpolate Z/M when adding vertex to segment
When adding a vertex in the middle of a segment with valid
Z or M values at the neighbouring vertices, interpolate the
new vertex Z / M value from these neighbouring Z / M values.

Fixes #57819
2025-09-12 09:09:18 +10:00
Nyall Dawson
5980bbd077
Add vertex tool tests for adding vertices to layer with M 2025-09-12 09:09:18 +10:00
Nyall Dawson
34d20c54f5
Add unit tests for vertex editor adding vertices to z enabled layer 2025-09-12 09:09:18 +10:00
2 changed files with 249 additions and 2 deletions

View File

@ -2165,10 +2165,46 @@ void QgsVertexTool::moveVertex( const QgsPointXY &mapPoint, const QgsPointLocato
QgsPoint pt( layerPoint );
if ( QgsWkbTypes::hasZ( dragLayer->wkbType() ) && !pt.is3D() )
{
pt.addZValue( defaultZValue() );
if ( !addingAtEndpoint )
{
// try to linearly interpolate z from adjacent vertices
const QgsPoint pointBefore = geomTmp->vertexAt( QgsVertexId( vid.part, vid.ring, vid.vertex - 1 ) );
const QgsPoint pointAfter = geomTmp->vertexAt( vid );
// we can only do this if the adjacent vertices HAVE valid z values
if ( !std::isnan( pointBefore.z() ) && !std::isnan( pointAfter.z() ) )
{
const double distanceFromFirstVertexToNewVertex = pointBefore.distance( pt );
const double newDistanceBetweenOriginalAdjacentVertices = distanceFromFirstVertexToNewVertex + pt.distance( pointAfter );
if ( newDistanceBetweenOriginalAdjacentVertices )
{
pt.setZ( pointBefore.z() + ( pointAfter.z() - pointBefore.z() ) * distanceFromFirstVertexToNewVertex / newDistanceBetweenOriginalAdjacentVertices );
}
}
}
}
if ( QgsWkbTypes::hasM( dragLayer->wkbType() ) && !pt.isMeasure() )
{
pt.addMValue( defaultMValue() );
if ( !addingAtEndpoint )
{
// try to linearly interpolate m from adjacent vertices
const QgsPoint pointBefore = geomTmp->vertexAt( QgsVertexId( vid.part, vid.ring, vid.vertex - 1 ) );
const QgsPoint pointAfter = geomTmp->vertexAt( vid );
// we can only do this if the adjacent vertices HAVE valid m values
if ( !std::isnan( pointBefore.m() ) && !std::isnan( pointAfter.m() ) )
{
const double distanceFromFirstVertexToNewVertex = pointBefore.distance( pt );
const double newDistanceBetweenOriginalAdjacentVertices = distanceFromFirstVertexToNewVertex + pt.distance( pointAfter );
if ( newDistanceBetweenOriginalAdjacentVertices )
{
pt.setM( pointBefore.m() + ( pointAfter.m() - pointBefore.m() ) * distanceFromFirstVertexToNewVertex / newDistanceBetweenOriginalAdjacentVertices );
}
}
}
}
if ( !geomTmp->insertVertex( vid, pt ) )
{

View File

@ -77,6 +77,8 @@ class TestQgsVertexTool : public QObject
void testAddVertexAtEndpoint();
void testAddVertexDoubleClick();
void testAddVertexDoubleClickWithShift();
void testAddVertexZ();
void testAddVertexM();
void testAvoidIntersections();
void testDeleteVertex();
void testConvertVertex();
@ -165,11 +167,14 @@ class TestQgsVertexTool : public QObject
QPointer< QgsVectorLayer > mLayerPoint;
QPointer< QgsVectorLayer > mLayerPointZ;
QPointer< QgsVectorLayer > mLayerLineZ;
QPointer< QgsVectorLayer > mLayerLineM;
QPointer< QgsVectorLayer > mLayerCompoundCurve;
QPointer< QgsVectorLayer > mLayerLineReprojected;
QgsFeatureId mFidLineZF1 = 0;
QgsFeatureId mFidLineZF2 = 0;
QgsFeatureId mFidLineZF3 = 0;
QgsFeatureId mFidLineMF1 = 0;
QgsFeatureId mFidLineMF2 = 0;
QgsFeatureId mFidLineF1 = 0;
QgsFeatureId mFidMultiLineF1 = 0;
QgsFeatureId mFidLineF13857 = 0;
@ -236,10 +241,13 @@ void TestQgsVertexTool::init()
mLayerLineZ = new QgsVectorLayer( QStringLiteral( "LineStringZ?" ), QStringLiteral( "layer line z" ), QStringLiteral( "memory" ) );
QVERIFY( mLayerLineZ->isValid() );
mLayerLineZ->setCrs( mFake27700 );
mLayerLineM = new QgsVectorLayer( QStringLiteral( "LineStringM?" ), QStringLiteral( "layer line m" ), QStringLiteral( "memory" ) );
QVERIFY( mLayerLineM->isValid() );
mLayerLineM->setCrs( mFake27700 );
mLayerCompoundCurve = new QgsVectorLayer( QStringLiteral( "CompoundCurve?" ), QStringLiteral( "layer compound curve" ), QStringLiteral( "memory" ) );
QVERIFY( mLayerCompoundCurve->isValid() );
mLayerCompoundCurve->setCrs( mFake27700 );
QgsProject::instance()->addMapLayers( QList<QgsMapLayer *>() << mLayerLine << mLayerMultiLine << mLayerPolygon << mLayerMultiPolygon << mLayerPoint << mLayerPointZ << mLayerLineZ << mLayerCompoundCurve );
QgsProject::instance()->addMapLayers( QList<QgsMapLayer *>() << mLayerLine << mLayerMultiLine << mLayerPolygon << mLayerMultiPolygon << mLayerPoint << mLayerPointZ << mLayerLineZ << mLayerLineM << mLayerCompoundCurve );
QgsFeature lineF1;
@ -271,6 +279,10 @@ void TestQgsVertexTool::init()
linez2.setGeometry( QgsGeometry::fromWkt( "LineStringZ (5 7 5, 7 7 10)" ) );
linez3.setGeometry( QgsGeometry::fromWkt( "LineStringZ (5 5.5 5, 7 5.5 10)" ) );
QgsFeature linem1, linem2;
linem1.setGeometry( QgsGeometry::fromWkt( "LineStringM (5 5 1, 6 6 1, 7 5 1)" ) );
linem2.setGeometry( QgsGeometry::fromWkt( "LineStringM (5 7 5, 7 7 10)" ) );
QgsFeature curveF1;
curveF1.setGeometry( QgsGeometry::fromWkt( "CompoundCurve (CircularString (14 14, 10 10, 17 10))" ) );
QgsFeature curveF2;
@ -321,6 +333,17 @@ void TestQgsVertexTool::init()
mFidLineZF3 = linez3.id();
QCOMPARE( mLayerLineZ->featureCount(), ( long ) 3 );
mLayerLineM->dataProvider()->addFeature( linem1 );
mFidLineMF1 = linem1.id();
mLayerLineM->dataProvider()->addFeature( linem2 );
mFidLineMF2 = linem2.id();
QCOMPARE( mLayerLineM->featureCount(), ( long ) 2 );
mFidLineZF1 = linez1.id();
mFidLineZF2 = linez2.id();
mFidLineZF3 = linez3.id();
QCOMPARE( mLayerLineZ->featureCount(), ( long ) 3 );
mLayerCompoundCurve->startEditing();
mLayerCompoundCurve->addFeature( curveF1 );
mLayerCompoundCurve->addFeature( curveF2 );
@ -355,7 +378,7 @@ void TestQgsVertexTool::init()
QCOMPARE( mCanvas->mapSettings().outputSize(), QSize( 512, 512 ) );
QCOMPARE( mCanvas->mapSettings().visibleExtent(), QgsRectangle( 0, 0, 8, 8 ) );
mCanvas->setLayers( QList<QgsMapLayer *>() << mLayerLine << mLayerMultiLine << mLayerLineReprojected << mLayerPolygon << mLayerMultiPolygon << mLayerPoint << mLayerPointZ << mLayerLineZ << mLayerCompoundCurve );
mCanvas->setLayers( QList<QgsMapLayer *>() << mLayerLine << mLayerMultiLine << mLayerLineReprojected << mLayerPolygon << mLayerMultiPolygon << mLayerPoint << mLayerPointZ << mLayerLineZ << mLayerLineM << mLayerCompoundCurve );
QgsMapCanvasSnappingUtils *snappingUtils = new QgsMapCanvasSnappingUtils( mCanvas, this );
mCanvas->setSnappingUtils( snappingUtils );
@ -368,6 +391,7 @@ void TestQgsVertexTool::init()
snappingUtils->locatorForLayer( mLayerPoint )->init();
snappingUtils->locatorForLayer( mLayerPointZ )->init();
snappingUtils->locatorForLayer( mLayerLineZ )->init();
snappingUtils->locatorForLayer( mLayerLineM )->init();
snappingUtils->locatorForLayer( mLayerCompoundCurve )->init();
// create vertex tool
@ -730,6 +754,193 @@ void TestQgsVertexTool::testAddVertex()
QCOMPARE( mLayerPoint->undoStack()->index(), 1 );
}
void TestQgsVertexTool::testAddVertexZ()
{
QgsSettingsRegistryCore::settingsDigitizingDefaultZValue->setValue( 333 );
// add vertex in linestringZ
QCOMPARE( mLayerLineZ->undoStack()->index(), 3 );
// in middle of segment
mouseClick( 6.5, 5.5, Qt::LeftButton );
QCOMPARE( mVertexTool->mDraggingVertexType, QgsVertexTool::DraggingVertexType::AddingVertex );
QCOMPARE( mVertexTool->mDraggingVertex->layer->id(), mLayerLineZ->id() );
QCOMPARE( mVertexTool->mDraggingVertex->fid, mFidLineZF1 );
QCOMPARE( mVertexTool->mDraggingVertex->vertexId, 2 );
mouseClick( 7, 6, Qt::LeftButton );
QCOMPARE( mLayerLineZ->undoStack()->index(), 4 );
// when adding a vertex in the middle of an existing segment with z values, the z value should be linearly interpolated
QCOMPARE( mLayerLineZ->getFeature( mFidLineZF1 ).geometry().asWkt( 1 ), QStringLiteral( "LineString Z (5 5 1, 6 6 1, 7 6 1, 7 5 1)" ) );
mLayerLineZ->undoStack()->undo();
QCOMPARE( mLayerLineZ->undoStack()->index(), 3 );
QCOMPARE( mLayerLineZ->getFeature( mFidLineZF1 ).geometry().asWkt( 1 ), QStringLiteral( "LineString Z (5 5 1, 6 6 1, 7 5 1)" ) );
mouseClick( 6, 7, Qt::LeftButton );
QCOMPARE( mVertexTool->mDraggingVertexType, QgsVertexTool::DraggingVertexType::AddingVertex );
QCOMPARE( mVertexTool->mDraggingVertex->layer->id(), mLayerLineZ->id() );
QCOMPARE( mVertexTool->mDraggingVertex->fid, mFidLineZF2 );
QCOMPARE( mVertexTool->mDraggingVertex->vertexId, 1 );
mouseClick( 5, 7.5, Qt::LeftButton );
QCOMPARE( mLayerLineZ->undoStack()->index(), 4 );
// when adding a vertex in the middle of an existing segment with z values, the z value should be linearly interpolated
QCOMPARE( mLayerLineZ->getFeature( mFidLineZF2 ).geometry().asWkt( 1 ), QStringLiteral( "LineString Z (5 7 5, 5 7.5 6, 7 7 10)" ) );
mLayerLineZ->undoStack()->undo();
QCOMPARE( mLayerLineZ->undoStack()->index(), 3 );
QCOMPARE( mLayerLineZ->getFeature( mFidLineZF2 ).geometry().asWkt( 1 ), QStringLiteral( "LineString Z (5 7 5, 7 7 10)" ) );
// inserting vertex via double-click
mouseDoubleClick( 5.2, 5.2, Qt::LeftButton );
QCOMPARE( mVertexTool->mDraggingVertexType, QgsVertexTool::DraggingVertexType::AddingVertex );
QCOMPARE( mVertexTool->mDraggingVertex->layer->id(), mLayerLineZ->id() );
QCOMPARE( mVertexTool->mDraggingVertex->fid, mFidLineZF1 );
QCOMPARE( mVertexTool->mDraggingVertex->vertexId, 1 );
mouseClick( 5, 5.5, Qt::LeftButton );
QCOMPARE( mLayerLineZ->undoStack()->index(), 4 );
QCOMPARE( mLayerLineZ->getFeature( mFidLineZF1 ).geometry().asWkt( 1 ), QStringLiteral( "LineString Z (5 5 1, 5 5.5 1, 6 6 1, 7 5 1)" ) );
mLayerLineZ->undoStack()->undo();
QCOMPARE( mLayerLineZ->undoStack()->index(), 3 );
QCOMPARE( mLayerLineZ->getFeature( mFidLineZF1 ).geometry().asWkt( 1 ), QStringLiteral( "LineString Z (5 5 1, 6 6 1, 7 5 1)" ) );
mouseDoubleClick( 5.3, 7, Qt::LeftButton );
QCOMPARE( mVertexTool->mDraggingVertexType, QgsVertexTool::DraggingVertexType::AddingVertex );
QCOMPARE( mVertexTool->mDraggingVertex->layer->id(), mLayerLineZ->id() );
QCOMPARE( mVertexTool->mDraggingVertex->fid, mFidLineZF2 );
QCOMPARE( mVertexTool->mDraggingVertex->vertexId, 1 );
mouseClick( 5, 7.5, Qt::LeftButton );
QCOMPARE( mLayerLineZ->undoStack()->index(), 4 );
QCOMPARE( mLayerLineZ->getFeature( mFidLineZF2 ).geometry().asWkt( 1 ), QStringLiteral( "LineString Z (5 7 5, 5 7.5 6, 7 7 10)" ) );
mLayerLineZ->undoStack()->undo();
QCOMPARE( mLayerLineZ->undoStack()->index(), 3 );
QCOMPARE( mLayerLineZ->getFeature( mFidLineZF2 ).geometry().asWkt( 1 ), QStringLiteral( "LineString Z (5 7 5, 7 7 10)" ) );
// insert vertex at endpoint -- the default z value should be used
// offset of the endpoint marker - currently set as 15px away from the last vertex in direction of the line
const double offsetInMapUnits = 15 * mCanvas->mapSettings().mapUnitsPerPixel();
mouseMove( 5, 5 ); // first we need to move to the vertex
mouseClick( 5 - offsetInMapUnits / M_SQRT2, 5 - offsetInMapUnits / M_SQRT2, Qt::LeftButton );
QCOMPARE( mVertexTool->mDraggingVertexType, QgsVertexTool::DraggingVertexType::AddingEndpoint );
QCOMPARE( mVertexTool->mDraggingVertex->layer->id(), mLayerLineZ->id() );
QCOMPARE( mVertexTool->mDraggingVertex->fid, mFidLineZF1 );
QCOMPARE( mVertexTool->mDraggingVertex->vertexId, 0 );
mouseClick( 5, 4, Qt::LeftButton );
QCOMPARE( mLayerLineZ->undoStack()->index(), 4 );
QCOMPARE( mLayerLineZ->getFeature( mFidLineZF1 ).geometry().asWkt( 1 ), QStringLiteral( "LineString Z (5 4 333, 5 5 1, 6 6 1, 7 5 1)" ) );
mLayerLineZ->undoStack()->undo();
QCOMPARE( mLayerLineZ->undoStack()->index(), 3 );
QCOMPARE( mLayerLineZ->getFeature( mFidLineZF1 ).geometry().asWkt( 1 ), QStringLiteral( "LineString Z (5 5 1, 6 6 1, 7 5 1)" ) );
}
void TestQgsVertexTool::testAddVertexM()
{
QgsSettingsRegistryCore::settingsDigitizingDefaultMValue->setValue( 222 );
// add vertex in linestringM
// ensure changes are made to mLayerLineZ
mLayerLineM->startEditing();
mLayerLineZ->commitChanges();
QCOMPARE( mLayerLineM->undoStack()->index(), 0 );
// in middle of segment
mouseClick( 6.5, 5.5, Qt::LeftButton );
QCOMPARE( mVertexTool->mDraggingVertexType, QgsVertexTool::DraggingVertexType::AddingVertex );
QCOMPARE( mVertexTool->mDraggingVertex->layer->id(), mLayerLineM->id() );
QCOMPARE( mVertexTool->mDraggingVertex->fid, mFidLineMF1 );
QCOMPARE( mVertexTool->mDraggingVertex->vertexId, 2 );
mouseClick( 7, 6, Qt::LeftButton );
QCOMPARE( mLayerLineM->undoStack()->index(), 1 );
// when adding a vertex in the middle of an existing segment with m values, the m value should be linearly interpolated
QCOMPARE( mLayerLineM->getFeature( mFidLineMF1 ).geometry().asWkt( 1 ), QStringLiteral( "LineString M (5 5 1, 6 6 1, 7 6 1, 7 5 1)" ) );
mLayerLineM->undoStack()->undo();
QCOMPARE( mLayerLineM->undoStack()->index(), 0 );
QCOMPARE( mLayerLineM->getFeature( mFidLineMF1 ).geometry().asWkt( 1 ), QStringLiteral( "LineString M (5 5 1, 6 6 1, 7 5 1)" ) );
mouseClick( 6, 7, Qt::LeftButton );
QCOMPARE( mVertexTool->mDraggingVertexType, QgsVertexTool::DraggingVertexType::AddingVertex );
QCOMPARE( mVertexTool->mDraggingVertex->layer->id(), mLayerLineM->id() );
QCOMPARE( mVertexTool->mDraggingVertex->fid, mFidLineMF2 );
QCOMPARE( mVertexTool->mDraggingVertex->vertexId, 1 );
mouseClick( 5, 7.5, Qt::LeftButton );
QCOMPARE( mLayerLineM->undoStack()->index(), 1 );
// when adding a vertex in the middle of an existing segment with m values, the m value should be linearly interpolated
QCOMPARE( mLayerLineM->getFeature( mFidLineMF2 ).geometry().asWkt( 1 ), QStringLiteral( "LineString M (5 7 5, 5 7.5 6, 7 7 10)" ) );
mLayerLineM->undoStack()->undo();
QCOMPARE( mLayerLineM->undoStack()->index(), 0 );
QCOMPARE( mLayerLineM->getFeature( mFidLineMF2 ).geometry().asWkt( 1 ), QStringLiteral( "LineString M (5 7 5, 7 7 10)" ) );
// inserting vertex via double-click
mouseDoubleClick( 5.2, 5.2, Qt::LeftButton );
QCOMPARE( mVertexTool->mDraggingVertexType, QgsVertexTool::DraggingVertexType::AddingVertex );
QCOMPARE( mVertexTool->mDraggingVertex->layer->id(), mLayerLineM->id() );
QCOMPARE( mVertexTool->mDraggingVertex->fid, mFidLineMF1 );
QCOMPARE( mVertexTool->mDraggingVertex->vertexId, 1 );
mouseClick( 5, 5.5, Qt::LeftButton );
QCOMPARE( mLayerLineM->undoStack()->index(), 1 );
QCOMPARE( mLayerLineM->getFeature( mFidLineMF1 ).geometry().asWkt( 1 ), QStringLiteral( "LineString M (5 5 1, 5 5.5 1, 6 6 1, 7 5 1)" ) );
mLayerLineM->undoStack()->undo();
QCOMPARE( mLayerLineM->undoStack()->index(), 0 );
QCOMPARE( mLayerLineM->getFeature( mFidLineMF1 ).geometry().asWkt( 1 ), QStringLiteral( "LineString M (5 5 1, 6 6 1, 7 5 1)" ) );
mouseDoubleClick( 5.3, 7, Qt::LeftButton );
QCOMPARE( mVertexTool->mDraggingVertexType, QgsVertexTool::DraggingVertexType::AddingVertex );
QCOMPARE( mVertexTool->mDraggingVertex->layer->id(), mLayerLineM->id() );
QCOMPARE( mVertexTool->mDraggingVertex->fid, mFidLineMF2 );
QCOMPARE( mVertexTool->mDraggingVertex->vertexId, 1 );
mouseClick( 5, 7.5, Qt::LeftButton );
QCOMPARE( mLayerLineM->undoStack()->index(), 1 );
QCOMPARE( mLayerLineM->getFeature( mFidLineMF2 ).geometry().asWkt( 1 ), QStringLiteral( "LineString M (5 7 5, 5 7.5 6, 7 7 10)" ) );
mLayerLineM->undoStack()->undo();
QCOMPARE( mLayerLineM->undoStack()->index(), 0 );
QCOMPARE( mLayerLineM->getFeature( mFidLineMF2 ).geometry().asWkt( 1 ), QStringLiteral( "LineString M (5 7 5, 7 7 10)" ) );
// insert vertex at endpoint
// offset of the endpoint marker - currently set as 15px away from the last vertex in direction of the line
const double offsetInMapUnits = 15 * mCanvas->mapSettings().mapUnitsPerPixel();
mouseMove( 5, 5 ); // first we need to move to the vertex
mouseClick( 5 - offsetInMapUnits / M_SQRT2, 5 - offsetInMapUnits / M_SQRT2, Qt::LeftButton );
QCOMPARE( mVertexTool->mDraggingVertexType, QgsVertexTool::DraggingVertexType::AddingEndpoint );
QCOMPARE( mVertexTool->mDraggingVertex->layer->id(), mLayerLineM->id() );
QCOMPARE( mVertexTool->mDraggingVertex->fid, mFidLineMF1 );
QCOMPARE( mVertexTool->mDraggingVertex->vertexId, 0 );
mouseClick( 5, 4, Qt::LeftButton );
QCOMPARE( mLayerLineM->undoStack()->index(), 1 );
QCOMPARE( mLayerLineM->getFeature( mFidLineMF1 ).geometry().asWkt( 1 ), QStringLiteral( "LineString M (5 4 222, 5 5 1, 6 6 1, 7 5 1)" ) );
mLayerLineM->undoStack()->undo();
QCOMPARE( mLayerLineM->undoStack()->index(), 0 );
QCOMPARE( mLayerLineM->getFeature( mFidLineMF1 ).geometry().asWkt( 1 ), QStringLiteral( "LineString M (5 5 1, 6 6 1, 7 5 1)" ) );
}
void TestQgsVertexTool::testAddVertexAtEndpoint()
{
// offset of the endpoint marker - currently set as 15px away from the last vertex in direction of the line