Index: Core/Common/src/Core.Common.Base/Geometry/Math2D.cs =================================================================== diff -u -rdaf4bf00e8ea376485f54faa2fee8497607ddfb0 -rd36795ad93a8aaf9daccd85f143b15562f963fb3 --- Core/Common/src/Core.Common.Base/Geometry/Math2D.cs (.../Math2D.cs) (revision daf4bf00e8ea376485f54faa2fee8497607ddfb0) +++ Core/Common/src/Core.Common.Base/Geometry/Math2D.cs (.../Math2D.cs) (revision d36795ad93a8aaf9daccd85f143b15562f963fb3) @@ -26,6 +26,8 @@ using Core.Common.Base.Properties; +using Vector = MathNet.Numerics.LinearAlgebra.Double.Vector; + namespace Core.Common.Base.Geometry { /// @@ -39,6 +41,114 @@ private const double epsilonForComparisons = 1e-8; /// + /// 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 - + 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)) > 1e-6) + { + throw new ArgumentException(Resources.Math2D_SplitLineAtLengths_Sum_of_lengths_must_equal_line_length, "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 splitDistance = lengths[i]; + var subLine = new List + { + startPoint + }; + + while (splitDistance > lineSegmentRemainder) + { + splitDistance -= 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 + splitDistance); + subLine.Add(interpolatedPoint); + + distanceOnSegment += splitDistance; + lineSegmentRemainder -= splitDistance; + startPoint = interpolatedPoint; + } + else + { + // Working on the last segment + if (!subLine.Contains(lineSegments[index].SecondPoint)) + { + subLine.Add(lineSegments[index].SecondPoint); + } + } + + splitResults[i] = subLine.ToArray(); + } + + return splitResults; + } + + 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); + } + + /// + /// 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 . Index: Core/Common/src/Core.Common.Base/Properties/Resources.Designer.cs =================================================================== diff -u -rdaf4bf00e8ea376485f54faa2fee8497607ddfb0 -rd36795ad93a8aaf9daccd85f143b15562f963fb3 --- Core/Common/src/Core.Common.Base/Properties/Resources.Designer.cs (.../Resources.Designer.cs) (revision daf4bf00e8ea376485f54faa2fee8497607ddfb0) +++ Core/Common/src/Core.Common.Base/Properties/Resources.Designer.cs (.../Resources.Designer.cs) (revision d36795ad93a8aaf9daccd85f143b15562f963fb3) @@ -125,6 +125,33 @@ } /// + /// Looks up a localized string similar to Er mogen geen negatieve lengtes worden opgegeven om de lijn mee op te knippen.. + /// + public static string Math2D_SplitLineAtLengths_All_lengths_cannot_be_negative { + get { + return ResourceManager.GetString("Math2D_SplitLineAtLengths_All_lengths_cannot_be_negative", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Er zijn niet genoeg punten beschikbaar om een lijn te definiëren.. + /// + public static string Math2D_SplitLineAtLengths_Not_enough_points_to_make_line { + get { + return ResourceManager.GetString("Math2D_SplitLineAtLengths_Not_enough_points_to_make_line", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to De som van alle lengtes moet gelijk zijn aan de lengte van de opgegeven lijn.. + /// + public static string Math2D_SplitLineAtLengths_Sum_of_lengths_must_equal_line_length { + get { + return ResourceManager.GetString("Math2D_SplitLineAtLengths_Sum_of_lengths_must_equal_line_length", resourceCulture); + } + } + + /// /// Looks up a localized string similar to Project. /// public static string Project_Constructor_Default_name { Index: Core/Common/src/Core.Common.Base/Properties/Resources.resx =================================================================== diff -u -rdaf4bf00e8ea376485f54faa2fee8497607ddfb0 -rd36795ad93a8aaf9daccd85f143b15562f963fb3 --- Core/Common/src/Core.Common.Base/Properties/Resources.resx (.../Resources.resx) (revision daf4bf00e8ea376485f54faa2fee8497607ddfb0) +++ Core/Common/src/Core.Common.Base/Properties/Resources.resx (.../Resources.resx) (revision d36795ad93a8aaf9daccd85f143b15562f963fb3) @@ -144,4 +144,13 @@ Punten voor een lijn moeten uit elkaar liggen om een lijn te kunnen vormen. + + De som van alle lengtes moet gelijk zijn aan de lengte van de opgegeven lijn. + + + Er mogen geen negatieve lengtes worden opgegeven om de lijn mee op te knippen. + + + Er zijn niet genoeg punten beschikbaar om een lijn te definiëren. + \ No newline at end of file Index: Core/Common/test/Core.Common.Base.Test/Geometry/Math2DTest.cs =================================================================== diff -u -rdaf4bf00e8ea376485f54faa2fee8497607ddfb0 -rd36795ad93a8aaf9daccd85f143b15562f963fb3 --- Core/Common/test/Core.Common.Base.Test/Geometry/Math2DTest.cs (.../Math2DTest.cs) (revision daf4bf00e8ea376485f54faa2fee8497607ddfb0) +++ Core/Common/test/Core.Common.Base.Test/Geometry/Math2DTest.cs (.../Math2DTest.cs) (revision d36795ad93a8aaf9daccd85f143b15562f963fb3) @@ -1,8 +1,10 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Linq; using Core.Common.Base.Geometry; +using Core.Common.TestUtil; using NUnit.Framework; @@ -206,5 +208,213 @@ // Assert Assert.AreEqual(intersectionHeights.Select(y => new Point2D(x, y)), result); } + + [Test] + [TestCase(0)] + [TestCase(1)] + public void ConvertLinePointsToLineSegments_TooFewPoints_ReturnEmpty(int pointCount) + { + // Setup + var linePoints = Enumerable.Repeat(new Point2D(), pointCount); + + // Call + IEnumerable segments = Math2D.ConvertLinePointsToLineSegments(linePoints); + + // Assert + CollectionAssert.IsEmpty(segments); + } + + [Test] + public void ConvertLinePointsToLineSegments_TwoPoints_ReturnOneSegmentOfThoseTwoPoints() + { + // Setup + var linePoints = new[] + { + new Point2D(1.1, 2.2), + new Point2D(3.3, 4.4), + }; + + // Call + Segment2D[] segments = Math2D.ConvertLinePointsToLineSegments(linePoints).ToArray(); + + // Assert + Assert.AreEqual(1, segments.Length); + Assert.AreEqual(linePoints[0], segments[0].FirstPoint); + Assert.AreEqual(linePoints[1], segments[0].SecondPoint); + } + + [Test] + [TestCase(0)] + [TestCase(1)] + public void SplitLineAtLengths_TooFewPoints_ThrowArgumentException(int pointCount) + { + // Setup + var originalLine = Enumerable.Repeat(new Point2D(0.0, 0.0), pointCount); + + var lengths = new[] + { + 0.0 + }; + + // Call + TestDelegate call = () => Math2D.SplitLineAtLengths(originalLine, lengths); + + // Assert + var expectedMessage = "Er zijn niet genoeg punten beschikbaar om een lijn te definiëren."; + TestHelper.AssertThrowsArgumentExceptionAndTestMessage(call, expectedMessage); + } + + [Test] + public void SplitLineAtLengths_NegativeLength_ThrowArgumentException() + { + // Setup + var originalLine = new[] + { + new Point2D(0.0, 0.0), + new Point2D(6.0, 0.0) + }; + + var lengths = new[] + { + 2.0, + 6.0, + -2.0 + }; + + // Call + TestDelegate call = () => Math2D.SplitLineAtLengths(originalLine, lengths); + + // Assert + var expectedMessage = "Er mogen geen negatieve lengtes worden opgegeven om de lijn mee op te knippen."; + TestHelper.AssertThrowsArgumentExceptionAndTestMessage(call, expectedMessage); + } + + [Test] + [TestCase(0.0)] + [TestCase(2.0 - 1.1e-6)] + [TestCase(2.0 + 1.1e-6)] + [TestCase(67.8)] + public void SplitLineAtLengths_LengthsDoNotFullyCoverLine_ThrowArgumentException(double l) + { + // Setup + var originalLine = new[] + { + new Point2D(0.0, 0.0), + new Point2D(6.0, 0.0) + }; + + var lengths = new[] + { + 2.0, + 2.0, + l + }; + + // Call + TestDelegate call = () => Math2D.SplitLineAtLengths(originalLine, lengths); + + // Assert + var expectedMessage = "De som van alle lengtes moet gelijk zijn aan de lengte van de opgegeven lijn."; + TestHelper.AssertThrowsArgumentExceptionAndTestMessage(call, expectedMessage); + } + + [Test] + public void SplitLineAtLengths_OneLengthsForWholeLine_ReturnAllLinePoints() + { + // Setup + var originalLine = new[] + { + new Point2D(0.0, 0.0), + new Point2D(2.0, 0.0), + new Point2D(4.0, 0.0), + new Point2D(6.0, 0.0) + }; + + var lengths = new[] + { + 6.0 + }; + + // Call + IEnumerable[] lineSplits = Math2D.SplitLineAtLengths(originalLine, lengths); + + // Assert + Assert.AreEqual(1, lineSplits.Length); + Assert.AreNotSame(originalLine, lineSplits[0]); + CollectionAssert.AreEqual(originalLine, lineSplits[0]); + } + + [Test] + public void SplitLineAtLengths_LongLineSplitInFourPieces_ReturnFourSplitResults() + { + // Setup + var originalLine = new[] + { + new Point2D(0.0, 0.0), + new Point2D(20.0, 60.0), + }; + + var lengths = GetLengthsBasedOnReletative(new[] + { + 0.25, + 0.25, + 0.15, + 0.35 + }, originalLine); + + // Call + Point2D[][] lineSplits = Math2D.SplitLineAtLengths(originalLine, lengths); + + // Assert + var doubleToleranceComparer = new Point2DComparerWithTolerance(1e-6); + Assert.AreEqual(4, lineSplits.Length); + CollectionAssert.AreEqual(new[] + { + new Point2D(0,0), + new Point2D(5.0, 15.0) + }, lineSplits[0], doubleToleranceComparer); + CollectionAssert.AreEqual(new[] + { + new Point2D(5.0, 15.0), + new Point2D(10.0, 30) + }, lineSplits[1], doubleToleranceComparer); + CollectionAssert.AreEqual(new[] + { + new Point2D(10.0, 30.0), + new Point2D(13.0, 39.0) + }, lineSplits[2], doubleToleranceComparer); + CollectionAssert.AreEqual(new[] + { + new Point2D(13.0, 39.0), + new Point2D(20.0, 60.0) + }, lineSplits[3], doubleToleranceComparer); + } + + private double[] GetLengthsBasedOnReletative(double[] relativeLengths, IEnumerable lineGeometryPoints) + { + var lineLength = Math2D.ConvertLinePointsToLineSegments(lineGeometryPoints).Sum(s => s.Length); + return relativeLengths.Select(l => lineLength * l).ToArray(); + } + + private class Point2DComparerWithTolerance : IComparer, IComparer + { + private readonly double tolerance; + + public Point2DComparerWithTolerance(double tolerance) + { + this.tolerance = tolerance; + } + + public int Compare(Point2D p0, Point2D p1) + { + double diff = p0.GetEuclideanDistanceTo(p1); + return Math.Abs(diff) < tolerance ? 0 : 1; + } + + public int Compare(object x, object y) + { + return Compare(x as Point2D, y as Point2D); + } + } } } \ No newline at end of file Index: Core/Common/test/Core.Common.TestUtil/Core.Common.TestUtil.csproj =================================================================== diff -u -r00c8bc7fed580b4a9820a7d85b3b6d2346599f65 -rd36795ad93a8aaf9daccd85f143b15562f963fb3 --- Core/Common/test/Core.Common.TestUtil/Core.Common.TestUtil.csproj (.../Core.Common.TestUtil.csproj) (revision 00c8bc7fed580b4a9820a7d85b3b6d2346599f65) +++ Core/Common/test/Core.Common.TestUtil/Core.Common.TestUtil.csproj (.../Core.Common.TestUtil.csproj) (revision d36795ad93a8aaf9daccd85f143b15562f963fb3) @@ -84,6 +84,7 @@ + @@ -105,6 +106,10 @@ + + {3bbfd65b-b277-4e50-ae6d-bd24c3434609} + Core.Common.Base + {f49bd8b2-332a-4c91-a196-8cce0a2c7d98} Core.Common.Utils Index: Core/Common/test/Core.Common.TestUtil/DoubleWithToleranceComparer.cs =================================================================== diff -u --- Core/Common/test/Core.Common.TestUtil/DoubleWithToleranceComparer.cs (revision 0) +++ Core/Common/test/Core.Common.TestUtil/DoubleWithToleranceComparer.cs (revision d36795ad93a8aaf9daccd85f143b15562f963fb3) @@ -0,0 +1,42 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +using Core.Common.Base.Geometry; + +namespace Core.Common.TestUtil +{ + /// + /// This class can be used to compare doubles with a given tolerance, which can be useful to overcome double precision + /// errors. + /// + public class DoubleWithToleranceComparer : IComparer, IComparer + { + private readonly double tolerance; + + public DoubleWithToleranceComparer(double tolerance) + { + this.tolerance = tolerance; + } + + public int Compare(double firstDouble, double secondDouble) + { + var diff = firstDouble - secondDouble; + + var tolerable = Math.Abs(diff) < tolerance; + + var nonTolerableDiff = !tolerable && diff < 0 ? -1 : 1; + + return tolerable ? 0 : nonTolerableDiff; + } + + public int Compare(object x, object y) + { + if (!(x is double) || !(y is double)) + { + throw new NotSupportedException(string.Format("Cannot compare objects other than {0} with this comparer.", typeof(Double))); + } + return Compare((double)x, (double)y); + } + } +} \ No newline at end of file Index: Ringtoets/Piping/test/Ringtoets.Piping.IO.Test/Ringtoets.Piping.IO.Test.csproj =================================================================== diff -u -r06f9145d8180df7fd26eac086a3f431c181e4d64 -rd36795ad93a8aaf9daccd85f143b15562f963fb3 --- Ringtoets/Piping/test/Ringtoets.Piping.IO.Test/Ringtoets.Piping.IO.Test.csproj (.../Ringtoets.Piping.IO.Test.csproj) (revision 06f9145d8180df7fd26eac086a3f431c181e4d64) +++ Ringtoets/Piping/test/Ringtoets.Piping.IO.Test/Ringtoets.Piping.IO.Test.csproj (.../Ringtoets.Piping.IO.Test.csproj) (revision d36795ad93a8aaf9daccd85f143b15562f963fb3) @@ -75,7 +75,6 @@ - Fisheye: Tag d36795ad93a8aaf9daccd85f143b15562f963fb3 refers to a dead (removed) revision in file `Ringtoets/Piping/test/Ringtoets.Piping.IO.Test/TestHelpers/DoubleWithToleranceComparer.cs'. Fisheye: No comparison available. Pass `N' to diff?