// 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 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) { Point2D point = geometrySurface.DetermineValidTestPointBasedOnNewSurface(); // If the test point is beyond the x-limits of the old surfaces, there cannot be an old layer. if (IsSurfaceLeftOrRightOfOldSurfaces(point, oldSurfaces)) { return null; } return DetermineOldSurfaceFromTestPoint(oldSurfaces, point); } /// /// Determines whether the given point is left or right of the old surfaces. /// /// /// /// true when the given point is left or right of the old surfaces else false. public static bool IsSurfaceLeftOrRightOfOldSurfaces(Point2D point, IEnumerable oldSurfaces) { // If the test point is beyound the x-limits of the old surfaces, there can not be an old layer double xminFromSurfaces = DetermineXminFromSurfaces(oldSurfaces); double xmaxFromSurfaces = DetermineXmaxFromSurfaces(oldSurfaces); if (point.X > xmaxFromSurfaces || point.X < xminFromSurfaces) { return true; } return false; } /// /// Returns a that represents this instance. /// /// /// A that represents this instance. /// public override string ToString() { return Name; } /// /// Determine the old surface from the test point. /// /// /// /// The found old surface/layer else null. public static SoilLayer2D DetermineOldSurfaceFromTestPoint(IEnumerable oldSurfaces, Point2D point) { 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; // Make sure point is NOT in (one of) the inner loop(s). foreach (GeometryLoop innerLoop in oldSurface.GeometrySurface.InnerLoops) { if (Routines2D.CheckIfPointIsInPolygon(innerLoop, point.X, point.Z) == PointInPolygon.InsidePolygon) { isPointInOuterLoopOfOldSurface = false; } } if (isPointInOuterLoopOfOldSurface) { return oldSurface; } } } return null; } 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 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; } }