// Copyright (C) Stichting Deltares 2023. 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.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.GetGeometryPoint(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.GetGeometryPoint(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) { var dikeTopAtRiver = line.CharacteristicPoints.GetGeometryPoint(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 GeometryPoint GetDikeToeInward(this SurfaceLine2 line) { return line.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.ShoulderBaseInside) ?? line.CharacteristicPoints.GetGeometryPoint(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) { var dikeToeAtRiver = line.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeToeAtRiver); var dikeToeAtPolder = line.CharacteristicPoints.GetGeometryPoint(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."); } bool movedPointToMatch = false; bool addPoint = false; GeometryPoint point = null; var newPoint = new GeometryPoint() { X = x, Z = z }; if (type.HasValue) { // Get point of this type.. point = line.CharacteristicPoints.GetGeometryPoint(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; } else { 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.GetGeometryPointAt(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.GetGeometryPointAt(x, z); // Get point at specified coords } if (point == null) { point = new GeometryPoint() { 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.GeometryPoint, point) && cp.CharacteristicPointType != CharacteristicPointType.None)) { line.AddCharacteristicPoint(point, type.Value); } else { int index = -1; for (int i = 0; i < line.CharacteristicPoints.Count; i++) { if (ReferenceEquals(line.CharacteristicPoints[i].GeometryPoint, point)) { index = i; break; } } line.CharacteristicPoints.Annotate(index, type.Value); } } line.Geometry.SyncCalcPoints(); } /// /// 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); } /// /// 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 (var geometryPoint in GetGeometryPointsWithinRange(line, startX, endX, isInclusiveRange).ToArray()) { var characteristicPoints = line.CharacteristicPoints.Where(cp => ReferenceEquals(cp.GeometryPoint, geometryPoint)).ToArray(); if (characteristicPoints.Length > 0) { // CharacteristicPointSet will manage both collections of CharacteristicPoint instances and Geometry foreach (var 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); } } line.Geometry.SyncCalcPoints(); } /// /// 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); } /// /// 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 var bottomDitchDikeSide = line.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.BottomDitchDikeSide); var bottomDitchPolderSide = line.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.BottomDitchPolderSide); res = (bottomDitchDikeSide != null && bottomDitchPolderSide != null); // check the ditch points describe following shape: // 0 0 // \ / // 0---0 var ditchPolderSide = line.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DitchPolderSide); var ditchDikeSide = line.CharacteristicPoints.GetGeometryPoint(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 GeometryPoint GetStartingPoint(this SurfaceLine2 line) { return line.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.ShoulderBaseOutside) ?? line.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.ShoulderTopOutside) ?? line.CharacteristicPoints.GetGeometryPoint(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 GeometryPoint point = line.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeTopAtRiver); return point == null ? null : (double?)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.GetGeometryPoint(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 GeometryPoint DetermineIntersectionWithLevel(this SurfaceLine2 line, double level) { double startXCoordinate = line.Geometry.GetMinX(); var 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 GeometryPoint DetermineIntersectionBetweenTaludRiverSideAndWaterLevel(this SurfaceLine2 line, double level) { double startXCoordinate = line.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.SurfaceLevelOutside).X; var 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, GeometryPoint shoulderTopInside) { GeometryPoint geometryPoint1 = line.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeTopAtPolder); GeometryPoint geometryPoint2 = line.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeToeAtPolder); GeometryPoint p2 = line.HasShoulderInside() ? line.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.ShoulderBaseInside) : geometryPoint2; GeometryPoint geometryPoint3 = line.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.SurfaceLevelOutside); GeometryPoint withExtrapolation = LineHelper.GetIntersectionPointWithExtrapolation(geometryPoint1, p2, shoulderTopInside, new GeometryPoint(geometryPoint3.X, shoulderTopInside.Z)); return shoulderTopInside.X - withExtrapolation.X; } /// /// Determines the height of the shoulder. /// /// The line. /// public static double DetermineShoulderHeight(this SurfaceLine2 line) { GeometryPoint geometryPoint1 = line.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.ShoulderTopInside); GeometryPoint geometryPoint2 = line.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.ShoulderBaseInside); if (geometryPoint1 != null && geometryPoint2 != null) return geometryPoint1.Z - line.CharacteristicPoints.GetGeometryPoint(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) { var shoulderTopInside = line.CharacteristicPoints.GetGeometryPoint(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) { GeometryPoint geometryPoint = line.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeTopAtPolder); if (geometryPoint == null) throw new InvalidOperationException(Resources.GetCotangentOfInnerSlopeDikeTopAtPolderRequired); GeometryPoint 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 GeometryPoint GetLimitPointForShoulderDesign(this SurfaceLine2 line) { return line.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.ShoulderTopInside) ?? line.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeToeAtPolder); } #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) { GeometryPoint pointEndOfprofile = line.CharacteristicPoints.GetGeometryPoint(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 GeometryPoint DetermineIntersectionWithHorizontalLevel(SurfaceLine2 line, Line waterlevelLine) { ThrowWhenLevelAboveDike(line, waterlevelLine.BeginPoint.Z); var list = line.Geometry.Points.Where(point => point.X >= line.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.SurfaceLevelOutside).X && point.X <= line.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeTopAtRiver).X).ToList(); for (int 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)); var intersectPoint = surfaceLineSegment.GetIntersectPointXz(waterlevelLine); if (intersectPoint != null) { return new GeometryPoint(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) { var dikeTopAtRiver = line.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeTopAtRiver); if (level > dikeTopAtRiver.Z) { throw new SurfaceLineException(String.Format( "Level ({0:F2} m) should NOT be higher than surface line ({1:F2}))", level, dikeTopAtRiver.Z)); } } private static void ThrowWhenLevelBelowDikeToeDike(double levelDikeToeRiverSide, double riverLevel) { throw new SurfaceLineException(String.Format( "River level ({0:F2} m) should be higher than dike toe at river side ({1:F2}))", riverLevel, levelDikeToeRiverSide)); } #endregion Private methods } }