// 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)); }); } }