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