// 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);
}
}
}