// 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.General;
using Deltares.DamEngine.Data.General.PlLines;
using Deltares.DamEngine.Data.Geometry;
using Deltares.DamEngine.Data.Geotechnics;
using Deltares.DamEngine.Data.Standard;
namespace Deltares.DamEngine.Calculators.Uplift;
///
/// Determie uplift location and uplift factor
///
public class UpliftLocationDeterminator
{
public PlLines PlLines { get; set; }
public SurfaceLine2 SurfaceLine { get; set; }
public SoilProfile1D SoilProfile { get; set; }
public SoilProfile2D SoilProfile2D { get; set; }
public Soil DikeEmbankmentMaterial { get; set; }
// public SoilbaseDB SoilBaseDB { get; set; }
public SoilList SoilList { get; set; }
///
/// Get location nearest dike with and upliftfactor lower than required
///
/// location and upliftfactor
public UpliftLocationAndResult GetLocationAndResult(double upliftCriterion)
{
ThrowIfNoPlLinesDefined();
ThrowIfNoSurfaceLinDefined();
ThrowIfNoSoilProfileDefined();
return GetLocationInPolderNearestDikeWithUpliftFactorLowerThanRequired(upliftCriterion);
}
///
/// Get location nearest dike with and upliftfactor lower than required
///
/// location and upliftfactor
public UpliftLocationAndResult GetLocationInPolderNearestDikeWithUpliftFactorLowerThanRequired(double upliftCriterion)
{
ThrowIfNoPlLinesDefined();
ThrowIfNoSurfaceLinDefined();
ThrowIfNoSoilProfileDefined();
Point2D startSurfacePoint = SurfaceLine.GetDikeToeInward();
IEnumerable relevantSurfacePointsList = from Point2D point in SurfaceLine.Geometry.Points
where point.X >= startSurfacePoint.X
orderby point.X
select point;
var foundUpliftFactor = false;
UpliftLocationAndResult upliftLocationAndResult = null;
foreach (Point2D surfacePoint in relevantSurfacePointsList)
{
upliftLocationAndResult = GetUpliftFactorAtPoint(surfacePoint);
if ((upliftLocationAndResult != null) && (upliftLocationAndResult.UpliftFactor < upliftCriterion))
{
foundUpliftFactor = true;
upliftLocationAndResult.X = surfacePoint.X;
upliftLocationAndResult.Z = surfacePoint.Z;
break;
}
}
return foundUpliftFactor ? upliftLocationAndResult : null;
}
///
/// Calculate upliftfactor for given point
///
///
/// location and upliftfactor
public UpliftLocationAndResult GetUpliftFactorAtPoint(Point2D point)
{
const double toleranceAlmostEqual = 1e-09;
SoilProfile1D soilProfileInCurrentPoint = GetSoilProfileBelowPoint(point.X);
var upliftFactorForInBetweenSandLayer = double.MaxValue;
if (soilProfileInCurrentPoint.InBetweenAquiferLayer != null)
{
// Check if inbetween sandlayer below surface
double topInBetweenSandLayer = soilProfileInCurrentPoint.InBetweenAquiferLayer.TopLevel;
if (topInBetweenSandLayer < point.Z)
{
// There is an aquitard above the aquifer, for which we can determine the uplift factor
UpliftCalculator upliftCalculatorForInBetweenSandLayer = CreateUpliftCalculator(point, topInBetweenSandLayer, soilProfileInCurrentPoint);
if ((PlLines.Lines[PlLineType.Pl4] != null) && (PlLines.Lines[PlLineType.Pl4].Points.Count > 0))
{
upliftFactorForInBetweenSandLayer = upliftCalculatorForInBetweenSandLayer.CalculateUpliftFactor(PlLines.Lines[PlLineType.Pl4].ZFromX(point.X));
}
}
else
{
if (soilProfileInCurrentPoint.GetBottomLevel(soilProfileInCurrentPoint.InBetweenAquiferLayer) < point.Z)
{
// The surface cuts into the aquifer so the level to be evaluated is at surfacelevel
UpliftCalculator upliftCalculatorForInBetweenSandLayer = CreateUpliftCalculator(point, point.Z, soilProfileInCurrentPoint);
if ((PlLines.Lines[PlLineType.Pl4] != null) && (PlLines.Lines[PlLineType.Pl4].Points.Count > 0))
{
upliftFactorForInBetweenSandLayer = upliftCalculatorForInBetweenSandLayer.CalculateUpliftFactor(PlLines.Lines[PlLineType.Pl4].ZFromX(point.X));
}
}
}
}
var upliftFactorForBottomSandLayer = double.MaxValue;
if (soilProfileInCurrentPoint.BottomAquiferLayer != null)
{
// Check if bottom sandlayer below surface
double topBottomSandLayer = soilProfileInCurrentPoint.BottomAquiferLayer.TopLevel;
if (topBottomSandLayer < point.Z)
{
UpliftCalculator upliftCalculatorForBottomSandLayer = CreateUpliftCalculator(point, soilProfileInCurrentPoint.BottomAquiferLayer.TopLevel, soilProfileInCurrentPoint);
if ((PlLines.Lines[PlLineType.Pl3] != null) && (PlLines.Lines[PlLineType.Pl3].Points.Count > 0))
{
upliftFactorForBottomSandLayer = upliftCalculatorForBottomSandLayer.CalculateUpliftFactor(PlLines.Lines[PlLineType.Pl3].ZFromX(point.X));
}
}
else
{
if (soilProfileInCurrentPoint.GetBottomLevel(soilProfileInCurrentPoint.BottomAquiferLayer) < point.Z)
{
// The surface cuts into the aquifer so the level to be evaluated is at surfacelevel
UpliftCalculator upliftCalculatorForInBetweenSandLayer = CreateUpliftCalculator(point, point.Z, soilProfileInCurrentPoint);
if ((PlLines.Lines[PlLineType.Pl3] != null) && (PlLines.Lines[PlLineType.Pl3].Points.Count > 0))
{
upliftFactorForBottomSandLayer = upliftCalculatorForInBetweenSandLayer.CalculateUpliftFactor(PlLines.Lines[PlLineType.Pl3].ZFromX(point.X));
}
}
}
}
if (upliftFactorForBottomSandLayer.IsNearEqual(double.MaxValue, toleranceAlmostEqual) &&
upliftFactorForInBetweenSandLayer.IsNearEqual(double.MaxValue, toleranceAlmostEqual))
{
return null;
}
if (upliftFactorForBottomSandLayer < upliftFactorForInBetweenSandLayer)
{
return new UpliftLocationAndResult(point, upliftFactorForBottomSandLayer, soilProfileInCurrentPoint.BottomAquiferLayer.Name);
}
return new UpliftLocationAndResult(point, upliftFactorForInBetweenSandLayer, soilProfileInCurrentPoint.InBetweenAquiferLayer.Name);
}
///
/// Determine location with the lowest uplift factor
///
/// location and uplift factor
public UpliftLocationAndResult GetLocationWithLowestUpliftFactor()
{
double? lowestUpliftFactor = null;
ThrowIfNoPlLinesDefined();
ThrowIfNoSurfaceLinDefined();
ThrowIfNoSoilProfileDefined();
Point2D startSurfacePoint = SurfaceLine.GetDikeToeInward();
IEnumerable relevantSurfacePointsList = from Point2D point in SurfaceLine.Geometry.Points
where point.X >= startSurfacePoint.X
orderby point.X
select point;
UpliftLocationAndResult lowestUpliftLocationAndResult = null;
foreach (Point2D surfacePoint in relevantSurfacePointsList)
{
UpliftLocationAndResult upliftLocationAndResult = GetUpliftFactorAtPoint(surfacePoint);
if (upliftLocationAndResult != null)
{
if (!lowestUpliftFactor.HasValue || upliftLocationAndResult.UpliftFactor < lowestUpliftFactor)
{
lowestUpliftFactor = upliftLocationAndResult.UpliftFactor;
lowestUpliftLocationAndResult = upliftLocationAndResult;
}
}
}
return lowestUpliftLocationAndResult;
}
///
/// Create upliftcalculator at given point
///
/// Point2D for which to calculate upliftfactor
/// Top of layer where uplift occurs
/// location and upliftfactor
private UpliftCalculator CreateUpliftCalculator(Point2D point, double topOfLayer, SoilProfile1D soilProfile)
{
PlLine phreaticLine = PlLines.Lines[PlLineType.Pl1];
return new UpliftCalculator
{
PhreaticLevel = phreaticLine.ZFromX(point.X),
SoilProfile = soilProfile,
TopOfLayerToBeEvaluated = topOfLayer,
SurfaceLevel = point.Z,
UnitWeightSoilEmbankment = (DikeEmbankmentMaterial == null) ? null : DikeEmbankmentMaterial.AbovePhreaticLevel
};
}
///
///
///
///
///
private SoilProfile1D GetSoilProfileBelowPoint(double xCoordinate)
{
if (SoilProfile != null)
{
SoilProfile1D soilProfile1D = SoilProfileHelper.DetermineForSurfaceLineCorrected1DProfileAtX(SoilProfile, SurfaceLine, xCoordinate, DikeEmbankmentMaterial);
return soilProfile1D;
}
if (SoilProfile2D != null)
{
return SoilProfile2D.GetSoilProfile1D(xCoordinate);
}
// This possibly should become conversion from StixFile
throw new NotImplementedException(@"Using full 2D geometry (based on stix file) not yet available.");
}
///
/// Check on precondition
///
private void ThrowIfNoPlLinesDefined()
{
if (PlLines == null)
{
throw new UpliftLocationDeterminatorException("Required pllines not found");
}
}
///
/// Check on precondition
///
private void ThrowIfNoSurfaceLinDefined()
{
if (SurfaceLine == null)
{
throw new UpliftLocationDeterminatorException("Required surfaceLine line not found");
}
}
///
/// Check on precondition
///
private void ThrowIfNoSoilProfileDefined()
{
if (SoilProfile == null && SoilProfile2D == null)
{
throw new UpliftLocationDeterminatorException("Required soilProfile not found");
}
}
}