// 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 Lesser 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 Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser 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 Core.Common.Base.Properties; using MathNet.Numerics.LinearAlgebra; namespace Core.Common.Base.Geometry { /// /// This class represents an immutable line-segment between two . /// public sealed class Segment2D { /// /// Creates a new instance of , with the set to /// and the set to . /// /// The first of the . /// The second of the . /// Thrown when either the or /// point is null. public Segment2D(Point2D first, Point2D second) { if (first == null) { throw new ArgumentNullException(nameof(first), Resources.Segment2D_Constructor_Segment_must_be_created_with_two_points); } if (second == null) { throw new ArgumentNullException(nameof(second), Resources.Segment2D_Constructor_Segment_must_be_created_with_two_points); } FirstPoint = first; SecondPoint = second; Length = FirstPoint.GetEuclideanDistanceTo(SecondPoint); } /// /// The first of the . /// public Point2D FirstPoint { get; } /// /// The second of the . /// public Point2D SecondPoint { get; } /// /// Gets the (euclidean) length of the segment. /// public double Length { get; } /// /// This method determines whether is contained by the /// and X-coordinates. /// /// The x for which to find out whether it is contained by the /// and . /// /// true if is on or between the points' X-coordinates. /// false otherwise. /// public bool ContainsX(double x) { double distanceFirstPoint = FirstPoint.X - x; double distanceSecondPoint = SecondPoint.X - x; bool onPoint = Math.Abs(FirstPoint.X - x) < 1e-6 || Math.Abs(SecondPoint.X - x) < 1e-6; return onPoint || Math.Sign(distanceFirstPoint) != Math.Sign(distanceSecondPoint); } /// /// Determines whether the is vertical. /// /// true if the is vertical. false otherwise. public bool IsVertical() { return Math.Abs(FirstPoint.X - SecondPoint.X) < 1e-6 && Math.Abs(FirstPoint.Y - SecondPoint.Y) >= 1e-6; } /// /// Gets the euclidean distance from this 2D line segment to some 2D point. /// /// The point to measure distance to. /// A value of 0 or greater. /// Thrown when is null. public double GetEuclideanDistanceToPoint(Point2D point) { if (point == null) { throw new ArgumentNullException(nameof(point)); } Vector segmentVector = SecondPoint - FirstPoint; // Vector from FirstPoint to SecondPoint Vector orientationVector = point - FirstPoint; // Vector from FirstPoint to 'point' // Situation sketch, normalized along the segment: // A : B : C // .....p1----p2...... // : : // 1. Use numerator part of vector projection to determine relative location of 'point': double dotProductOrientationVector = segmentVector.DotProduct(orientationVector); if (dotProductOrientationVector <= 0) { // 'point' falls outside the perpendicular area defined by segment, specifically: Zone A return point.GetEuclideanDistanceTo(FirstPoint); } // 2. Use denominator part of vector projection to determine relative location of 'point': double dotProductSegmentVector = segmentVector.DotProduct(segmentVector); if (dotProductSegmentVector <= dotProductOrientationVector) { // 'point' falls outside the perpendicular area defined by segment, specifically: Zone C return point.GetEuclideanDistanceTo(SecondPoint); } // 'point' falls within the perpendicular area defined by the segment (zone B). // 3. Use remainder of vector projection to determine point on segment for perpendicular line: double projectionFactor = dotProductOrientationVector / dotProductSegmentVector; double perpendicularOnSegmentX = FirstPoint.X + projectionFactor * segmentVector[0]; double perpendicularOnSegmentY = FirstPoint.Y + projectionFactor * segmentVector[1]; var perpendicularLineIntersectionPoint = new Point2D(perpendicularOnSegmentX, perpendicularOnSegmentY); return point.GetEuclideanDistanceTo(perpendicularLineIntersectionPoint); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } if (ReferenceEquals(this, obj)) { return true; } if (obj.GetType() != GetType()) { return false; } return Equals((Segment2D) obj); } public override int GetHashCode() { unchecked { return ((FirstPoint.X + SecondPoint.X).GetHashCode() * 397) ^ (FirstPoint.Y + SecondPoint.Y).GetHashCode(); } } private bool Equals(Segment2D other) { return FirstPoint.Equals(other.FirstPoint) && SecondPoint.Equals(other.SecondPoint) || FirstPoint.Equals(other.SecondPoint) && SecondPoint.Equals(other.FirstPoint); } } }