// 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.Data.Geometry; using Deltares.DamEngine.Data.Properties; using Deltares.DamEngine.Data.Standard; namespace Deltares.DamEngine.Data.Geotechnics; /// /// All extension-methods for . /// public static class SurfaceLine2Extensions { /// /// Returns all that require to be sorted on X /// ascending. /// /// The evaluated surfaceline. /// Collection of characteristic points, ordered by type from the point /// expecting the lowest X coordinate in the set through to the one expecting the /// highest X coordinate. public static IEnumerable GetCharacteristicPointsRequiringAscendingX(this SurfaceLine2 line) { return line.CharacteristicPoints.Where(p => !double.IsNaN(p.Point.X) && // Note: This probably shall no longer apply to SurfaceLine2 p.CharacteristicPointType != CharacteristicPointType.None && p.CharacteristicPointType != CharacteristicPointType.TrafficLoadInside && p.CharacteristicPointType != CharacteristicPointType.TrafficLoadOutside).OrderBy(p => p.CharacteristicPointType); } /// /// Checks whether the specified line has the given annotation. /// /// The surface line to be checked. /// The type. /// /// public static bool HasAnnotation(this SurfaceLine2 line, CharacteristicPointType type) { return line.CharacteristicPoints.GetPoint2D(type) != null; } /// /// Determines whether the specified characteristic point type is defined (has /// the given annotation and its X coordinate is not ). /// /// The evaluated surfaceline. /// Type of the characteristic point. /// true if input parameter is defined /// public static bool IsDefined(this SurfaceLine2 line, CharacteristicPointType characteristicPointType) { // TODO: GRASP: Information Expert -> CharacteristicPoint class should be responsible for this logic. return line.HasAnnotation(characteristicPointType) && !Double.IsNaN(line.CharacteristicPoints.GetPoint2D(characteristicPointType).X); } /// /// Gets the absolute height ([m]) of the dike. /// /// Height of dike, or null if /// is not defined for the surface line. public static double? GetDikeHeight(this SurfaceLine2 line) { Point2D dikeTopAtRiver = line.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DikeTopAtRiver); if (dikeTopAtRiver != null) { return dikeTopAtRiver.Z; } return null; } /// /// If shoulder is present then the toe of the shoulder () /// is returned, else the toe of the dike () /// is returned. /// /// Toe of the dike, or null if none of the required characteristic /// annotations can be found. public static Point2D GetDikeToeInward(this SurfaceLine2 line) { return line.CharacteristicPoints.GetPoint2D(CharacteristicPointType.ShoulderBaseInside) ?? line.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DikeToeAtPolder); } /// /// Gets the length of the dike. /// /// dike length or null in case toe points are null public static double? GetDikeLength(this SurfaceLine2 line) { Point2D dikeToeAtRiver = line.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DikeToeAtRiver); Point2D dikeToeAtPolder = line.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DikeToeAtPolder); if (dikeToeAtRiver != null && dikeToeAtPolder != null) { return Math.Abs(dikeToeAtRiver.X - dikeToeAtPolder.X); } return null; } /// /// Checks if a surfaceline has all characteristic point types required to describe /// a dike. /// /// Surfaceline to be checked. /// True if there are characteristic points defined that describe a dike; /// False otherwise. public static bool HasDike(this SurfaceLine2 line) { return IsDefined(line, CharacteristicPointType.DikeToeAtRiver) && IsDefined(line, CharacteristicPointType.DikeTopAtRiver) && IsDefined(line, CharacteristicPointType.DikeTopAtPolder) && IsDefined(line, CharacteristicPointType.DikeToeAtPolder); } /// /// Add 2D characteristic point without a type /// /// The surfaceline to be modified. /// The x. /// The z. public static void EnsurePoint(this SurfaceLine2 line, double x, double z) { line.EnsurePointOfType(x, z, null); } /// /// Add characteristic point in X-Z plane /// /// The surfaceline to be modified. /// The x. /// The z. /// The type. public static void EnsurePointOfType(this SurfaceLine2 line, double x, double z, CharacteristicPointType? type) { if (!line.CharacteristicPoints.GeometryMustContainPoint) { throw new NotImplementedException("SurfaceLine specific method, not implemented for 'Ringtoets'-mode."); } var movedPointToMatch = false; var addPoint = false; Point2D point = null; var newPoint = new Point2D() { X = x, Z = z }; if (type.HasValue) { // Get point of this type.. point = line.CharacteristicPoints.GetPoint2D(type.Value); if (point != null) { // Characteristic point annotation set, check location... if (point.LocationEquals(newPoint)) { // Annotated point is at given location; We're done here! :) return; } bool isAssignedToOtherCharacteristicPoints = line.GetCharacteristicPoints(point).Count(cpt => cpt != CharacteristicPointType.None) > 1; if (isAssignedToOtherCharacteristicPoints) { // Other characteristic points exist with the same coordinates so add as new point point = line.Geometry.DeterminePointAt(newPoint.X, newPoint.Z); // Get point at specified coords if (point == null) { point = newPoint; addPoint = true; } } else { // Point is unique as characteristic point so set its coords to specified coords point.X = x; point.Z = z; movedPointToMatch = true; } } } if (point == null) { point = line.Geometry.DeterminePointAt(x, z); // Get point at specified coords } if (point == null) { point = new Point2D() { X = x, Z = z }; addPoint = true; } if (addPoint) { line.Geometry.Points.Add(point); line.AddCharacteristicPoint(point, type ?? CharacteristicPointType.None); } else if (type.HasValue && !movedPointToMatch) { if (line.CharacteristicPoints.Any(cp => ReferenceEquals(cp.Point, point) && cp.CharacteristicPointType != CharacteristicPointType.None)) { line.AddCharacteristicPoint(point, type.Value); } else { int index = -1; for (var i = 0; i < line.CharacteristicPoints.Count; i++) { if (ReferenceEquals(line.CharacteristicPoints[i].Point, point)) { index = i; break; } } line.CharacteristicPoints.Annotate(index, type.Value); } } } /// /// Removes the points between the two x values. The start and end points will /// not be removed. /// /// Surfaceline being modified. /// The non-inclusive starting X-coordinate. /// The non-inclusive ending X-coordinate. /// public static void RemoveSegmentBetween(this SurfaceLine2 line, double startX, double endX) { RemoveGeometryPointsInRange(line, startX, endX, false); } /// /// Removes the points between the two x values. /// /// Surfaceline being modified. /// The inclusive starting X-Coordinate. /// The inclusive ending X-Coordinate. /// public static void RemoveSegmentIncluding(this SurfaceLine2 line, double startX, double endX) { RemoveGeometryPointsInRange(line, startX, endX, true); } /// /// Checks if a surfaceline has all characteristic point types required to describe /// an inside shoulder. /// /// Surfaceline to be checked. /// True if there are characteristic points defined that described the /// inside shoulder; False otherwise. public static bool HasShoulderInside(this SurfaceLine2 line) { return IsDefined(line, CharacteristicPointType.ShoulderTopInside) && IsDefined(line, CharacteristicPointType.ShoulderBaseInside); } /// /// Checks if a surfaceline has all characteristic point types required to describe /// a ditch. /// /// Surfaceline to be checked. /// True if there are characteristic points defined that described the /// ditch; False otherwise. public static bool HasDitch(this SurfaceLine2 line) { return IsDefined(line, CharacteristicPointType.DitchDikeSide) && IsDefined(line, CharacteristicPointType.DitchPolderSide); } /// /// Determines whether ditch is correct. /// /// true if ditch is correct, otherwise false /// This methods checks if the following points have their X cooridnates /// properly defined: /// /// /// /// /// /// /// public static bool IsDitchCorrect(this SurfaceLine2 line) { // No ditch then always ok bool res = !line.HasDitch(); if (!res) { // check the unchecked points Point2D bottomDitchDikeSide = line.CharacteristicPoints.GetPoint2D(CharacteristicPointType.BottomDitchDikeSide); Point2D bottomDitchPolderSide = line.CharacteristicPoints.GetPoint2D(CharacteristicPointType.BottomDitchPolderSide); res = (bottomDitchDikeSide != null && bottomDitchPolderSide != null); // check the ditch points describe following shape: // 0 0 // \ / // 0---0 Point2D ditchPolderSide = line.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DitchPolderSide); Point2D ditchDikeSide = line.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DitchDikeSide); if (res) { res = ditchPolderSide.X >= bottomDitchPolderSide.X && bottomDitchPolderSide.X >= bottomDitchDikeSide.X && bottomDitchDikeSide.X >= ditchDikeSide.X && bottomDitchDikeSide.Z <= ditchDikeSide.Z && bottomDitchPolderSide.Z <= ditchPolderSide.Z; } } return res; } /// /// Gets the starting point of the surface line. /// /// Starting point, or null if none of the considered points can be found. /// /// Method looks for the following characteristic points (in this order) and returns /// the corresponding geometry point if present: /// /// /// /// /// /// public static Point2D GetStartingPoint(this SurfaceLine2 line) { return line.CharacteristicPoints.GetPoint2D(CharacteristicPointType.ShoulderBaseOutside) ?? line.CharacteristicPoints.GetPoint2D(CharacteristicPointType.ShoulderTopOutside) ?? line.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DikeToeAtRiver); } /// /// Gets the default height of the dike table. /// /// null or height of table public static double? GetDefaultDikeTableHeight(this SurfaceLine2 line) { // Consulted Erik Vastenburg about this: Use buitenkruinlijn as default DTH Point2D point = line.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DikeTopAtRiver); return point == null ? null : point.Z; } /// /// Check whether river level is above the dike toe at river side. If not, throw error /// /// The evaluated surfaceline. /// The current river level. /// Bottom of the river if bottomlevel is above riverLevel, else /// is required to have the characteristic point /// when /// is not null. /// public static void CheckWaterLevelIsAboveDikeToeRiverSide(this SurfaceLine2 line, double? riverLevel) { // Waterlevel MUST be above level of dike toe at river side if (riverLevel.HasValue) { double z = line.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DikeToeAtRiver).Z; if (riverLevel < z) { ThrowWhenLevelBelowDikeToeDike(z, riverLevel.Value); } } } /// /// Determines the intersection of a horizontal line with the surfaceline, starting /// from to . /// /// The surfaceline being evaluated. /// The height of the horizontal line. /// The GeometryPoint at the intersection, or null if no intersection can /// be found. /// /// Requires that the following characteristic points are defined: /// /// /// /// /// /// This method draws the horizontal line starting from the smallest X available /// in the geometry to . /// /// When greater than /// the height of the characteristic point . /// public static Point2D DetermineIntersectionWithLevel(this SurfaceLine2 line, double level) { double startXCoordinate = line.Geometry.GetMinX(); Line waterlevelLine = GetWaterlevelLineStartingFrom(line, level, startXCoordinate); return DetermineIntersectionWithHorizontalLevel(line, waterlevelLine); } /// /// Determines the intersection of a horizontal line with the surfaceline, starting /// from to . /// /// The surfaceline being evaluated. /// The height of the horizontal line. /// The GeometryPoint at the intersection, or null if no intersection can /// be found. /// /// Requires that the following characteristic points are defined: /// /// /// /// /// /// This method draws the horizontal line starting from the X coordinate of /// to . /// /// When greater than /// the height of the characteristic point . /// public static Point2D DetermineIntersectionBetweenTaludRiverSideAndWaterLevel(this SurfaceLine2 line, double level) { double startXCoordinate = line.CharacteristicPoints.GetPoint2D(CharacteristicPointType.SurfaceLevelOutside).X; Line waterlevelLine = GetWaterlevelLineStartingFrom(line, level, startXCoordinate); return DetermineIntersectionWithHorizontalLevel(line, waterlevelLine); } /// /// Determines the shoulder length for given shoulder top inside. /// /// The line. /// The shoulder top inside. /// public static double DetermineShoulderLengthForGivenShoulderTopInside(this SurfaceLine2 line, Point2D shoulderTopInside) { Point2D geometryPoint1 = line.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DikeTopAtPolder); Point2D geometryPoint2 = line.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DikeToeAtPolder); Point2D p2 = line.HasShoulderInside() ? line.CharacteristicPoints.GetPoint2D(CharacteristicPointType.ShoulderBaseInside) : geometryPoint2; Point2D geometryPoint3 = line.CharacteristicPoints.GetPoint2D(CharacteristicPointType.SurfaceLevelOutside); Point2D withExtrapolation = LineHelper.DetermineIntersectionPointWithExtrapolation(geometryPoint1, p2, shoulderTopInside, new Point2D(geometryPoint3.X, shoulderTopInside.Z)); return shoulderTopInside.X - withExtrapolation.X; } /// /// Determines the height of the shoulder. /// /// The line. /// public static double DetermineShoulderHeight(this SurfaceLine2 line) { Point2D geometryPoint1 = line.CharacteristicPoints.GetPoint2D(CharacteristicPointType.ShoulderTopInside); Point2D geometryPoint2 = line.CharacteristicPoints.GetPoint2D(CharacteristicPointType.ShoulderBaseInside); if (geometryPoint1 != null && geometryPoint2 != null) { return geometryPoint1.Z - line.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DikeToeAtPolder).Z; } return 0.0; } /// /// Determines the width of the shoulder for this surfaceline. /// /// shoulder length /// Method requires the existenc of the following characteristic points when /// and /// are defined: /// /// /// /// /// /// public static double DetermineShoulderLength(this SurfaceLine2 line) { Point2D shoulderTopInside = line.CharacteristicPoints.GetPoint2D(CharacteristicPointType.ShoulderTopInside); if (shoulderTopInside != null && HasAnnotation(line, CharacteristicPointType.ShoulderBaseInside)) { return line.DetermineShoulderLengthForGivenShoulderTopInside(shoulderTopInside); } return 0.0; } /// /// Gets the cotangent of inner slope. /// /// The line. /// /// /// GetCotangentOfInnerSlope requires characteristic point DikeTopAtPolder to be defined. /// or /// GetCotangentOfInnerSlope requires either of characteristic points ShoulderBaseInside or DikeToeAtPolder to be defined. /// public static double GetCotangentOfInnerSlope(this SurfaceLine2 line) { Point2D geometryPoint = line.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DikeTopAtPolder); if (geometryPoint == null) { throw new InvalidOperationException(Resources.GetCotangentOfInnerSlopeDikeTopAtPolderRequired); } Point2D dikeToeInward = line.GetDikeToeInward(); if (dikeToeInward == null) { throw new InvalidOperationException(Resources.GetCotangentOfInnerSlopeDikeToeAtPolderRequired); } return Math.Abs((dikeToeInward.X - geometryPoint.X) / (geometryPoint.Z - dikeToeInward.Z)); } /// /// Gets the limit point for shoulder design. /// /// The line. /// public static Point2D GetLimitPointForShoulderDesign(this SurfaceLine2 line) { return line.CharacteristicPoints.GetPoint2D(CharacteristicPointType.ShoulderTopInside) ?? line.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DikeToeAtPolder); } /// /// Removes the points between the two x values. /// /// Surfaceline being modified. /// The starting X-Coordinate. /// The ending X-Coordinate. /// Indicates if and /// are inclusive bounds or not. private static void RemoveGeometryPointsInRange(SurfaceLine2 line, double startX, double endX, bool isInclusiveRange) { foreach (Point2D geometryPoint in GetGeometryPointsWithinRange(line, startX, endX, isInclusiveRange).ToArray()) { CharacteristicPoint[] characteristicPoints = line.CharacteristicPoints.Where(cp => ReferenceEquals(cp.Point, geometryPoint)).ToArray(); if (characteristicPoints.Length > 0) { // CharacteristicPointSet will manage both collections of CharacteristicPoint instances and Geometry foreach (CharacteristicPoint characteristicPoint in characteristicPoints) { line.CharacteristicPoints.Remove(characteristicPoint); } } else { // Notify change such that CharacteristicPointSet instances observing this // geometry can update if required. line.Geometry.Points.Remove(geometryPoint); } } } /// /// Retrieve all instances of a surfaceline that /// fall with the range. /// /// Surfaceline being evaluated. /// Starting X-coordinate. /// Ending X-coordinate. /// Indicates if and /// are inclusive bounds or not. /// Collection of characteristic points within the given range. private static IEnumerable GetGeometryPointsWithinRange(SurfaceLine2 line, double startX, double endX, bool isInclusiveRange) { if (isInclusiveRange) { return line.Geometry.Points.Where(cp => (cp.X >= startX || cp.X.AlmostEquals(startX, GeometryPoint.Precision)) && (cp.X <= endX || cp.X.AlmostEquals(endX, GeometryPoint.Precision))); } return line.Geometry.Points.Where(cp => cp.X > startX && cp.X < endX); } #region Private methods /// /// Create a horizontal line from a given starting X coordinate and ending at the /// X coordinate of defined /// in the surfaceline. /// /// The referenced surfaceline. /// The height level of the horizontal line. /// The starting coordinate. /// The line segment. private static Line GetWaterlevelLineStartingFrom(SurfaceLine2 line, double level, double startXCoordinate) { Point2D pointEndOfprofile = line.CharacteristicPoints.GetPoint2D(CharacteristicPointType.SurfaceLevelInside); var waterlevelLine = new Line(); waterlevelLine.CreateHorizontalZLine(startXCoordinate, pointEndOfprofile.X, level); return waterlevelLine; } /// /// Finds the intersection point of a given horizontal line and the river side talud. /// /// The evaluated surfaceline. /// The horizontal line. /// The intersection point, or null in case no intersection was found. /// When height of the horizontal line is /// greater than the height of the characteristic point . private static Point2D DetermineIntersectionWithHorizontalLevel(SurfaceLine2 line, Line waterlevelLine) { ThrowWhenLevelAboveDike(line, waterlevelLine.BeginPoint.Z); List list = line.Geometry.Points.Where(point => point.X >= line.CharacteristicPoints.GetPoint2D(CharacteristicPointType.SurfaceLevelOutside).X && point.X <= line.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DikeTopAtRiver).X).ToList(); for (var i = 1; i < list.Count; i++) { var surfaceLineSegment = new Line(); surfaceLineSegment.SetBeginAndEndPoints( new Point2D(list[i - 1].X, list[i - 1].Z), new Point2D(list[i].X, list[i].Z)); Point2D intersectPoint = surfaceLineSegment.GetIntersectPointXz(waterlevelLine); if (intersectPoint != null) { return new Point2D(intersectPoint.X, intersectPoint.Z); } } return null; } /// /// Throw an in case the given height level is /// higher than the characteristic point . /// private static void ThrowWhenLevelAboveDike(SurfaceLine2 line, double level) { Point2D dikeTopAtRiver = line.CharacteristicPoints.GetPoint2D(CharacteristicPointType.DikeTopAtRiver); if (level > dikeTopAtRiver.Z) { throw new SurfaceLineException($"Level ({level:F2} m) should NOT be higher than dike top at river side ({dikeTopAtRiver.Z:F2}))"); } } private static void ThrowWhenLevelBelowDikeToeDike(double levelDikeToeRiverSide, double riverLevel) { throw new SurfaceLineException($"River level ({riverLevel:F2} m) should be higher than dike toe at river side ({levelDikeToeRiverSide:F2}))"); } #endregion Private methods }