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