// Copyright (C) Stichting Deltares 2016. 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 System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Core.Common.Base.Properties;
using MathNet.Numerics.LinearAlgebra.Double;
namespace Core.Common.Base.Geometry
{
///
/// This class contains general mathematical routines for 2D lines.
///
public static class Math2D
{
///
/// Constant which is used to precision errors in comparisons.
///
private const double epsilonForComparisons = 1e-6;
///
/// Splits the line geometry at given lengths.
///
/// The line to split.
/// The lengths where the splits should be placed.
/// A sequence of line geometries of N elements long where N is the number
/// of elements in .
/// When the sum of all elements in
/// does not fully cover the line given by - or - when
/// contains negative values - or - when
/// does not have 2 or more elements.
public static Point2D[][] SplitLineAtLengths(IEnumerable linePoints, double[] lengths)
{
if (lengths.Any(l => l < 0))
{
throw new ArgumentException(Resources.Math2D_SplitLineAtLengths_All_lengths_cannot_be_negative, "lengths");
}
if (linePoints.Count() <= 1)
{
throw new ArgumentException(Resources.Math2D_SplitLineAtLengths_Not_enough_points_to_make_line, "linePoints");
}
Segment2D[] lineSegments = ConvertLinePointsToLineSegments(linePoints).ToArray();
if (Math.Abs(lengths.Sum(l => l) - lineSegments.Sum(s => s.Length)) > epsilonForComparisons)
{
throw new ArgumentException(Resources.Math2D_SplitLineAtLengths_Sum_of_lengths_must_equal_line_length, "lengths");
}
return SplitLineSegmentsAtLengths(lineSegments, lengths);
}
///
/// Creates an enumerator that converts a sequence of line points to a sequence of line segments.
///
/// The line points.
/// A sequence of N elements, where N is the number of elements in
/// - 1, or 0 if only has one or no elements.
public static IEnumerable ConvertLinePointsToLineSegments(IEnumerable linePoints)
{
Point2D endPoint = null;
foreach (Point2D linePoint in linePoints)
{
Point2D startPoint = endPoint;
endPoint = linePoint;
if (startPoint != null)
{
yield return new Segment2D(startPoint, endPoint);
}
}
}
///
/// Determines the intersection point of a line which passes through the and
/// the ; and a line which passes through the
/// and the .
///
/// A which the first line passes through.
/// Another which the first line passes through.
/// A which the second line passes through.
/// Another which the second line passes through.
/// An with coordinates at the point where the lines intersect. Or null when no
/// intersection point exists (lines are parallel).
///
/// Taken from: https://www.topcoder.com/community/data-science/data-science-tutorials/geometry-concepts-line-intersection-and-its-applications/
/// Based on https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
///
/// Thrown when equals or
/// equals , which makes it impossible to determine
/// a line through the points.
public static Point2D LineIntersectionWithLine(Point2D line1Point1, Point2D line1Point2, Point2D line2Point1, Point2D line2Point2)
{
if (line1Point1.Equals(line1Point2) || line2Point1.Equals(line2Point2))
{
throw new ArgumentException(Resources.Math2D_LineIntersectionWithLine_Line_points_are_equal);
}
var aLine = line1Point2.Y - line1Point1.Y;
var bLine = line1Point1.X - line1Point2.X;
var cLine = aLine*line1Point1.X + bLine*line1Point1.Y;
var aOtherLine = line2Point2.Y - line2Point1.Y;
var bOtherLine = line2Point1.X - line2Point2.X;
var cOtherLine = aOtherLine*line2Point1.X + bOtherLine*line2Point1.Y;
var determinant = aLine*bOtherLine - aOtherLine*bLine;
if (Math.Abs(determinant) < epsilonForComparisons)
{
return null;
}
var x = (bOtherLine*cLine - bLine*cOtherLine)/determinant;
var y = (aLine*cOtherLine - aOtherLine*cLine)/determinant;
return new Point2D(x, y);
}
///
/// Determines the intersection points between multiple segments.
///
/// A of .
/// Another of which may or may not intersect with the from .
/// A of intersection points.
public static IEnumerable SegmentsIntersectionsWithSegments(IEnumerable segments, IEnumerable segmentsToCompare)
{
return segments.SelectMany(segment => segmentsToCompare, SegmentIntersectionWithSegment).Where(intersection => intersection != null).Distinct().ToList();
}
///
/// Determines the intersection points between two segments.
///
/// A segment.
/// Another segment which may or may not intersect with the from .
/// A intersection point, or null when there is no intersection.
public static Point2D SegmentIntersectionWithSegment(Segment2D segment1, Segment2D segment2)
{
if (AreEqualPoints(segment1.FirstPoint, segment1.SecondPoint))
{
return segment1.FirstPoint;
}
if (AreEqualPoints(segment1.SecondPoint, segment2.FirstPoint))
{
return segment1.SecondPoint;
}
return GetIntersectionPoint(segment1, segment2);
}
///
/// Determines if two points are equal.
///
/// The first point.
/// The second point.
/// True when the points are equal. False otherwise.
public static bool AreEqualPoints(Point2D point1, Point2D point2)
{
return Math.Abs(point1.X - point2.X) < epsilonForComparisons && Math.Abs(point1.Y - point2.Y) < epsilonForComparisons;
}
///
/// Determines the intersection points of a of with a vertical line
/// which is plotted at x=.
///
/// A collection of segments that possibly intersect with the
/// vertical line at x=.
/// The x coordinate of the vertical line.
/// A of with all intersection points of the
/// with the vertical line at x=.
/// Segments which have length=0 or which are vertical, will not return an intersection point.
public static IEnumerable SegmentsIntersectionWithVerticalLine(IEnumerable segments, double verticalLineX)
{
var intersectionPointY = new Collection();
foreach (Segment2D segment in segments.Where(s => s.ContainsX(verticalLineX)))
{
Point2D intersectionPoint = LineIntersectionWithVerticalLine(segment.FirstPoint, segment.SecondPoint, verticalLineX);
if (intersectionPoint != null)
{
intersectionPointY.Add(intersectionPoint);
}
}
return intersectionPointY;
}
///
/// Calculates the length of a line defined as a collection of .
///
/// The points that make up a 2D line.
/// The sum of the distances between consecutive points.
public static double Length(IEnumerable points)
{
double length = 0;
Point2D previousPoint = null;
foreach (Point2D point in points)
{
if (previousPoint != null)
{
length += previousPoint.GetEuclideanDistanceTo(point);
}
previousPoint = point;
}
return length;
}
private static Point2D GetIntersectionPoint(Segment2D segment1, Segment2D segment2)
{
var aLine = (segment1.FirstPoint.Y - segment2.FirstPoint.Y)*(segment2.SecondPoint.X - segment2.FirstPoint.X) - (segment1.FirstPoint.X - segment2.FirstPoint.X)*(segment2.SecondPoint.Y - segment2.FirstPoint.Y);
var bLine = (segment1.SecondPoint.X - segment1.FirstPoint.X)*(segment2.SecondPoint.Y - segment2.FirstPoint.Y) - (segment1.SecondPoint.Y - segment1.FirstPoint.Y)*(segment2.SecondPoint.X - segment2.FirstPoint.X);
if (Math.Abs(bLine) < epsilonForComparisons)
{
return null;
}
var intersectionPoint = aLine/bLine;
var cLine = (segment1.FirstPoint.Y - segment2.FirstPoint.Y)*(segment1.SecondPoint.X - segment1.FirstPoint.X) - (segment1.FirstPoint.X - segment2.FirstPoint.X)*(segment1.SecondPoint.Y - segment1.FirstPoint.Y);
var dLine = cLine/bLine;
if (intersectionPoint >= 0 && intersectionPoint <= 1 && dLine >= 0 && dLine <= 1)
{
return new Point2D
(
segment1.FirstPoint.X + intersectionPoint*(segment1.SecondPoint.X - segment1.FirstPoint.X),
segment1.FirstPoint.Y + intersectionPoint*(segment1.SecondPoint.Y - segment1.FirstPoint.Y)
);
}
return null;
}
private static Point2D[][] SplitLineSegmentsAtLengths(Segment2D[] lineSegments, double[] lengths)
{
var splitResults = new Point2D[lengths.Length][];
int index = 0;
double lineSegmentRemainder = lineSegments[index].Length;
double distanceOnSegment = 0;
Point2D startPoint = lineSegments[index].FirstPoint;
for (int i = 0; i < lengths.Length; i++)
{
double splitDistanceRemainder = lengths[i];
var subLine = new List
{
startPoint
};
while (splitDistanceRemainder > lineSegmentRemainder)
{
splitDistanceRemainder -= lineSegmentRemainder;
subLine.Add(lineSegments[index].SecondPoint);
if (index < lineSegments.Length - 1)
{
lineSegmentRemainder = lineSegments[++index].Length;
distanceOnSegment = 0;
}
}
if (i < lengths.Length - 1)
{
Point2D interpolatedPoint = GetInterpolatedPoint(lineSegments[index], distanceOnSegment + splitDistanceRemainder);
subLine.Add(interpolatedPoint);
distanceOnSegment += splitDistanceRemainder;
lineSegmentRemainder -= splitDistanceRemainder;
startPoint = interpolatedPoint;
}
else
{
EnsureLastSplitResultHasEndPointOfLine(subLine, lineSegments);
}
splitResults[i] = subLine.ToArray();
}
return splitResults;
}
private static void EnsureLastSplitResultHasEndPointOfLine(ICollection subLine, IList lineSegments)
{
var lastSegmentIndex = lineSegments.Count - 1;
if (!subLine.Contains(lineSegments[lastSegmentIndex].SecondPoint))
{
subLine.Add(lineSegments[lastSegmentIndex].SecondPoint);
}
}
private static Point2D GetInterpolatedPoint(Segment2D lineSegment, double splitDistance)
{
var interpolationFactor = splitDistance/lineSegment.Length;
Vector segmentVector = lineSegment.SecondPoint - lineSegment.FirstPoint;
double interpolatedX = lineSegment.FirstPoint.X + interpolationFactor*segmentVector[0];
double interpolatedY = lineSegment.FirstPoint.Y + interpolationFactor*segmentVector[1];
return new Point2D(interpolatedX, interpolatedY);
}
///
/// Determines the intersection point of a line through the points and
/// with a vertical line at x=. If
/// equals , then no intersection point
/// will be returned.
///
/// A which the line passes through.
/// Another which the line passes through.
/// The x coordinate of the vertical line.
/// The intersection point between the line through and
/// and the vertical line at x=; or null if
/// the line through and is vertical or
/// the points are equal.
private static Point2D LineIntersectionWithVerticalLine(Point2D point1, Point2D point2, double x)
{
var verticalLineFirstPoint = new Point2D(x, 0);
var verticalLineSecondPoint = new Point2D(x, 1);
try
{
return LineIntersectionWithLine(point1, point2, verticalLineFirstPoint, verticalLineSecondPoint);
}
catch (ArgumentException)
{
return null;
}
}
}
}