// Copyright (C) Stichting Deltares 2024. 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.Calculators.Properties;
using Deltares.DamEngine.Data.General;
using Deltares.DamEngine.Data.Geometry;
using Deltares.DamEngine.Data.Geotechnics;
namespace Deltares.DamEngine.Calculators.DikesDesign;
///
/// Class for adapting the shoulder (berm) of a surfaceline
///
public class SurfaceLineShoulderAdapter : SurfaceLineAdapter
{
///
/// Initializes a new instance of the class.
///
///
///
///
///
public SurfaceLineShoulderAdapter(SurfaceLine2 surfaceLine, Location location, double scenarioPolderLevel) : base(surfaceLine, location, scenarioPolderLevel)
{
if (!surfaceLine.HasAnnotation(CharacteristicPointType.SurfaceLevelInside))
{
throw new SurfaceLineAdapterException();
}
SlopeOfNewShoulder = 3; // CoTangent i.e. width (dx) : height (dz) = 3 : 1 (= tangent: 1/3)
// It must be possible to define SlopeOfNewShoulder hard in code for certain options (such as Rijnland option) which should not be overriden
// by this use option. So the use option has to be implemented here in the constructor.
if (Location.UseNewShoulderBaseSlope)
{
SlopeOfNewShoulder = 1 / Location.NewShoulderBaseSlope;
}
MaxShoulderLevel = surfaceLine.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeTopAtPolder).Z;
}
///
/// Gets or sets the maximum shoulder level.
///
///
/// The maximum shoulder level.
///
public double MaxShoulderLevel { get; set; }
///
/// Gets or sets the slope of new shoulder.
///
///
/// The slope of new shoulder.
///
public double SlopeOfNewShoulder { get; set; }
///
/// shoulderLength = horizontal distance between ShoulderTopInside and ShoulderBaseInside
/// shoulderHeight = vertical distance between ShoulderTopInside and DikeToeAtPolder
/// The default slope of the shoulder will be 1 : 3
/// The height has a maximum of MaxFractionOfDikeHeightForShoulderHeight (default 2/3) * dike height
///
///
///
/// Ensure that shoulder complies with piping design Demands (shoulder from toe instead of intersection old dike, height gives error
///
public SurfaceLine2 ConstructNewSurfaceLine(double shoulderLength, double shoulderHeight, bool designFromDikeToe)
{
double orgMaxX = surfaceLine.Geometry.GetMaxX();
if (double.IsNaN(orgMaxX))
{
orgMaxX = double.MaxValue;
}
// Make dike and shoulder straight
RemovePointsBetweenCharacteristicPointsDikeTopAtPolderDikeToeAtPolder(surfaceLine);
// See it there is a ditch, if so store its characteristics.
DitchDefinition? ditchDefinition = GetDitchDefinition();
// Then delete it from the surfaceline
RemoveExistingDitch(ditchDefinition);
ValidateNewShoulderSize(shoulderLength, shoulderHeight);
GeometryPoint dikeTopAtPolder = surfaceLine.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeTopAtPolder);
GeometryPoint dikeToeAtPolder = surfaceLine.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeToeAtPolder);
if (designFromDikeToe)
{
// for piping design, when the required new height is larger than allowed, it is an error!
if ((MaxShoulderLevel - dikeToeAtPolder.Z) < shoulderHeight)
{
throw new SurfaceLineAdapterException(Resources.SurfaceLineShoulderAdapterNewShoulderHeightTooLargeError);
}
}
else
{
// Assure new height is less then or equal to the maximum height
shoulderHeight = Math.Min(shoulderHeight, (MaxShoulderLevel - dikeToeAtPolder.Z));
}
// Find the intersection point at new shoulder height with the dike, this is where the new shoulder starts
bool hasShoulderInside = surfaceLine.HasShoulderInside();
double dikeToeZ = dikeToeAtPolder.Z;
GeometryPoint dikeBaseInside = hasShoulderInside ? surfaceLine.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.ShoulderBaseInside) : dikeToeAtPolder;
// Determine intersectionpoint with slope for a horizontal shoulder
GeometryPoint intersectionPointAtDike;
intersectionPointAtDike = LineHelper.DetermineIntersectionPointWithExtrapolation(dikeTopAtPolder, dikeBaseInside,
new GeometryPoint(dikeToeAtPolder.X, shoulderHeight + dikeToeZ),
new GeometryPoint(dikeToeAtPolder.X + 1, shoulderHeight + dikeToeZ));
var newTopShoulder = new GeometryPoint(intersectionPointAtDike.X + shoulderLength, shoulderHeight + dikeToeZ);
if (Location.UseNewShoulderTopSlope)
{
// if top slope is not to be horizontal then recalculate the intersection from a point at required slope width
// from the horizontal intersection point. This will result in the actual width of the shoulder being a bit
// larger than the requested size but that can not be helped (classic chicken-egg problem)
var pb = new GeometryPoint(newTopShoulder.X - 100, newTopShoulder.Z + (100 * Location.NewShoulderTopSlope));
intersectionPointAtDike = LineHelper.DetermineIntersectionPointWithExtrapolation(dikeTopAtPolder, dikeBaseInside, pb, newTopShoulder);
if (intersectionPointAtDike.Z > MaxShoulderLevel)
{
throw new SurfaceLineAdapterException(Resources.SurfaceLineShoulderAdapterNewShoulderHeightTooLargeTopSlopeError);
}
}
// Find the intersection at the surface line from a line starting at the end of the new shoulder sloping down at 1 : SlopeOfNewShoulder
// to get the new point of the toe. Note that for stability the shoulder length is applied directly to the intersection point as for piping
// the shoulder length is applied from the original diketoe (so for piping the complete shoulder is longer).
double xStart = intersectionPointAtDike.X;
const double offset = 100.0;
if (designFromDikeToe)
{
xStart = dikeToeAtPolder.X;
}
var line = new Line
{
BeginPoint = new Point2D(newTopShoulder.X, newTopShoulder.Z),
EndPoint = new Point2D(newTopShoulder.X + offset, newTopShoulder.Z - offset / SlopeOfNewShoulder)
};
IList intersectionpoints = surfaceLine.Geometry.IntersectionPointsXzWithLineXz(line);
GeometryPoint newToeAtPolder = null;
if (intersectionpoints.Count > 0)
{
Point2D firstIntersectionPoint = intersectionpoints.First();
newToeAtPolder = new GeometryPoint(firstIntersectionPoint.X, firstIntersectionPoint.Z);
}
if (newToeAtPolder == null)
{
// If the intersection was not found, this could mean that A) the new shoulder intersects the surfaceline at the
// shoulderheight itself or B) that the original surfaceline is not long enough to intersect the new shoulder
// (this happens when the end of the new shoulder falls pratically at the end of the surfaceline in which case
// there is no room to fit the entire shoulder in the original surfaceline). For A) try to find that intersection
// point and use it as new toe. If not, something is very wrong (probably B) and an exception must be raised.
var line1 = new Line
{
BeginPoint = new Point2D(xStart + 0.001, intersectionPointAtDike.Z),
EndPoint = new Point2D(xStart + shoulderLength, intersectionPointAtDike.Z)
};
IList intersectionpoints2 = surfaceLine.Geometry.IntersectionPointsXzWithLineXz(line1);
if (intersectionpoints2.Count > 0)
{
Point2D firstIntersectionPoint = intersectionpoints2.First();
newToeAtPolder = new GeometryPoint(firstIntersectionPoint.X, firstIntersectionPoint.Z);
}
if (newToeAtPolder == null)
{
throw new SurfaceLineAdapterException(Resources.SurfaceLineShoulderAdapterNewShoulderNoIntersectionError);
}
}
if (designFromDikeToe)
{
// for piping design, the new toe must be placed at shoulderLength from the original toe, not from the new intersection point.
newToeAtPolder.X = dikeToeAtPolder.X + shoulderLength + shoulderHeight * SlopeOfNewShoulder;
}
// remove old line (point) segment starting from intersection dike inside to new intersection ground
surfaceLine.RemoveSegmentBetween(intersectionPointAtDike.X, newToeAtPolder.X);
// insert the new shoulder points
surfaceLine.EnsurePointOfType(intersectionPointAtDike.X, intersectionPointAtDike.Z, CharacteristicPointType.ShoulderBaseInside);
surfaceLine.EnsurePointOfType(newTopShoulder.X, newTopShoulder.Z, CharacteristicPointType.ShoulderTopInside);
// Place new dike toe
surfaceLine.EnsurePointOfType(newToeAtPolder.X, newToeAtPolder.Z, CharacteristicPointType.DikeToeAtPolder);
// Check whether the surface line is extended. This is not allowed!
if (surfaceLine.Geometry.GetMaxX() > orgMaxX)
{
throw new SurfaceLineAdapterException(Resources.SurfaceLineShoulderAdapterNewShoulderLengthTooLargeError);
}
// Restore Ditch (if any)
RestoreDitch(ditchDefinition);
// Restore traffic load
RestoreTrafficLoad();
surfaceLine.SortPoints();
return surfaceLine;
}
///
/// Raise exception if new shoulder is smaller than existing shoulder
///
///
///
///
private void ValidateNewShoulderSize(double shoulderLength, double shoulderHeight)
{
if (surfaceLine.HasShoulderInside())
{
double currentShoulderLength = surfaceLine.DetermineShoulderLengthForGivenShoulderTopInside(surfaceLine.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.ShoulderTopInside));
if (currentShoulderLength > shoulderLength)
{
throw new SurfaceLineAdapterException(Resources.SurfaceLineShoulderAdapterNewShoulderLengthError);
}
double currentShoulderHeight = surfaceLine.DetermineShoulderHeight();
if (currentShoulderHeight > shoulderHeight)
{
throw new SurfaceLineAdapterException(Resources.SurfaceLineShoulderAdapterNewShoulderHeightError);
}
if (shoulderLength < 0)
{
throw new SurfaceLineAdapterException(Resources.SurfaceLineShoulderAdapterNewShoulderLengthError);
}
if (shoulderHeight < 0)
{
throw new SurfaceLineAdapterException(Resources.SurfaceLineShoulderAdapterNewShoulderHeightZeroError);
}
}
}
///
/// Remove all non characteristic points between DikeTopAtPolder and DikeToeAtPolder
///
/// The surface line.
private void RemovePointsBetweenCharacteristicPointsDikeTopAtPolderDikeToeAtPolder(SurfaceLine2 surfaceLine2)
{
bool hasShoulderInside = surfaceLine2.HasShoulderInside();
GeometryPoint dikeTopAtPolder = surfaceLine2.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeTopAtPolder);
GeometryPoint dikeToeAtPolder = surfaceLine2.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeToeAtPolder);
if (hasShoulderInside)
{
GeometryPoint shoulderBaseInside = surfaceLine2.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.ShoulderBaseInside);
GeometryPoint shoulderTopInside = surfaceLine2.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.ShoulderTopInside);
surfaceLine2.RemoveSegmentBetween(dikeTopAtPolder.X, shoulderBaseInside.X);
surfaceLine2.RemoveSegmentBetween(shoulderBaseInside.X, shoulderTopInside.X);
surfaceLine2.RemoveSegmentBetween(shoulderTopInside.X, dikeToeAtPolder.X);
}
else
{
surfaceLine2.RemoveSegmentBetween(dikeTopAtPolder.X, dikeToeAtPolder.X);
}
}
}