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