// Copyright (C) Stichting Deltares 2025. 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; using System.Collections.Generic; using System.Linq; using Deltares.DamEngine.Data.Geometry; using Deltares.DamEngine.Data.Standard; using Deltares.DamEngine.Data.Standard.Language; using Deltares.DamEngine.Data.Standard.Validation; namespace Deltares.DamEngine.Data.Geotechnics; /// /// 2D Soil Profile Object /// public class SoilProfile2D : SoilProfile { private const double deviation = 1E-05; private const double toleranceAlmostEqual = 1e-07; private readonly List surfaces = new List(); /// /// Initializes a new instance of the class. /// public SoilProfile2D() { Name = LocalizationManager.GetTranslatedText(this, "DefaultNameSoilProfile2D"); } /// /// Gets the surfaces. /// /// /// The surfaces. /// [Validate] public virtual IList Surfaces { get { return surfaces; } } /// /// Gets or sets the geometry. /// /// /// The geometry. /// public GeometryData Geometry { get; set; } = new GeometryData(); /// /// Gets the soil profile 1D at the given X. /// If the given X coincides with a vertical layer separation and both profiles at left and at right of the separation /// are different, then the X is shifted by 1 mm to the right /// /// The x. /// Soil Profile 1D public virtual SoilProfile1D GetSoilProfile1D(double x) { const double diff = 0.001; if (Geometry.Surfaces.Count == 0) { Geometry.Right = Geometry.MaxGeometryPointsX; Geometry.Left = Geometry.MinGeometryPointsX; } // At the end of a geometry, there are no layers to be found beyond that end. In that case get the layers just before // the end of the geometry. if (x.IsGreaterThanOrEqualTo(Geometry.Right, toleranceAlmostEqual)) { x = Geometry.Right - diff; } if (Geometry.Left.IsGreaterThanOrEqualTo(x, toleranceAlmostEqual)) { x = Geometry.Left + diff; } bool isXShiftedToRight; double xRelevant = x; do { isXShiftedToRight = AreLayersWithVerticalPartPresentAtGivenX(xRelevant) && !AreSoilProfilesIdentical(xRelevant - diff / 2, xRelevant + diff / 2); if (isXShiftedToRight) { xRelevant += diff; } } while (isXShiftedToRight && xRelevant < Geometry.Right); return DetermineSoilProfile1DAtX(xRelevant); } /// /// Clone the soil profile 2D /// /// The cloned SoilProfile2D public SoilProfile2D Clone() { var clonedSoilProfile2D = new SoilProfile2D { Name = Name }; clonedSoilProfile2D.Geometry = Geometry.Clone(); foreach (SoilLayer2D surface in Surfaces) { SoilLayer2D clonedSurface = surface.Clone(clonedSoilProfile2D.Geometry); clonedSoilProfile2D.Surfaces.Add(clonedSurface); } foreach (PreConsolidationStress preconsolidationStress in PreconsolidationStresses) { clonedSoilProfile2D.PreconsolidationStresses.Add((PreConsolidationStress) preconsolidationStress.Clone()); } return clonedSoilProfile2D; } /// /// Finds a SoilLayer2D based on its outer loop /// /// /// the layer or when not found null public SoilLayer2D FindSoilLayer2DByItsOuterLoop(GeometryLoop outerLoop) { foreach (SoilLayer2D soilLayer2D in Surfaces) { if (soilLayer2D.GeometrySurface.OuterLoop.HasSameCurves(outerLoop)) { return soilLayer2D; } } return null; } /// /// Get the soil layer from the old surfaces /// /// /// /// The 2D soil layer public static SoilLayer2D DetermineOriginalLayerFromOldSurfaces(GeometrySurface geometrySurface, IEnumerable oldSurfaces) { var point = new Point2D(0.0, 0.0); var isPointInOuterLoopAndOldSurface = false; point = IsPointInOuterLoopAndOldSurface(geometrySurface, oldSurfaces, point, ref isPointInOuterLoopAndOldSurface); if (!isPointInOuterLoopAndOldSurface) { isPointInOuterLoopAndOldSurface = IsPointInOldSurfaceJustBelowTopOfNewGeometryWithinItsLimits(geometrySurface, oldSurfaces, point, isPointInOuterLoopAndOldSurface); } if (isPointInOuterLoopAndOldSurface) { if (IsPointInPreviousOuterLoopOfOldSurface(oldSurfaces, point, out SoilLayer2D soilLayer2D)) { return soilLayer2D; } if (IsPointInOuterLoopOfOldSurface(oldSurfaces, point, out SoilLayer2D originalLayerFromOldSurfaces1)) { return originalLayerFromOldSurfaces1; } } return null; } /// /// Returns a that represents this instance. /// /// /// A that represents this instance. /// public override string ToString() { return Name; } private static bool IsPointInOuterLoopOfOldSurface(IEnumerable oldSurfaces, Point2D point, out SoilLayer2D originalLayerFromOldSurfaces1) { originalLayerFromOldSurfaces1 = null; foreach (SoilLayer2D oldSurface in oldSurfaces) { GeometryLoop outerLoop = oldSurface.GeometrySurface.OuterLoop; if (outerLoop != null && outerLoop.CurveList.Count > 2 && Routines2D.CheckIfPointIsInPolygon(outerLoop, point.X, point.Z) == PointInPolygon.InsidePolygon) { var isPointInOuterLoopOfOldSurface = true; foreach (GeometryLoop innerLoop in oldSurface.GeometrySurface.InnerLoops) { if (Routines2D.CheckIfPointIsInPolygon(innerLoop, point.X, point.Z) == PointInPolygon.InsidePolygon) { isPointInOuterLoopOfOldSurface = false; } } if (isPointInOuterLoopOfOldSurface) { originalLayerFromOldSurfaces1 = oldSurface; return true; } } } return false; } private static bool IsPointInPreviousOuterLoopOfOldSurface(IEnumerable oldSurfaces, Point2D point, out SoilLayer2D soilLayer2D) { soilLayer2D = null; foreach (SoilLayer2D oldSurface in oldSurfaces) { GeometryLoop previousOuterLoop = oldSurface.GeometrySurface.PreviousOuterLoop; if (previousOuterLoop != null && previousOuterLoop.CurveList.Count > 2 && Routines2D.CheckIfPointIsInPolygon(previousOuterLoop, point.X, point.Z) == PointInPolygon.InsidePolygon) { var isPointInPreviousOuterLoopOfOldSurface = true; foreach (GeometryLoop previousInnerLoop in oldSurface.GeometrySurface.PreviousInnerLoops) { if (Routines2D.CheckIfPointIsInPolygon(previousInnerLoop, point.X, point.Z) == PointInPolygon.InsidePolygon) { isPointInPreviousOuterLoopOfOldSurface = false; } } if (isPointInPreviousOuterLoopOfOldSurface) { soilLayer2D = oldSurface; return true; } } } return false; } private static bool IsPointInOldSurfaceJustBelowTopOfNewGeometryWithinItsLimits(GeometrySurface geometrySurface, IEnumerable oldSurfaces, Point2D point, bool isPointInOuterLoopAndOldSurface) { GeometryPointString topGeometrySurface = geometrySurface.DetermineTopGeometrySurface(); topGeometrySurface.SortPointsByXAscending(); Point2D geometryPoint1 = topGeometrySurface[0]; geometryPoint1.X -= deviation; geometryPoint1.Z -= deviation; Point2D geometryPoint2 = topGeometrySurface[checked(topGeometrySurface.Count - 1)]; geometryPoint2.X += deviation; geometryPoint2.Z -= deviation; bool isPoint1WithinOldSurfaces = IsPointWithinOldSurfaces(geometryPoint1, oldSurfaces, -deviation); bool isPoint2WithinOldSurfaces = IsPointWithinOldSurfaces(geometryPoint2, oldSurfaces, -deviation); double d = double.NaN; if (isPoint1WithinOldSurfaces && !isPoint2WithinOldSurfaces) { point.X = geometryPoint1.X; point.Z = geometryPoint1.Z; isPointInOuterLoopAndOldSurface = true; d = geometryPoint1.X + deviation; } if (!isPoint1WithinOldSurfaces && isPoint2WithinOldSurfaces) { point.X = geometryPoint2.X; point.Z = geometryPoint2.Z; isPointInOuterLoopAndOldSurface = true; d = geometryPoint2.X - deviation; } if (!double.IsNaN(d)) { double xminFromSurfaces = DetermineXminFromSurfaces(oldSurfaces); double xmaxFromSurfaces = DetermineXmaxFromSurfaces(oldSurfaces); if (d <= xmaxFromSurfaces && d >= xminFromSurfaces) { isPointInOuterLoopAndOldSurface = false; } } return isPointInOuterLoopAndOldSurface; } private static Point2D IsPointInOuterLoopAndOldSurface(GeometrySurface geometrySurface, IEnumerable oldSurfaces, Point2D point, ref bool isPointInOuterLoopAndOldSurface) { foreach (GeometryCurve curve in geometrySurface.OuterLoop.CurveList) { point = new Point2D((curve.HeadPoint.X + curve.EndPoint.X) / 2.0, (curve.HeadPoint.Z + curve.EndPoint.Z) / 2.0); if (IsPointWithinOldSurfaces(point, oldSurfaces, deviation) || IsPointWithinOldSurfaces(point, oldSurfaces, -deviation)) { point.Z += deviation; if (Routines2D.CheckIfPointIsInPolygon(geometrySurface.OuterLoop, point.X, point.Z) == PointInPolygon.InsidePolygon) { isPointInOuterLoopAndOldSurface = true; break; } point.Z -= 2 * deviation; if (Routines2D.CheckIfPointIsInPolygon(geometrySurface.OuterLoop, point.X, point.Z) == PointInPolygon.InsidePolygon) { isPointInOuterLoopAndOldSurface = true; break; } } } return point; } private static double DetermineXminFromSurfaces(IEnumerable oldSurfaces) { var xminFromSurfaces = double.MaxValue; foreach (SoilLayer2D oldSurface in oldSurfaces) { xminFromSurfaces = Math.Min(xminFromSurfaces, oldSurface.GeometrySurface.OuterLoop.GetMinX()); } return xminFromSurfaces; } private static double DetermineXmaxFromSurfaces(IEnumerable oldSurfaces) { var xmaxFromSurfaces = double.MinValue; foreach (SoilLayer2D oldSurface in oldSurfaces) { xmaxFromSurfaces = Math.Max(xmaxFromSurfaces, oldSurface.GeometrySurface.OuterLoop.GetMaxX()); } return xmaxFromSurfaces; } private static bool IsPointWithinOldSurfaces(Point2D point, IEnumerable oldSurfaces, double verticalShift) { point.Z += verticalShift; var shiftedPoint = new Point2D(point.X, point.Z); foreach (SoilLayer2D oldSurface in oldSurfaces) { GeometryLoop outerLoop = oldSurface.GeometrySurface.OuterLoop; List innerLoops = oldSurface.GeometrySurface.InnerLoops; bool isPointInSurface = IsPointInGivenOuterLoopOfOldSurface(shiftedPoint, outerLoop, innerLoops); if (!isPointInSurface) { GeometryLoop previousOuterLoop = oldSurface.GeometrySurface.PreviousOuterLoop; List previousInnerLoops = oldSurface.GeometrySurface.PreviousInnerLoops; isPointInSurface = IsPointInGivenOuterLoopOfOldSurface(shiftedPoint, previousOuterLoop, previousInnerLoops); } if (isPointInSurface) { return true; } } return false; } private static bool IsPointInGivenOuterLoopOfOldSurface(Point2D point, GeometryLoop outerLoop, List innerLoops) { var isPointInSurface = false; if (outerLoop != null && Routines2D.CheckIfPointIsInPolygon(outerLoop, point.X, point.Z) == PointInPolygon.InsidePolygon) { isPointInSurface = true; // Make sure the point is in the outer loop, not in the inner loop(s). foreach (GeometryLoop innerLoop in innerLoops) { if (Routines2D.CheckIfPointIsInPolygon(innerLoop, point.X, point.Z) == PointInPolygon.InsidePolygon) { return false; } } } return isPointInSurface; } private SoilProfile1D DetermineSoilProfile1DAtX(double x) { var soilProfile = new SoilProfile1D { Name = "Generated at x = " + x.ToString("F3") + " from " + Name }; var detector = new LayerDetector(Surfaces); detector.DetermineMaterials(x); if (detector.LayerList.Count > 0) { soilProfile.BottomLevel = detector.LayerList[detector.LayerList.Count - 1].EndPoint.Z; for (var i = 0; i < detector.LayerList.Count; i++) { var layer = new SoilLayer1D(detector.LayerList[i].Soil, detector.LayerList[i].StartPoint.Z) { IsAquifer = detector.LayerList[i].IsAquifer, WaterpressureInterpolationModel = detector.LayerList[i].WaterpressureInterpolationModel, Name = detector.LayerList[i].Name, SoilName = detector.LayerList[i].SoilName }; soilProfile.Layers.Add(layer); } } return soilProfile; } private bool AreLayersWithVerticalPartPresentAtGivenX(double xCoordinate) { return Surfaces.Any(surface => surface.GeometrySurface.OuterLoop.CurveList.Any (curve => curve.HeadPoint.X.IsNearEqual(xCoordinate, toleranceAlmostEqual) && curve.EndPoint.X.IsNearEqual(xCoordinate, toleranceAlmostEqual))); } private bool AreSoilProfilesIdentical(double x1, double x2) { SoilProfile1D soilProfile1 = DetermineSoilProfile1DAtX(x1); SoilProfile1D soilProfile2 = DetermineSoilProfile1DAtX(x2); bool isIdentical = soilProfile1.Layers.Count == soilProfile2.Layers.Count; if (!isIdentical) { return false; } for (var i = 0; i < soilProfile1.Layers.Count; i++) { isIdentical = isIdentical && soilProfile1.Layers[i].TopLevel.IsNearEqual(soilProfile2.Layers[i].TopLevel, toleranceAlmostEqual); isIdentical = isIdentical && soilProfile1.Layers[i].Soil == soilProfile2.Layers[i].Soil; isIdentical = isIdentical && soilProfile1.Layers[i].SoilName == soilProfile2.Layers[i].SoilName; isIdentical = isIdentical && soilProfile1.Layers[i].IsAquifer == soilProfile2.Layers[i].IsAquifer; isIdentical = isIdentical && soilProfile1.Layers[i].WaterpressureInterpolationModel == soilProfile2.Layers[i].WaterpressureInterpolationModel; } return isIdentical; } }