// 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.Exceptions; using Ringtoets.Common.Data.Properties; namespace Ringtoets.Common.Data { /// /// Base class for mechanism specific surface lines. /// public abstract class MechanismSurfaceLineBase : Observable, IMechanismSurfaceLine { private const int numberOfDecimalPlaces = 2; /// /// Initializes a new instance of the class. /// /// The name of the surface line. /// Thrown when is null. protected MechanismSurfaceLineBase(string name) { if (name == null) { throw new ArgumentNullException(nameof(name)); } Name = name; Points = new Point3D[0]; LocalGeometry = new RoundedPoint2DCollection(numberOfDecimalPlaces, new Point2D[0]); } /// /// Gets the name of the surface line. /// public string Name { get; protected 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 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.MechanismSurfaceLineBase_Collection_of_points_for_geometry_is_null); } if (points.Any(p => p == null)) { throw new ArgumentException(Resources.MechanismSurfaceLineBase_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()); } /// /// 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)); } /// /// 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.MechanismSurfaceLineBase_0_L_needs_to_be_in_Range_1_, Resources.MechanismSurfaceLineBase_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.MechanismSurfaceLineBase_Cannot_determine_reliable_z_when_surface_line_is_vertical_in_l, l); throw new MechanismSurfaceLineException(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); } public override string ToString() { return Name; } /// /// 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. protected 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)); } /// /// Creates a configured for the case that a characteristic point /// is not in the geometry of the surface line. /// /// The point that is not in the geometry. /// The description of the characteristic point. /// Returns a configured . protected static ArgumentException CreatePointNotInGeometryException(Point3D point, string characteristicPointDescription) { string message = string.Format(Resources.MechanismSurfaceLineBase_SetCharacteristicPointAt_Geometry_does_not_contain_point_at_0_to_assign_as_characteristic_point_1_, point, characteristicPointDescription); return new ArgumentException(message); } /// /// Checks whether the current collection is not empty. /// /// Thrown when is empty. private void ValidateHasPoints() { if (!Points.Any()) { throw new InvalidOperationException(Resources.MechanismSurfaceLineBase_SurfaceLine_has_no_Geometry); } } } }