// Copyright (C) Stichting Deltares 2017. 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.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; Point2D point = null; var newPoint = new Point2D() { X = x, Z = z }; GeometryPoint gp = null; if (type.HasValue) { // Get point of this type.. gp = line.CharacteristicPoints.GetGeometryPoint(type.Value); if (gp != null) { point = new Point2D(gp.X, gp.Z); // 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(gp).Count(cpt => cpt != CharacteristicPointType.None) > 1; if (isAssignedToOtherCharacteristicPoints) { // Other characteristic points exist with the same coordinates so add as new point point = line.Geometry.GetPointAt(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; } gp.X = point.X; gp.Z = point.Z; } } } if (point == null) { point = line.Geometry.GetPointAt(x, z); // Get point at specified coords } if (point == null) { point = new Point2D() { X = x, Z = z }; addPoint = true; } if (addPoint) { var newgp = new GeometryPoint(point.X, point.Z); line.Geometry.Points.Add(newgp); line.AddCharacteristicPoint(newgp, type ?? CharacteristicPointType.None); } else if (type.HasValue && !movedPointToMatch) { if (line.CharacteristicPoints.Any(cp => ReferenceEquals(cp.GeometryPoint, gp) && cp.CharacteristicPointType != CharacteristicPointType.None)) { line.AddCharacteristicPoint(gp, type.Value); } else { int index = -1; for (int i = 0; i < line.CharacteristicPoints.Count; i++) { if (ReferenceEquals(line.CharacteristicPoints[i].GeometryPoint, gp)) { 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); } /// /// 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); } } } /// /// 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; } /// /// Make sure river level is above the bottom of the river. /// /// 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 double? EnsureWaterLevelIsAboveRiverBottom(this SurfaceLine2 line, double? riverLevel) { // Waterlevel is supposed to be at level of SurfaceLevelOutside when waterlevel // is below SurfaceLevelOutside (is the bottom of the river) if (riverLevel.HasValue) { double z = line.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.SurfaceLevelOutside).Z; riverLevel = Math.Max(z, riverLevel.Value); } return riverLevel; } /// /// 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); } /// /// 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)); } } } }