// Copyright (C) Stichting Deltares 2024. All rights reserved.
//
// This file is part of the Dam Engine.
//
// The Dam Engine is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
//
// All names, logos, and references to "Deltares" are registered trademarks of
// Stichting Deltares and remain full property of Stichting Deltares at all times.
// All rights reserved.
using System.Collections.Generic;
using System.Linq;
using Deltares.DamEngine.Data.Geometry;
using Deltares.DamEngine.Data.GeometryExport;
using Deltares.DamEngine.Data.Geotechnics;
using Deltares.DamEngine.Data.Standard;
using Deltares.DamEngine.TestHelpers.Factories;
using NUnit.Framework;
namespace Deltares.DamEngine.Data.Tests.Geotechnics;
[TestFixture]
public class SoilProfile2DSurfaceLineHelperTests
{
private static IEnumerable SurfaceLinesTestCases
{
get
{
yield return new TestCaseData(
new TestCaseSurfaceLine
{
SurfaceLine = FactoryForSoilProfiles.CreateSurfaceLine(new[]
{
new GeometryPoint(0, 0),
new GeometryPoint(5, 10),
new GeometryPoint(10, 10)
}),
SurfaceCount = 3,
TestNumber = 1
}).SetName("Surface is same as original surfaceline of soil profile");
yield return new TestCaseData(
new TestCaseSurfaceLine
{
SurfaceLine = FactoryForSoilProfiles.CreateSurfaceLine(new[]
{
new GeometryPoint(0, 1),
new GeometryPoint(5, 11),
new GeometryPoint(10, 11)
}),
SurfaceCount = 4,
TestNumber = 2
}).SetName("Surface is completely above original surfaceline of soil profile");
yield return new TestCaseData(
new TestCaseSurfaceLine
{
SurfaceLine = FactoryForSoilProfiles.CreateSurfaceLine(new[]
{
new GeometryPoint(0, -1),
new GeometryPoint(5, 9),
new GeometryPoint(10, 9)
}),
SurfaceCount = 3,
TestNumber = 3
}).SetName("Surface is completely below original surfaceline of soil profile");
yield return new TestCaseData(
new TestCaseSurfaceLine
{
SurfaceLine = FactoryForSoilProfiles.CreateSurfaceLine(new[]
{
new GeometryPoint(0, 1),
new GeometryPoint(5, 9),
new GeometryPoint(10, 11)
}),
SurfaceCount = 5,
TestNumber = 4
}).SetName("Surface is partially above and below original surfaceline of soil profile");
}
}
[Test]
[TestCase(PositionToSoilProfile2D.LeftOfSoilProfile, false)]
[TestCase(PositionToSoilProfile2D.RightOfSoilProfile, false)]
[TestCase(PositionToSoilProfile2D.OnSoilProfile, true)]
[TestCase(PositionToSoilProfile2D.InsideOfSoilProfile, true)]
public void GivenSurfaceLineAndSoilProfile2D_WhenCheckIsSurfaceLineInsideSoilProfile2D_ThenReturnCorrectResult(PositionToSoilProfile2D positionToSoilProfile2D, bool result)
{
// Given
SoilProfile2D soilProfile2D = FactoryForSoilProfiles.CreateSoilProfile2DWithTwoLayers();
SurfaceLine2 surfaceLine = CreateSurfaceLineForSoilProfile2D(soilProfile2D, positionToSoilProfile2D);
// When-Then
Assert.That(SoilProfile2DSurfaceLineHelper.IsSurfaceLineInsideSoilProfile2D(surfaceLine, soilProfile2D), Is.EqualTo(result));
}
[Test, TestCaseSource(nameof(SurfaceLinesTestCases))]
public void GivenSoilProfile2DWhenCombiningWithSurfaceLineThenCorrectNewSoilProfile2DIsCreated(TestCaseSurfaceLine testCaseSurfaceLine)
{
// Given
SoilProfile2D soilProfile2D = FactoryForSoilProfiles.CreateSoilProfile2DWithThreeLayers();
var defaultSoil = new Soil
{
Name = "dikemat"
};
SoilProfile2D newSoilProfile2D = SoilProfile2DSurfaceLineHelper.CombineSurfaceLineWithSoilProfile2D(
testCaseSurfaceLine.SurfaceLine.Geometry, soilProfile2D, defaultSoil);
Assert.That(newSoilProfile2D, Is.Not.Null);
Assert.That(newSoilProfile2D.Surfaces, Has.Count.EqualTo(testCaseSurfaceLine.SurfaceCount));
}
[Test]
[TestCase(0, true)] // Surface line is above the bottom of the soil profile
[TestCase(-10, true)] // Surface line is on and above the bottom of the soil profile
[TestCase(-12, false)] // Surface line is partly below the bottom of the soil profile
[TestCase(-20, false)] // Surface line is below the bottom of the soil profile
public void GivenSoilProfile2DWhenCallingIsSurfaceLineAboveBottomSoilProfile2DThenCorrectResponseIsReturned(double offset, bool response)
{
SurfaceLine2 surfaceLine = FactoryForSurfaceLines.CreateSurfaceLineDike(offset);
SoilProfile2D soilProfile2D = FactoryForSoilProfiles.CreateSoilProfile2DWithThreeLayers();
Assert.That(SoilProfile2DSurfaceLineHelper.IsSurfaceLineAboveBottomSoilProfile2D(surfaceLine, soilProfile2D), Is.EqualTo(response));
}
///
/// Test case class for GivenSoilProfile2DWhenCombiningWithSurfaceLineThenCorrectNewSoilProfile2DIsCreated()
///
public class TestCaseSurfaceLine
{
public SurfaceLine2 SurfaceLine { get; init; }
public int SurfaceCount { get; init; }
public int TestNumber { get; set; }
}
///
/// Test case class for GivenComplexSoilProfile2D_WhenCombiningWithZigZagSurfaceLine_ThenCorrectNewSoilProfile2DIsCreated
///
public class TestCaseZigZagSurfaceLine
{
public SurfaceLine2 GivenZigZagSurfaceLine { get; init; }
public double GivenXStartOfSoilProfile { get; init; }
public int ExpectedSurfaceCount { get; init; }
public SoilLayer2D ExpectedSurface1 { get; init; }
public SoilLayer2D ExpectedSurface2 { get; init; }
public SoilLayer2D ExpectedSurface3 { get; init; }
public SoilLayer2D ExpectedSurface4 { get; init; }
public SoilLayer2D ExpectedSurface5 { get; init; }
public SoilLayer2D ExpectedSurface6 { get; init; }
public SoilLayer2D ExpectedExtendedSurface1 { get; init; }
public SoilLayer2D ExpectedExtendedSurface3 { get; init; }
public SoilLayer2D ExpectedExtendedSurface4 { get; init; }
public SoilLayer2D ExpectedExtendedSurface6 { get; init; }
public SoilLayer2D ExpectedFilling1 { get; init; }
public SoilLayer2D ExpectedFilling2 { get; init; }
public int TestNumber { get; init; }
}
private static SurfaceLine2 CreateSurfaceLineForSoilProfile2D(SoilProfile2D soilProfile2D, PositionToSoilProfile2D positionToSoilProfile2D)
{
GeometryPoint geometryPoint = soilProfile2D.Geometry.SurfaceLine.Points.First();
var leftPoint = new GeometryPoint(geometryPoint.X, geometryPoint.Z);
geometryPoint = soilProfile2D.Geometry.SurfaceLine.Points.Last();
var rightPoint = new GeometryPoint(geometryPoint.X, geometryPoint.Z);
var middlePoint = new GeometryPoint((leftPoint.X + rightPoint.X) / 2, (leftPoint.Z + rightPoint.Z) / 2);
switch (positionToSoilProfile2D)
{
case PositionToSoilProfile2D.LeftOfSoilProfile:
leftPoint.X -= 1;
break;
case PositionToSoilProfile2D.RightOfSoilProfile:
rightPoint.X += 1;
break;
case PositionToSoilProfile2D.OnSoilProfile:
break;
case PositionToSoilProfile2D.InsideOfSoilProfile:
leftPoint.X += 1;
rightPoint.X -= 1;
break;
}
SurfaceLine2 surfaceLine = FactoryForSoilProfiles.CreateSurfaceLine(new[]
{
leftPoint,
middlePoint,
rightPoint
});
surfaceLine.CharacteristicPoints.Add(new CharacteristicPoint(surfaceLine.CharacteristicPoints, leftPoint));
surfaceLine.CharacteristicPoints.Annotate(0, CharacteristicPointType.SurfaceLevelOutside);
surfaceLine.CharacteristicPoints.Add(new CharacteristicPoint(surfaceLine.CharacteristicPoints, rightPoint));
surfaceLine.CharacteristicPoints.Annotate(1, CharacteristicPointType.SurfaceLevelInside);
return surfaceLine;
}
private static void CheckSoilProfileContainsSoilLayer(SoilProfile2D soilProfile2D, SoilLayer2D expectedSoilLayer)
{
if (expectedSoilLayer == null)
{
return;
}
int indexLayer = -1;
// Find a point within the expected soil layer
double[] xMin = expectedSoilLayer.GeometrySurface.OuterLoop.CalcPoints.Select(p => p.X).OrderBy(x => x).Distinct().ToArray();
double[] zMin = expectedSoilLayer.GeometrySurface.OuterLoop.CalcPoints.Select(p => p.Z).OrderBy(z => z).Distinct().ToArray();
var gravityPoint = new Point2D
{
X = xMin[0] + 2,
Z = zMin[0] + 0.2
};
for (var i = 0; i < soilProfile2D.Surfaces.Count; i++)
{
bool find = soilProfile2D.Surfaces[i].GeometrySurface.OuterLoop.IsPointInLoopArea(gravityPoint);
if (find)
{
indexLayer = i;
break;
}
}
Assert.That(indexLayer, Is.GreaterThan(-1), "The soil layer was not found ");
Assert.Multiple(() =>
{
Assert.That(soilProfile2D.Surfaces[indexLayer].SoilName, Is.EqualTo(expectedSoilLayer.SoilName));
Assert.That(soilProfile2D.Geometry.Surfaces[indexLayer].OuterLoop.CalcPoints, Has.Count.EqualTo(expectedSoilLayer.GeometrySurface.OuterLoop.CalcPoints.Count));
Assert.That(soilProfile2D.Geometry.Surfaces[indexLayer].OuterLoop.CurveList, Has.Count.EqualTo(expectedSoilLayer.GeometrySurface.OuterLoop.CurveList.Count));
Assert.That(soilProfile2D.Surfaces[indexLayer].GeometrySurface.OuterLoop.CalcPoints, Has.Count.EqualTo(expectedSoilLayer.GeometrySurface.OuterLoop.CalcPoints.Count));
Assert.That(soilProfile2D.Surfaces[indexLayer].GeometrySurface.OuterLoop.CurveList, Has.Count.EqualTo(expectedSoilLayer.GeometrySurface.OuterLoop.CurveList.Count));
});
foreach (GeometryCurve curve in expectedSoilLayer.GeometrySurface.OuterLoop.CurveList)
{
Assert.That(IsCurvePresentInSoilLayer(soilProfile2D.Surfaces[indexLayer], curve), Is.True);
}
foreach (Point2D point in expectedSoilLayer.GeometrySurface.OuterLoop.CalcPoints)
{
Assert.That(IsPointPresentInSoilLayer(soilProfile2D.Surfaces[indexLayer], point), Is.True);
}
}
private static bool IsPointPresentInSoilLayer(SoilLayer2D soilLayer, Point2D point)
{
return soilLayer.GeometrySurface.OuterLoop.Points.Any(p => p.X.IsNearEqual(point.X) && p.Z.IsNearEqual(point.Z));
}
private static bool IsCurvePresentInSoilLayer(SoilLayer2D soilLayer, GeometryCurve curve)
{
return soilLayer.GeometrySurface.OuterLoop.CurveList.Any(c => (c.HeadPoint.X.IsNearEqual(curve.HeadPoint.X) && c.HeadPoint.Z.IsNearEqual(curve.HeadPoint.Z)
&& c.EndPoint.X.IsNearEqual(curve.EndPoint.X) && c.EndPoint.Z.IsNearEqual(curve.EndPoint.Z)) ||
(c.EndPoint.X.IsNearEqual(curve.HeadPoint.X) && c.EndPoint.Z.IsNearEqual(curve.HeadPoint.Z) &&
c.HeadPoint.X.IsNearEqual(curve.EndPoint.X) && c.HeadPoint.Z.IsNearEqual(curve.EndPoint.Z)));
}
[TestCase (0.0, 0, true)]
[TestCase (0.0, 3, false)]
[TestCase (1.0, 0, false)]
[Test]
public void TestIsLayerAboveOriginalSurfaceLine(double surfaceZ, int layer, bool expectedLayerAbove)
{
// Given
SurfaceLine2 surfaceLine = FactoryForSurfaceLines.CreateHorizontalSurfaceLine(surfaceZ, -50.0, 60.0);
SoilProfile2D soilProfile = FactoryForSoilProfiles.CreateSoilProfile2DWithSixSurfacesFormingTwoLayers();
soilProfile.Geometry.RegenerateGeometry();
// When
bool result = SoilProfile2DSurfaceLineHelper.IsLayerAboveOriginalSurfaceLine(soilProfile.Surfaces[layer], surfaceLine.Geometry);
// Then
Assert.That(result, Is.EqualTo(expectedLayerAbove));
}
[Test]
public void GivenSoilProfile2DWithInnerLoopsWhenCombiningWithSurfaceLineThenCorrectNewSoilProfile2DIsCreated()
{
// Given
SurfaceLine2 surfaceLine = FactoryForSoilProfiles.CreateSurfaceLine(new[]
{
new GeometryPoint(-50, -5),
new GeometryPoint(-20, -5),
new GeometryPoint(-5, 15),
new GeometryPoint(10, 15),
new GeometryPoint(30, 5),
new GeometryPoint(50, 5)
});
SoilProfile2D soilProfile2D = FactoryForSoilProfiles.CreateSoilProfile2DWithThreeLayersOfWhichTwoAreInnerLoops();
var defaultSoil = new Soil
{
Name = "dikemat"
};
// When
SoilProfile2D newSoilProfile2D = SoilProfile2DSurfaceLineHelper.CombineSurfaceLineWithSoilProfile2D(
surfaceLine.Geometry, soilProfile2D, defaultSoil);
//Then
Assert.That(newSoilProfile2D, Is.Not.Null);
Assert.Multiple(() =>
{
Assert.That(newSoilProfile2D.Surfaces, Has.Count.EqualTo(4));
Assert.That(newSoilProfile2D.Geometry.Surfaces, Has.Count.EqualTo(4));
Assert.That(newSoilProfile2D.Geometry.Points, Has.Count.EqualTo(19));
Assert.That(newSoilProfile2D.Geometry.Curves, Has.Count.EqualTo(21));
Assert.That(newSoilProfile2D.Geometry.InnerLoopsCount, Is.EqualTo(1));
});
}
}