// 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; using Deltares.DamEngine.Data.Standard; using MathNet.Numerics; namespace Deltares.DamEngine.Calculators.DikesDesign; /// /// Definition of a ditch /// public struct DitchDefinition { public double OriginalX { get; set; } public double DistanceFromToe { get; set; } public double DistanceToBottomDikeSide { get; set; } public double DistanceToBottomPolderSide { get; set; } public double DistanceToEndDitch { get; set; } public double DepthBottomDikeSide { get; set; } public double DepthBottomPolderSide { get; set; } } /// /// Base class for adapting the surfaceline /// public abstract class SurfaceLineAdapter { const double offset = 100.0; protected readonly Location Location; protected readonly SurfaceLine2 surfaceLine; protected double trafficLoadOffsetXfromRiverside; protected double trafficLoadOffsetXfromPolderside; protected double trafficLoadWidth; protected bool hasTrafficLoad; protected bool isTrafficLoadOnCrest; protected double polderLevel; /// /// Constructor /// /// /// /// protected SurfaceLineAdapter(SurfaceLine2 surfaceLine, Location location, double scenarioPolderLevel) { ThrowWhenSurfaceLineIsNull(surfaceLine); ThrowWhenSurfaceLineDoesNotSatisfyToSpecification(surfaceLine); this.surfaceLine = surfaceLine.FullDeepClone(); Location = location; polderLevel = scenarioPolderLevel; RetainTrafficLoad(); } /// /// Create traffic load based on the retained traffic load parameters /// See documentation in Issue [MWDAM-548] /// protected void RestoreTrafficLoad() { if (hasTrafficLoad) { Point2D pointDikeTopAtRiver = surfaceLine.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DikeTopAtRiver); Point2D pointDikeTopAtPolder = surfaceLine.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DikeTopAtPolder); if (isTrafficLoadOnCrest) { // The traffic load will be restored relative to the binnenkruinlijn. If the leftside of the traffic load passes the buitenkruinlijn, // the leftside of the traffic load will be placed on the buitenkruinlijn. It is possible that the rightside of the traffic load // passes the buitenkruinlijn. surfaceLine.SortPoints(); // line need to be sorted to use GetZAtX double xCoordinate = pointDikeTopAtPolder.X - trafficLoadOffsetXfromPolderside - trafficLoadWidth; if (xCoordinate < pointDikeTopAtRiver.X) { xCoordinate = pointDikeTopAtRiver.X; } double zCoordinate = surfaceLine.Geometry.GetZatX(xCoordinate); surfaceLine.EnsurePointOfType(xCoordinate, zCoordinate, CharacteristicPointType.TrafficLoadOutside); surfaceLine.SortPoints(); // line need to be sorted to use GetZAtX xCoordinate += trafficLoadWidth; zCoordinate = surfaceLine.Geometry.GetZatX(xCoordinate); surfaceLine.EnsurePointOfType(xCoordinate, zCoordinate, CharacteristicPointType.TrafficLoadInside); } else { // The traffic load will be restore relative to the buitenkruinlijn. surfaceLine.SortPoints(); // line need to be sorted to use GetZAtX double xCoordinate = pointDikeTopAtRiver.X + trafficLoadOffsetXfromRiverside; double zCoordinate = surfaceLine.Geometry.GetZatX(xCoordinate); surfaceLine.EnsurePointOfType(xCoordinate, zCoordinate, CharacteristicPointType.TrafficLoadOutside); surfaceLine.SortPoints(); // line need to be sorted to use GetZAtX xCoordinate = pointDikeTopAtRiver.X + trafficLoadOffsetXfromRiverside + trafficLoadWidth; zCoordinate = surfaceLine.Geometry.GetZatX(xCoordinate); surfaceLine.EnsurePointOfType(xCoordinate, zCoordinate, CharacteristicPointType.TrafficLoadInside); } } } /// /// Replaces the inside slope by adding the new end point of the slope to the surfaceline. /// /// /// /// point equal to the ShoulderTopInside when the new slope ends on that point else null protected Point2D ReplaceBaseInsideForNewSlope(Point2D startPoint, double slopeTangent) { Point2D result = null; var line = new Line { BeginPoint = new Point2D(startPoint.X, startPoint.Z), EndPoint = new Point2D(startPoint.X + offset, startPoint.Z - offset * slopeTangent) }; // Find the intersectionpoint(s) of the new slope with the surface line IList intersectionpoints = surfaceLine.Geometry.IntersectionPointsXzWithLineXz(line); Point2D newSlopeEndPoint = null; if (intersectionpoints.Count > 0) { newSlopeEndPoint = intersectionpoints.First(); // One of the intersection points can be the dike top which should not be replaced if (newSlopeEndPoint.X.IsNearEqual(surfaceLine.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DikeTopAtPolder).X, GeometryConstants.Tolerance) && newSlopeEndPoint.Z.IsNearEqual(surfaceLine.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DikeTopAtPolder).Z, GeometryConstants.Tolerance)) { newSlopeEndPoint = null; if (intersectionpoints.Count > 1) { newSlopeEndPoint = intersectionpoints[1]; } } } if (newSlopeEndPoint == null) { throw new SurfaceLineAdapterException(Resources.SlopeErrorNoIntersection); } Point2D dikeToeAtPolder = surfaceLine.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DikeToeAtPolder); if (newSlopeEndPoint.X > dikeToeAtPolder.X) { // The new point is beyond the old dike toe so adapt that point. surfaceLine.EnsurePointOfType(newSlopeEndPoint.X, newSlopeEndPoint.Z, CharacteristicPointType.DikeToeAtPolder); // Remove all points between top and dike toe, surfaceLine.RemoveSegmentBetween( surfaceLine.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DikeTopAtPolder).X, newSlopeEndPoint.X); } else { Point2D shoulderTopInside = surfaceLine.CharacteristicPoints.GetPoint2D(CharacteristicPointType.ShoulderTopInside); Point2D shoulderBaseInside = surfaceLine.CharacteristicPoints.GetPoint2D(CharacteristicPointType.ShoulderBaseInside); if (shoulderBaseInside != null && shoulderTopInside != null) { if (newSlopeEndPoint.X > shoulderTopInside.X) { // The new point is in the slope part of the shoulder (between ShoulderTopInside and Dike toe). So add a normal point // at the new location and remove all points between the top and the new point. //surfaceLine.RemovePoint(CharacteristicPointType.); surfaceLine.EnsurePoint(newSlopeEndPoint.X, newSlopeEndPoint.Z); surfaceLine.RemoveSegmentBetween( surfaceLine.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DikeTopAtPolder).X, newSlopeEndPoint.X); } else { if (newSlopeEndPoint.X < shoulderTopInside.X) { // The new point is in the horizontal part of the shoulder. Remove all points between the current // shoulderbase inside and its new location, then move the base surfaceLine.RemoveSegmentBetween(shoulderBaseInside.X, newSlopeEndPoint.X); surfaceLine.EnsurePointOfType(newSlopeEndPoint.X, newSlopeEndPoint.Z, CharacteristicPointType.ShoulderBaseInside); } else { // The new point is equal to ShoulderTopInside. So remove that, add a normal point at its location // and remove all points between the top and the new point. CharacteristicPoint toBeRemoved = surfaceLine.CharacteristicPoints.FirstOrDefault( cp => cp.CharacteristicPointType == CharacteristicPointType.ShoulderTopInside); if (toBeRemoved != null) { surfaceLine.CharacteristicPoints.Remove(toBeRemoved); } surfaceLine.EnsurePoint(newSlopeEndPoint.X, newSlopeEndPoint.Z); surfaceLine.RemoveSegmentBetween( surfaceLine.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DikeTopAtPolder).X, newSlopeEndPoint.X); result = new Point2D(newSlopeEndPoint.X, newSlopeEndPoint.Z); } } } else { // if the newtoe equals the diketoe, then just skip, nothing has to be changed. Otherwise there is a mistake! if (!newSlopeEndPoint.LocationEquals(new Point2D(dikeToeAtPolder.X, dikeToeAtPolder.Z))) { // There is no shoulder so the slope must be too steep. throw new SurfaceLineAdapterException(Resources.SlopeErrorNoIntersection); } } } surfaceLine.SortPoints(); return result; } /// /// Gets the ditch definition. /// /// /// protected DitchDefinition? GetDitchDefinition() { var ditchDefinition = new DitchDefinition(); if (surfaceLine.HasDitch() && surfaceLine.IsDitchCorrect()) { Point2D ditchDikeSide = surfaceLine.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DitchDikeSide); ditchDefinition.DistanceFromToe = ditchDikeSide.X - surfaceLine.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DikeToeAtPolder).X; if (ditchDefinition.DistanceFromToe >= 0) { ditchDefinition.OriginalX = ditchDikeSide.X; ditchDefinition.DistanceToBottomDikeSide = surfaceLine.CharacteristicPoints.GetPoint2D(CharacteristicPointType.BottomDitchDikeSide).X - ditchDikeSide.X; ditchDefinition.DistanceToBottomPolderSide = surfaceLine.CharacteristicPoints.GetPoint2D(CharacteristicPointType.BottomDitchPolderSide).X - ditchDikeSide.X; ditchDefinition.DistanceToEndDitch = surfaceLine.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DitchPolderSide).X - ditchDikeSide.X; ditchDefinition.DepthBottomDikeSide = ditchDikeSide.Z - surfaceLine.CharacteristicPoints.GetPoint2D(CharacteristicPointType.BottomDitchDikeSide).Z; ditchDefinition.DepthBottomPolderSide = ditchDikeSide.Z - surfaceLine.CharacteristicPoints.GetPoint2D(CharacteristicPointType.BottomDitchPolderSide).Z; return ditchDefinition; } } else { if (surfaceLine.HasDitch()) { // Incorrect ditch is an error here, this should have been checked already throw new SurfaceLineAdapterException(Resources.SurfaceLineAdapterDikeDitchError); } } return null; } /// /// Removes the existing ditch. /// /// The ditch definition. protected void RemoveExistingDitch(DitchDefinition? ditchDefinition) { if (ditchDefinition != null) { // It is possible that DikeToeAtPolder coinsides with DitchDikeSide or SurfaceLevelInside with DitchPolderSide // If that is the case, those characteristic points should be restored // So first save those characteristic points Point2D dikeToeAtPolder = surfaceLine.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DikeToeAtPolder); Point2D surfaceLevelInside = surfaceLine.CharacteristicPoints.GetPoint2D(CharacteristicPointType.SurfaceLevelInside); // The characteristic points DitchDikeSide and DitchPolderSide should be removed, but the points itself should be restored, // else the surfaceline will get a new form. So save those points Point2D ditchDikeSide = surfaceLine.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DitchDikeSide); Point2D ditchPolderSide = surfaceLine.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DitchPolderSide); surfaceLine.RemoveSegmentIncluding( surfaceLine.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DitchDikeSide).X, surfaceLine.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DitchPolderSide).X); // Now restore characteristic points DikeToeAtPolder and SurfaceLevelInside if necessary surfaceLine.EnsurePointOfType(dikeToeAtPolder.X, dikeToeAtPolder.Z, CharacteristicPointType.DikeToeAtPolder); surfaceLine.EnsurePointOfType(surfaceLevelInside.X, surfaceLevelInside.Z, CharacteristicPointType.SurfaceLevelInside); // Now restore points ditchDikeSide and ditchPolderSide surfaceLine.EnsurePoint(ditchDikeSide.X, ditchDikeSide.Z); surfaceLine.EnsurePoint(ditchPolderSide.X, ditchPolderSide.Z); surfaceLine.SortPoints(); } } /// /// Restores the ditch. /// /// The ditch definition. /// protected void RestoreDitch(DitchDefinition? ditchDefinition) { if (ditchDefinition == null) { return; } Point2D dikeToeAtPolder = surfaceLine.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DikeToeAtPolder); double distanceToNewToe = ditchDefinition.Value.OriginalX - dikeToeAtPolder.X; double xDitchDikeSide; DitchCoordinates coors; surfaceLine.SortPoints(); // TODO: #the Following code is not correct; you should also look at the setting of Location.UseNewMinDistanceDikeToeStartDitch // First determine all coordinates if (distanceToNewToe.IsSmaller(Location.NewMinDistanceDikeToeStartDitch, GeometryConstants.Tolerance) && !distanceToNewToe.IsNearEqual(ditchDefinition.Value.DistanceFromToe, GeometryConstants.Tolerance)) { // Ditch needs to be moved as it is less then the minimum required distance from the new toe xDitchDikeSide = dikeToeAtPolder.X + Location.NewMinDistanceDikeToeStartDitch; if (Location.UseNewDitchDefinition) { // Use the new definition instead of the old shape coors = GetCoordinatesForNewShape(xDitchDikeSide); } else { // replace the ditch with the same shape. coors = GetCoordinatesForOldShape(xDitchDikeSide, ditchDefinition.Value); } } else { // replace the ditch with the same shape and same location. xDitchDikeSide = dikeToeAtPolder.X + distanceToNewToe; coors = GetCoordinatesForOldShape(xDitchDikeSide, ditchDefinition.Value); } // check if the new bottom is beneath the surface line. If not, the ditch must not be replaced at all if (coors.ZBottom >= surfaceLine.Geometry.GetZatX(coors.XBottomAtDike) || coors.ZBottom >= surfaceLine.Geometry.GetZatX(coors.XBottomAtPolder)) { return; } double surfaceLevelInsideX = surfaceLine.CharacteristicPoints.GetPoint2D(CharacteristicPointType.SurfaceLevelInside).X; if (coors.XAtPolder > surfaceLevelInsideX) { throw new SurfaceLineAdapterException(Resources.SurfaceLineHeightAdapterDitchOutsideSurfaceLine); } // Add the outside points of the new ditch surfaceLine.EnsurePointOfType(coors.XAtDike, coors.ZAtDike, CharacteristicPointType.DitchDikeSide); surfaceLine.EnsurePointOfType(coors.XAtPolder, coors.ZAtPolder, CharacteristicPointType.DitchPolderSide); // Delete all existing points in the new ditch surfaceLine.RemoveSegmentBetween(coors.XAtDike, coors.XAtPolder); // Add the bottom of the ditch surfaceLine.EnsurePointOfType(coors.XBottomAtDike, coors.ZBottom, CharacteristicPointType.BottomDitchDikeSide); surfaceLine.EnsurePointOfType(coors.XBottomAtPolder, coors.ZBottom, CharacteristicPointType.BottomDitchPolderSide); surfaceLine.SortPoints(); } /// /// Store the parameters with which the traffic load can be restored. /// The traffic load will be retained relative to the buitenkruinlijn. /// If the traffic load is completly inside area between buitenkruinlijn and binnenkruinlijn the following will be done: /// - The traffic load will be retained relative to the binnenkruinlijn. If the leftside of the traffic load passes the buitenkruinlijn, /// the leftside of the traffic load will be placed on the buitenkruinlijn. It is possible that the rightside of the traffic load /// passes the buitenkruinlijn. /// See documentation in Issue [MWDAM-548] /// private void RetainTrafficLoad() { Point2D pointTrafficLoadInside = surfaceLine.CharacteristicPoints.GetPoint2D(CharacteristicPointType.TrafficLoadInside); Point2D pointTrafficLoadOutside = surfaceLine.CharacteristicPoints.GetPoint2D(CharacteristicPointType.TrafficLoadOutside); Point2D pointDikeTopAtRiver = surfaceLine.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DikeTopAtRiver); Point2D pointDikeTopAtPolder = surfaceLine.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DikeTopAtPolder); hasTrafficLoad = ((pointTrafficLoadInside != null) && (pointTrafficLoadOutside != null)); if (hasTrafficLoad) { trafficLoadOffsetXfromRiverside = pointTrafficLoadOutside.X - pointDikeTopAtRiver.X; trafficLoadOffsetXfromPolderside = pointDikeTopAtPolder.X - pointTrafficLoadInside.X; trafficLoadWidth = pointTrafficLoadInside.X - pointTrafficLoadOutside.X; isTrafficLoadOnCrest = (pointTrafficLoadOutside.X >= pointDikeTopAtRiver.X) && (pointTrafficLoadInside.X <= pointDikeTopAtPolder.X); } } /// /// Throws an exception when the surface line does not comply to the specification /// /// /// The specification is that there: /// - There are should be at least 4 points in the surface line /// - must be a dike /// - that the point coords are non zero /// - There is a surface line when a shoulder exists /// /// The candidate to test private static void ThrowWhenSurfaceLineDoesNotSatisfyToSpecification(SurfaceLine2 surfaceLine) { ThrowWhenSurfaceLineHasNoOrLessThenFourPoints(surfaceLine.Geometry.Points); ThrowWhenSurfaceHasNoDike(surfaceLine); ThrowWhenSurfaceLineHasAShoulderInsideAndNoSurfaceLevel(surfaceLine, surfaceLine.CharacteristicPoints.GetPoint2D(CharacteristicPointType.SurfaceLevelInside)); Point2D p1 = surfaceLine.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DikeTopAtRiver); Point2D p2 = surfaceLine.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DikeTopAtPolder); var zeroPoint = new Point2D(); if (zeroPoint.LocationEquals(p1) || zeroPoint.LocationEquals(p2)) { throw new SurfaceLineAdapterException(Resources.SurfaceLineAdapterDikeHeightError); } if (surfaceLine.HasShoulderInside()) { p1 = surfaceLine.CharacteristicPoints.GetPoint2D(CharacteristicPointType.ShoulderTopInside); p2 = surfaceLine.CharacteristicPoints.GetPoint2D(CharacteristicPointType.ShoulderBaseInside); if (zeroPoint.LocationEquals(p1) || zeroPoint.LocationEquals(p2)) { throw new SurfaceLineAdapterException(Resources.SurfaceLineAdapterShoulderHeightError); } } } private static void ThrowWhenSurfaceLineHasAShoulderInsideAndNoSurfaceLevel(SurfaceLine2 surfaceLine, Point2D surfaceLevelPoint) { if (surfaceLine.HasShoulderInside() && surfaceLevelPoint == null) { throw new SurfaceLineAdapterException(Resources.SurfaceLineAdapterSurfaceLevelError); } } private static void ThrowWhenSurfaceHasNoDike(SurfaceLine2 surfaceLine) { if (!surfaceLine.HasDike()) { throw new SurfaceLineAdapterException(Resources.SurfaceLineAdapterInvalidDikeError); } } private static void ThrowWhenSurfaceLineIsNull(SurfaceLine2 surfaceLine) { if (surfaceLine == null) { throw new ArgumentNullException(Resources.SurfaceLineAdapterNoDikeError); } } private static void ThrowWhenSurfaceLineHasNoOrLessThenFourPoints(ICollection points) { if (points == null || points.Count < 4) { throw new SurfaceLineAdapterException(Resources.SurfaceLineAdapterNoDikePointsError); } } private DitchCoordinates GetCoordinatesForOldShape(double xDitchDike, DitchDefinition ditchDefinition) { var res = new DitchCoordinates(); res.XAtDike = xDitchDike; res.ZAtDike = surfaceLine.Geometry.GetZatX(res.XAtDike); res.XBottomAtDike = xDitchDike + ditchDefinition.DistanceToBottomDikeSide; res.ZBottom = res.ZAtDike - ditchDefinition.DepthBottomDikeSide; res.XBottomAtPolder = xDitchDike + ditchDefinition.DistanceToBottomPolderSide; res.XAtPolder = xDitchDike + ditchDefinition.DistanceToEndDitch; res.ZAtPolder = surfaceLine.Geometry.GetZatX(res.XAtPolder); return res; } private DitchCoordinates GetCoordinatesForNewShape(double xDitchDike) { var res = new DitchCoordinates(); res.XAtDike = xDitchDike; res.ZAtDike = surfaceLine.Geometry.GetZatX(res.XAtDike); // Depth of the ditch is defined towards PolderLevel res.ZBottom = polderLevel - Location.NewDepthDitch; res.XBottomAtDike = xDitchDike + (res.ZAtDike - res.ZBottom) * Location.NewSlopeAngleDitch; res.XBottomAtPolder = res.XBottomAtDike + Location.NewWidthDitchBottom; var line = new Line { BeginPoint = new Point2D(res.XBottomAtPolder, res.ZBottom), EndPoint = new Point2D(res.XBottomAtPolder + offset, res.ZBottom + offset * Location.NewSlopeAngleDitch) }; // Find the intersectionpoint(s) of the new ditch slope with the surface line IList intersectionpoints = surfaceLine.Geometry.IntersectionPointsXzWithLineXz(line); if (intersectionpoints.Count > 0) { res.XAtPolder = intersectionpoints[0].X; res.ZAtPolder = intersectionpoints[0].Z; } else { //new slope of ditch does not intersect with surface line (most probably because the bottom is too high) throw new SurfaceLineAdapterException(Resources.SurfaceLineAdapterDitchSlopeError); } return res; } private struct DitchCoordinates { public double XAtDike; public double ZAtDike; public double XBottomAtDike; public double ZBottom; public double XBottomAtPolder; public double XAtPolder; public double ZAtPolder; } }