// Copyright (C) Stichting Deltares 2017. All rights reserved. // // This file is part of Ringtoets. // // Ringtoets is free software: you can redistribute it and/or modify // it under the terms of the GNU 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 General Public License for more details. // // You should have received a copy of the GNU 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.Collections.ObjectModel; using System.Globalization; using System.Linq; using Core.Common.Base; using Core.Common.Base.Data; using Core.Common.Base.Geometry; using Ringtoets.Common.Data; using Ringtoets.MacroStabilityInwards.Primitives.Exceptions; using Ringtoets.MacroStabilityInwards.Primitives.Properties; using RingtoetsCommonDataResources = Ringtoets.Common.Data.Properties.Resources; namespace Ringtoets.MacroStabilityInwards.Primitives { /// /// Definition of a surface line for macro stability inwards. /// public class MacroStabilityInwardsSurfaceLine : Observable, IMechanismSurfaceLine { private const int numberOfDecimalPlaces = 2; /// /// Initializes a new instance of the class. /// public MacroStabilityInwardsSurfaceLine() { Name = string.Empty; Points = new Point3D[0]; LocalGeometry = new RoundedPoint2DCollection(2, new Point2D[0]); } /// /// Gets or sets the name of the surface line. /// public string Name { get; set; } /// /// Gets the 3D points describing the geometry of the surface line. /// public Point3D[] Points { get; private set; } /// /// Gets the first 3D geometry point defining the surface line in world coordinates. /// public Point3D StartingWorldPoint { get; private set; } /// /// Gets the last 3D geometry point defining the surface line in world coordinates. /// public Point3D EndingWorldPoint { get; private set; } /// /// Gets the location which generalizes the height of the surface /// on the outside of the polder. /// public Point3D SurfaceLevelOutside { get; private set; } /// /// Gets the location of dike toe when approaching from outside /// the polder. /// public Point3D DikeToeAtRiver { get; private set; } /// /// Gets the location of the start of traffic load when approaching /// from outside the polder. /// public Point3D TrafficLoadOutside { get; private set; } /// /// Gets the location of the start of traffic load when approaching /// from inside the polder. /// public Point3D TrafficLoadInside { get; private set; } /// /// Gets the location of the top of the dike when approaching from /// inside the polder. /// public Point3D DikeTopAtPolder { get; private set; } /// /// Gets the location where the shoulder on the side of the polder /// connects with the dike. /// public Point3D ShoulderBaseInside { get; private set; } /// /// Gets the location where the shoulder on the side of the polder /// declines towards the location of the dike toe when approaching from inside /// the polder. /// public Point3D ShoulderTopInside { get; private set; } /// /// Gets the location of dike toe when approaching from inside /// the polder. /// public Point3D DikeToeAtPolder { get; private set; } /// /// Gets the location of the start of the ditch when approaching /// from the dike. /// public Point3D DitchDikeSide { get; private set; } /// /// Gets the location of the bottom of the ditch when approaching /// from the dike. /// public Point3D BottomDitchDikeSide { get; private set; } /// /// Gets the location of the bottom of the ditch when approaching /// from inside the polder. /// public Point3D BottomDitchPolderSide { get; private set; } /// /// Gets the location of the start of the ditch when approaching from /// inside the polder. /// public Point3D DitchPolderSide { get; private set; } /// /// Gets the location which generalizes the surface level on the /// inside of the polder. /// public Point3D SurfaceLevelInside { get; private set; } /// /// Gets or sets the reference line intersection point in world coordinates. /// public Point2D ReferenceLineIntersectionWorldPoint { get; set; } /// /// Gets the 2D points describing the local geometry of the surface line. /// public RoundedPoint2DCollection LocalGeometry { get; private set; } /// /// Sets the geometry of the surface line. /// /// The collection of points defining the surface line geometry. /// Thrown when is null. /// Thrown when any element of is null. public void SetGeometry(IEnumerable points) { if (points == null) { throw new ArgumentNullException(nameof(points), Resources.MacroStabilityInwardsSurfaceLine_Collection_of_points_for_geometry_is_null); } if (points.Any(p => p == null)) { throw new ArgumentException(Resources.MacroStabilityInwardsSurfaceLine_A_point_in_the_collection_was_null); } Points = points.Select(p => new Point3D(p)).ToArray(); if (Points.Length > 0) { StartingWorldPoint = Points[0]; EndingWorldPoint = Points[Points.Length - 1]; } LocalGeometry = new RoundedPoint2DCollection(numberOfDecimalPlaces, Points.ProjectToLZ().ToArray()); } /// /// Sets the at the given point. /// /// The location as a which to set as the . /// Thrown when doesn't contain a at /// . /// Thrown when is null. public void SetDitchPolderSideAt(Point3D point) { Point3D geometryPoint = GetPointFromGeometry(point); if (geometryPoint == null) { throw CreatePointNotInGeometryException(point, RingtoetsCommonDataResources.CharacteristicPoint_DitchPolderSide); } DitchPolderSide = geometryPoint; } /// /// Sets the at the given point. /// /// The location as a which to set as the . /// Thrown when doesn't contain a at /// . /// Thrown when is null. public void SetBottomDitchPolderSideAt(Point3D point) { Point3D geometryPoint = GetPointFromGeometry(point); if (geometryPoint == null) { throw CreatePointNotInGeometryException(point, RingtoetsCommonDataResources.CharacteristicPoint_BottomDitchPolderSide); } BottomDitchPolderSide = geometryPoint; } /// /// Sets the at the given point. /// /// The location as a which to set as the . /// Thrown when doesn't contain a at /// . /// Thrown when is null. public void SetBottomDitchDikeSideAt(Point3D point) { Point3D geometryPoint = GetPointFromGeometry(point); if (geometryPoint == null) { throw CreatePointNotInGeometryException(point, RingtoetsCommonDataResources.CharacteristicPoint_BottomDitchDikeSide); } BottomDitchDikeSide = geometryPoint; } /// /// Sets the at the given point. /// /// The location as a which to set as the . /// Thrown when doesn't contain a at /// . /// Thrown when is null. public void SetDitchDikeSideAt(Point3D point) { Point3D geometryPoint = GetPointFromGeometry(point); if (geometryPoint == null) { throw CreatePointNotInGeometryException(point, RingtoetsCommonDataResources.CharacteristicPoint_DitchDikeSide); } DitchDikeSide = geometryPoint; } /// /// Sets the at the given point. /// /// The location as a which to set as the . /// Thrown when doesn't contain a at /// . /// Thrown when is null. public void SetDikeTopAtPolderAt(Point3D point) { Point3D geometryPoint = GetPointFromGeometry(point); if (geometryPoint == null) { throw CreatePointNotInGeometryException(point, RingtoetsCommonDataResources.CharacteristicPoint_DikeTopAtPolder); } DikeTopAtPolder = geometryPoint; } /// /// Sets the at the given point. /// /// The location as a which to set as the . /// Thrown when doesn't contain a at /// . /// Thrown when is null. public void SetShoulderBaseInsideAt(Point3D point) { Point3D geometryPoint = GetPointFromGeometry(point); if (geometryPoint == null) { throw CreatePointNotInGeometryException(point, RingtoetsCommonDataResources.CharacteristicPoint_ShoulderBaseInside); } ShoulderBaseInside = geometryPoint; } /// /// Sets the at the given point. /// /// The location as a which to set as the . /// Thrown when doesn't contain a at /// . /// Thrown when is null. public void SetShoulderTopInsideAt(Point3D point) { Point3D geometryPoint = GetPointFromGeometry(point); if (geometryPoint == null) { throw CreatePointNotInGeometryException(point, RingtoetsCommonDataResources.CharacteristicPoint_ShoulderTopInside); } ShoulderTopInside = geometryPoint; } /// /// Sets the at the given point. /// /// The location as a which to set as the . /// Thrown when doesn't contain a at /// . /// Thrown when is null. public void SetTrafficLoadInsideAt(Point3D point) { Point3D geometryPoint = GetPointFromGeometry(point); if (geometryPoint == null) { throw CreatePointNotInGeometryException(point, RingtoetsCommonDataResources.CharacteristicPoint_TrafficLoadInside); } TrafficLoadInside = geometryPoint; } /// /// Sets the at the given point. /// /// The location as a which to set as the . /// Thrown when doesn't contain a at /// . /// Thrown when is null. public void SetTrafficLoadOutsideAt(Point3D point) { Point3D geometryPoint = GetPointFromGeometry(point); if (geometryPoint == null) { throw CreatePointNotInGeometryException(point, RingtoetsCommonDataResources.CharacteristicPoint_TrafficLoadOutside); } TrafficLoadOutside = geometryPoint; } /// /// Sets the at the given point. /// /// The location as a which to set as the . /// Thrown when doesn't contain a at /// . /// Thrown when is null. public void SetDikeToeAtRiverAt(Point3D point) { Point3D geometryPoint = GetPointFromGeometry(point); if (geometryPoint == null) { throw CreatePointNotInGeometryException(point, RingtoetsCommonDataResources.CharacteristicPoint_DikeToeAtRiver); } DikeToeAtRiver = geometryPoint; } /// /// Sets the at the given point. /// /// The location as a which to set as the . /// Thrown when doesn't contain a at /// . /// Thrown when is null. public void SetDikeToeAtPolderAt(Point3D point) { Point3D geometryPoint = GetPointFromGeometry(point); if (geometryPoint == null) { throw CreatePointNotInGeometryException(point, RingtoetsCommonDataResources.CharacteristicPoint_DikeToeAtPolder); } DikeToeAtPolder = geometryPoint; } /// /// Sets the at the given point. /// /// The location as a which to set as the . /// Thrown when doesn't contain a at /// . /// Thrown when is null. public void SetSurfaceLevelInsideAt(Point3D point) { Point3D geometryPoint = GetPointFromGeometry(point); if (geometryPoint == null) { throw CreatePointNotInGeometryException(point, RingtoetsCommonDataResources.CharacteristicPoint_SurfaceLevelInside); } SurfaceLevelInside = geometryPoint; } /// /// Sets the at the given point. /// /// The location as a which to set as the . /// Thrown when doesn't contain a at /// . /// Thrown when is null. public void SetSurfaceLevelOutsideAt(Point3D point) { Point3D geometryPoint = GetPointFromGeometry(point); if (geometryPoint == null) { throw CreatePointNotInGeometryException(point, RingtoetsCommonDataResources.CharacteristicPoint_SurfaceLevelOutside); } SurfaceLevelOutside = geometryPoint; } /// /// Gets the height of the projected at a L=. /// /// The L coordinate from where to take the height of the . /// The height of the at L=. /// Thrown when the /// intersection point at have a significant difference in their y coordinate. /// Thrown when is not in range of the LZ-projected . /// Thrown when is empty. public double GetZAtL(RoundedDouble l) { ValidateHasPoints(); if (!ValidateInRange(l)) { var localRangeL = new Range(LocalGeometry.First().X, LocalGeometry.Last().X); string outOfRangeMessage = string.Format(Resources.MacroStabilityInwardsSurfaceLine_0_L_needs_to_be_in_Range_1_, Resources.MacroStabilityInwardsSurfaceLine_GetZAtL_Cannot_determine_height, localRangeL.ToString(FormattableConstants.ShowAtLeastOneDecimal, CultureInfo.CurrentCulture)); throw new ArgumentOutOfRangeException(null, outOfRangeMessage); } var segments = new Collection(); for (var i = 1; i < LocalGeometry.Count(); i++) { segments.Add(new Segment2D(LocalGeometry.ElementAt(i - 1), LocalGeometry.ElementAt(i))); } IEnumerable intersectionPoints = Math2D.SegmentsIntersectionWithVerticalLine(segments, l).OrderBy(p => p.Y).ToArray(); const double intersectionTolerance = 1e-2; bool equalIntersections = Math.Abs(intersectionPoints.First().Y - intersectionPoints.Last().Y) < intersectionTolerance; if (equalIntersections) { return intersectionPoints.First().Y; } string message = string.Format(Resources.MacroStabilityInwardsSurfaceLine_Cannot_determine_reliable_z_when_surface_line_is_vertical_in_l, l); throw new MacroStabilityInwardsSurfaceLineException(message); } /// /// Checks whether is in range of the geometry projected in local coordinate system /// where the points are ordered on the L-coordinate being monotonically non-decreasing. /// /// The local L-coordinate value to check for. /// true when local L-coordinate is in range of the local geometry. false otherwise. public bool ValidateInRange(double localCoordinateL) { Point2D firstLocalPoint = LocalGeometry.First(); Point2D lastLocalPoint = LocalGeometry.Last(); var roundedLocalCoordinateL = new RoundedDouble(numberOfDecimalPlaces, localCoordinateL); return !(firstLocalPoint.X > roundedLocalCoordinateL) && !(lastLocalPoint.X < roundedLocalCoordinateL); } /// /// Gets the local coordinate with rounded values based on the geometry of the surface line and the given world coordinate. /// /// The world coordinate to get the local coordinate for. /// The local coordinate. public Point2D GetLocalPointFromGeometry(Point3D worldCoordinate) { int count = Points.Length; if (count <= 1) { return new Point2D(double.NaN, double.NaN); } Point3D first = Points.First(); Point3D last = Points.Last(); var firstPoint = new Point2D(first.X, first.Y); var lastPoint = new Point2D(last.X, last.Y); Point2D localCoordinate = worldCoordinate.ProjectIntoLocalCoordinates(firstPoint, lastPoint); return new Point2D(new RoundedDouble(numberOfDecimalPlaces, localCoordinate.X), new RoundedDouble(numberOfDecimalPlaces, localCoordinate.Y)); } /// /// Copies the property values of the to /// the . /// /// The /// to get the property values from. /// Thrown when /// is null. public void CopyProperties(MacroStabilityInwardsSurfaceLine fromSurfaceLine) { if (fromSurfaceLine == null) { throw new ArgumentNullException(nameof(fromSurfaceLine)); } Name = fromSurfaceLine.Name; ReferenceLineIntersectionWorldPoint = fromSurfaceLine.ReferenceLineIntersectionWorldPoint != null ? new Point2D(fromSurfaceLine.ReferenceLineIntersectionWorldPoint) : null; SetGeometry(fromSurfaceLine.Points); SetCharacteristicPoints(fromSurfaceLine); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } if (ReferenceEquals(this, obj)) { return true; } var other = obj as MacroStabilityInwardsSurfaceLine; return other != null && Equals((MacroStabilityInwardsSurfaceLine) obj); } public override int GetHashCode() { unchecked { int hashCode = Name.GetHashCode(); foreach (Point3D point in Points) { hashCode = (hashCode * 397) ^ point.GetHashCode(); } hashCode = (hashCode * 397) ^ (SurfaceLevelInside?.GetHashCode() ?? 0); hashCode = (hashCode * 397) ^ (SurfaceLevelOutside?.GetHashCode() ?? 0); hashCode = (hashCode * 397) ^ (TrafficLoadInside?.GetHashCode() ?? 0); hashCode = (hashCode * 397) ^ (TrafficLoadOutside?.GetHashCode() ?? 0); hashCode = (hashCode * 397) ^ (DikeTopAtPolder?.GetHashCode() ?? 0); hashCode = (hashCode * 397) ^ (ShoulderBaseInside?.GetHashCode() ?? 0); hashCode = (hashCode * 397) ^ (ShoulderTopInside?.GetHashCode() ?? 0); hashCode = (hashCode * 397) ^ (DikeToeAtPolder?.GetHashCode() ?? 0); hashCode = (hashCode * 397) ^ (DikeToeAtRiver?.GetHashCode() ?? 0); hashCode = (hashCode * 397) ^ (DitchDikeSide?.GetHashCode() ?? 0); hashCode = (hashCode * 397) ^ (DitchPolderSide?.GetHashCode() ?? 0); hashCode = (hashCode * 397) ^ (BottomDitchDikeSide?.GetHashCode() ?? 0); hashCode = (hashCode * 397) ^ (BottomDitchPolderSide?.GetHashCode() ?? 0); return hashCode; } } public override string ToString() { return Name; } private void SetCharacteristicPoints(MacroStabilityInwardsSurfaceLine fromSurfaceLine) { SurfaceLevelOutside = PointFromGeometryOrNull(fromSurfaceLine.SurfaceLevelOutside); TrafficLoadOutside = PointFromGeometryOrNull(fromSurfaceLine.TrafficLoadOutside); TrafficLoadInside = PointFromGeometryOrNull(fromSurfaceLine.TrafficLoadInside); DikeTopAtPolder = PointFromGeometryOrNull(fromSurfaceLine.DikeTopAtPolder); ShoulderBaseInside = PointFromGeometryOrNull(fromSurfaceLine.ShoulderBaseInside); ShoulderTopInside = PointFromGeometryOrNull(fromSurfaceLine.ShoulderTopInside); BottomDitchDikeSide = PointFromGeometryOrNull(fromSurfaceLine.BottomDitchDikeSide); BottomDitchPolderSide = PointFromGeometryOrNull(fromSurfaceLine.BottomDitchPolderSide); DikeToeAtPolder = PointFromGeometryOrNull(fromSurfaceLine.DikeToeAtPolder); DikeToeAtRiver = PointFromGeometryOrNull(fromSurfaceLine.DikeToeAtRiver); DitchDikeSide = PointFromGeometryOrNull(fromSurfaceLine.DitchDikeSide); DitchPolderSide = PointFromGeometryOrNull(fromSurfaceLine.DitchPolderSide); SurfaceLevelInside = PointFromGeometryOrNull(fromSurfaceLine.SurfaceLevelInside); } private Point3D PointFromGeometryOrNull(Point3D point3D) { return point3D != null ? GetPointFromGeometry(point3D) : null; } /// /// Finds a point from which is at the same position as . /// /// The location of a point from . /// The from at the same location as . /// Thrown when is null. private Point3D GetPointFromGeometry(Point3D point) { if (point == null) { throw new ArgumentNullException(nameof(point), @"Cannot find a point in geometry using a null point."); } return Points.FirstOrDefault(p => p.Equals(point)); } private static ArgumentException CreatePointNotInGeometryException(Point3D point, string characteristicPointDescription) { string message = string.Format(Resources.MacroStabilityInwardsSurfaceLine_SetCharacteristicPointAt_Geometry_does_not_contain_point_at_0_to_assign_as_characteristic_point_1_, point, characteristicPointDescription); return new ArgumentException(message); } private bool Equals(MacroStabilityInwardsSurfaceLine other) { return string.Equals(Name, other.Name) && Equals(ReferenceLineIntersectionWorldPoint, other.ReferenceLineIntersectionWorldPoint) && EqualGeometricPoints(other.Points) && EqualCharacteristicPoints(other); } private bool EqualCharacteristicPoints(MacroStabilityInwardsSurfaceLine other) { return Equals(SurfaceLevelInside, other.SurfaceLevelInside) && Equals(SurfaceLevelOutside, other.SurfaceLevelOutside) && Equals(TrafficLoadInside, other.TrafficLoadInside) && Equals(TrafficLoadOutside, other.TrafficLoadOutside) && Equals(DikeTopAtPolder, other.DikeTopAtPolder) && Equals(ShoulderBaseInside, other.ShoulderBaseInside) && Equals(ShoulderTopInside, other.ShoulderTopInside) && Equals(DikeToeAtPolder, other.DikeToeAtPolder) && Equals(DikeToeAtRiver, other.DikeToeAtRiver) && Equals(DitchDikeSide, other.DitchDikeSide) && Equals(DitchPolderSide, other.DitchPolderSide) && Equals(BottomDitchDikeSide, other.BottomDitchDikeSide) && Equals(BottomDitchPolderSide, other.BottomDitchPolderSide); } private bool EqualGeometricPoints(Point3D[] otherPoints) { int nrOfOtherPoints = otherPoints.Length; if (Points.Length != nrOfOtherPoints) { return false; } for (var index = 0; index < Points.Length; index++) { if (!Points[index].Equals(otherPoints[index])) { return false; } } return true; } /// /// Checks whether the current collection is not empty. /// /// Thrown when is empty. private void ValidateHasPoints() { if (!Points.Any()) { throw new InvalidOperationException(Resources.MacroStabilityInwardsSurfaceLine_SurfaceLine_has_no_Geometry); } } } }