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