Index: src/Plugins/Wti/Wti.IO/Builders/SoilLayer2D.cs =================================================================== diff -u -r65260b1c152bf584ac4d6bdb68f6e4d115c513a0 -r947dc8ecd6e0b674f39bf1a22aef489ec8cc21fa --- src/Plugins/Wti/Wti.IO/Builders/SoilLayer2D.cs (.../SoilLayer2D.cs) (revision 65260b1c152bf584ac4d6bdb68f6e4d115c513a0) +++ src/Plugins/Wti/Wti.IO/Builders/SoilLayer2D.cs (.../SoilLayer2D.cs) (revision 947dc8ecd6e0b674f39bf1a22aef489ec8cc21fa) @@ -1,27 +1,57 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Linq; using Wti.Data; +using Wti.IO.Calculation; namespace Wti.IO.Builders { public class SoilLayer2D { + public SoilLayer2D() + { + InnerLoops = new Collection>(); + } + public HashSet OuterLoop { get; set; } - public HashSet InnerLoop { get; set; } + public Collection> InnerLoops { get; private set; } internal IEnumerable AsPipingSoilLayers(double atX, out double bottom) { bottom = Double.MaxValue; var result = new Collection(); if (OuterLoop != null) { - for (int i = 0; i <= OuterLoop.Count; i++) + Collection intersectionPointY = new Collection(); + for (int i = 0; i < OuterLoop.Count; i++) { - var current = i; - var next = (i+i) %OuterLoop.Count; + var current = OuterLoop.ElementAt(i); + var next = OuterLoop.ElementAt((i + 1)%OuterLoop.Count); + var intersectionPoint = Math2D.LineSegmentIntersectionWithLine(new[] + { + current.X, + next.X, + atX, + atX + }, new[] + { + current.Z, + next.Z, + 0, + 1 + }); + if (intersectionPoint.Length > 0) + { + intersectionPointY.Add(intersectionPoint[1]); + } } + if (intersectionPointY.Count > 0) + { + result.Add(new PipingSoilLayer(intersectionPointY.Max())); + bottom = intersectionPointY.Min(); + } } return result; } Index: src/Plugins/Wti/Wti.IO/Builders/SoilProfileBuilder.cs =================================================================== diff -u -r65260b1c152bf584ac4d6bdb68f6e4d115c513a0 -r947dc8ecd6e0b674f39bf1a22aef489ec8cc21fa --- src/Plugins/Wti/Wti.IO/Builders/SoilProfileBuilder.cs (.../SoilProfileBuilder.cs) (revision 65260b1c152bf584ac4d6bdb68f6e4d115c513a0) +++ src/Plugins/Wti/Wti.IO/Builders/SoilProfileBuilder.cs (.../SoilProfileBuilder.cs) (revision 947dc8ecd6e0b674f39bf1a22aef489ec8cc21fa) @@ -27,20 +27,20 @@ public void Add(SoilLayer2D soilLayer) { double bottom; - foreach(PipingSoilLayer layer in soilLayer.AsPipingSoilLayers(AtX, out bottom)) + foreach (PipingSoilLayer layer in soilLayer.AsPipingSoilLayers(AtX, out bottom)) { layers.Add(layer); } Bottom = Math.Min(Bottom, bottom); } - private double Bottom { get; set; } - public PipingSoilProfile Build() { - return new PipingSoilProfile(ProfileName, 0.0, layers); + return new PipingSoilProfile(ProfileName, Bottom, layers); } + private double Bottom { get; set; } + private double AtX { get; set; } private string ProfileName { get; set; } Index: src/Plugins/Wti/Wti.IO/Calculation/Math2D.cs =================================================================== diff -u -r65260b1c152bf584ac4d6bdb68f6e4d115c513a0 -r947dc8ecd6e0b674f39bf1a22aef489ec8cc21fa --- src/Plugins/Wti/Wti.IO/Calculation/Math2D.cs (.../Math2D.cs) (revision 65260b1c152bf584ac4d6bdb68f6e4d115c513a0) +++ src/Plugins/Wti/Wti.IO/Calculation/Math2D.cs (.../Math2D.cs) (revision 947dc8ecd6e0b674f39bf1a22aef489ec8cc21fa) @@ -9,45 +9,63 @@ /// /// Determines whether two line segments intersect with each other. If the lines are parallel, no intersection point is returned. /// - /// X coordinates of the first segment. Should have matching y coordinates in . - /// Y coordinates of the first segment. Should have matching x coordinates in . - /// X coordinates of the second segment. Should have matching y coordinates in . - /// Y coordinates of the second segment. Should have matching x coordinates in . + /// X coordinates of the segments. Should have matching y coordinates in . + /// Y coordinates of the segments. Should have matching x coordinates in . /// - public static double[] LineSegmentIntersectionWithLineSegment(double[] segmentX, double[] segmentY, double[] otherX, double[] otherY) + public static double[] LineSegmentIntersectionWithLineSegment(double[] segmentsX, double[] segmentsY) { - var numberOfPoints = 2; - if (segmentX.Length != numberOfPoints || segmentY.Length != numberOfPoints || otherX.Length != numberOfPoints || otherY.Length != numberOfPoints) + var numberOfPoints = 4; + if (segmentsX.Length != numberOfPoints || segmentsY.Length != numberOfPoints) { - throw new ArgumentException("Collections of segments' x and y coordinates need to have length of 2"); + throw new ArgumentException(String.Format("Collections of segments' x and y coordinates need to have length of {0}.", numberOfPoints)); } - var extraPolatedIntersectionPoint = LineIntersectionWithLine(segmentX, segmentY, otherX, otherY); + var extraPolatedIntersectionPoint = LineSegmentIntersectionWithLine(segmentsX, segmentsY); if (extraPolatedIntersectionPoint.Length == 2) { - var onFirstSegment = IsBetween(new[] + var onSecondSegment = IsBetween(new[] { - segmentX[0], - segmentX[1], + segmentsX[2], + segmentsX[3], extraPolatedIntersectionPoint[0] }, new[] { - segmentY[0], - segmentY[1], + segmentsY[2], + segmentsY[3], extraPolatedIntersectionPoint[1] }); - var onSecondSegment = IsBetween(new[] + if (onSecondSegment) { - otherX[0], - otherX[1], + return extraPolatedIntersectionPoint; + } + } + return new double[0]; + } + + /// + /// Determines whether a line segment intersects with a line. If the segment and the line are parallel, no intersection point is returned. + /// + /// X coordinates of the segment and line. Should have matching y coordinates in . + /// Y coordinates of the segment and line. Should have matching x coordinates in . + /// + public static double[] LineSegmentIntersectionWithLine(double[] segmentsX, double[] segmentsY) + { + var extraPolatedIntersectionPoint = LineIntersectionWithLine(segmentsX, segmentsY); + + if (extraPolatedIntersectionPoint.Length == 2) + { + var onFirstSegment = IsBetween(new[] + { + segmentsX[0], + segmentsX[1], extraPolatedIntersectionPoint[0] }, new[] { - otherY[0], - otherY[1], + segmentsY[0], + segmentsY[1], extraPolatedIntersectionPoint[1] }); - if (onFirstSegment && onSecondSegment) + if (onFirstSegment) { return extraPolatedIntersectionPoint; } @@ -56,21 +74,21 @@ } /// Taken from: https://www.topcoder.com/community/data-science/data-science-tutorials/geometry-concepts-line-intersection-and-its-applications/ . - private static double[] LineIntersectionWithLine(double[] lineX, double[] lineY, double[] otherLineX, double[] otherLineY) + private static double[] LineIntersectionWithLine(double[] linesX, double[] linesY) { - var numberOfPoints = 2; - if (lineX.Length != numberOfPoints || lineY.Length != numberOfPoints || otherLineX.Length != numberOfPoints || otherLineY.Length != numberOfPoints) + var numberOfPoints = 4; + if (linesX.Length != numberOfPoints || linesY.Length != numberOfPoints) { - throw new ArgumentException("Collections of lines' x and y coordinates need to have length of 2"); + throw new ArgumentException(String.Format("Collections of lines' x and y coordinates need to have length of {0}", numberOfPoints)); } - var aLine = lineY[1] - lineY[0]; - var bLine = lineX[0] - lineX[1]; - var cLine = aLine*lineX[0] + bLine*lineY[0]; + var aLine = linesY[1] - linesY[0]; + var bLine = linesX[0] - linesX[1]; + var cLine = aLine * linesX[0] + bLine * linesY[0]; - var aOtherLine = otherLineY[1] - otherLineY[0]; - var bOtherLine = otherLineX[0] - otherLineX[1]; - var cOtherLine = aOtherLine * otherLineX[0] + bOtherLine * otherLineY[0]; + var aOtherLine = linesY[3] - linesY[2]; + var bOtherLine = linesX[2] - linesX[3]; + var cOtherLine = aOtherLine * linesX[2] + bOtherLine * linesY[2]; var determinant = aLine*bOtherLine - aOtherLine*bLine; if (Math.Abs(determinant) < epsilonForComparisons) Index: src/Plugins/Wti/Wti.IO/PipingSoilLayer2DReader.cs =================================================================== diff -u -r65260b1c152bf584ac4d6bdb68f6e4d115c513a0 -r947dc8ecd6e0b674f39bf1a22aef489ec8cc21fa --- src/Plugins/Wti/Wti.IO/PipingSoilLayer2DReader.cs (.../PipingSoilLayer2DReader.cs) (revision 65260b1c152bf584ac4d6bdb68f6e4d115c513a0) +++ src/Plugins/Wti/Wti.IO/PipingSoilLayer2DReader.cs (.../PipingSoilLayer2DReader.cs) (revision 947dc8ecd6e0b674f39bf1a22aef489ec8cc21fa) @@ -56,7 +56,7 @@ } if (TryParseLoop(innerLoopElementName, out innerLoop)) { - pipingSoilLayer.InnerLoop = innerLoop; + pipingSoilLayer.InnerLoops.Add(innerLoop); } } Index: src/Plugins/Wti/Wti.IO/PipingSoilProfileReader.cs =================================================================== diff -u -r65260b1c152bf584ac4d6bdb68f6e4d115c513a0 -r947dc8ecd6e0b674f39bf1a22aef489ec8cc21fa --- src/Plugins/Wti/Wti.IO/PipingSoilProfileReader.cs (.../PipingSoilProfileReader.cs) (revision 65260b1c152bf584ac4d6bdb68f6e4d115c513a0) +++ src/Plugins/Wti/Wti.IO/PipingSoilProfileReader.cs (.../PipingSoilProfileReader.cs) (revision 947dc8ecd6e0b674f39bf1a22aef489ec8cc21fa) @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Data; using System.Data.SQLite; using System.IO; Index: test/Plugins/Wti/Wti.Data.Test/PipingSoilProfileTest.cs =================================================================== diff -u -r46c2fd882297a1243b82d8cacfbb4fc1881f2157 -r947dc8ecd6e0b674f39bf1a22aef489ec8cc21fa --- test/Plugins/Wti/Wti.Data.Test/PipingSoilProfileTest.cs (.../PipingSoilProfileTest.cs) (revision 46c2fd882297a1243b82d8cacfbb4fc1881f2157) +++ test/Plugins/Wti/Wti.Data.Test/PipingSoilProfileTest.cs (.../PipingSoilProfileTest.cs) (revision 947dc8ecd6e0b674f39bf1a22aef489ec8cc21fa) @@ -29,6 +29,26 @@ Assert.AreEqual(name, profile.Name); Assert.AreEqual(bottom, profile.Bottom); } + + [Test] + public void Constructor_WithNameBottomLayersEmpty_ThrowsArgumentException() + { + // Call + TestDelegate test = () => new PipingSoilProfile(String.Empty, Double.NaN, new Collection()); + + // Assert + Assert.Throws(test); + } + + [Test] + public void Constructor_WithNameBottomLayersNull_ThrowsArgumentException() + { + // Call + TestDelegate test = () => new PipingSoilProfile(String.Empty, Double.NaN, null); + + // Assert + Assert.Throws(test); + } } } \ No newline at end of file Index: test/Plugins/Wti/Wti.IO.Test/Builders/SoilLayer2DTest.cs =================================================================== diff -u -r46c2fd882297a1243b82d8cacfbb4fc1881f2157 -r947dc8ecd6e0b674f39bf1a22aef489ec8cc21fa --- test/Plugins/Wti/Wti.IO.Test/Builders/SoilLayer2DTest.cs (.../SoilLayer2DTest.cs) (revision 46c2fd882297a1243b82d8cacfbb4fc1881f2157) +++ test/Plugins/Wti/Wti.IO.Test/Builders/SoilLayer2DTest.cs (.../SoilLayer2DTest.cs) (revision 947dc8ecd6e0b674f39bf1a22aef489ec8cc21fa) @@ -10,14 +10,14 @@ public class SoilLayer2DTest { [Test] - public void DefaultConstructor_Always_NotInstantiatedOuterAndInnerLoops() + public void DefaultConstructor_Always_NotInstantiatedOuterLoopAndEmptyInnerLoops() { // Call var result = new SoilLayer2D(); // Assert Assert.IsNull(result.OuterLoop); - Assert.IsNull(result.InnerLoop); + CollectionAssert.IsEmpty(result.InnerLoops); } [Test] @@ -58,5 +58,29 @@ CollectionAssert.IsEmpty(result); Assert.AreEqual(double.MaxValue, bottom); } + + [Test] + public void AsPipingSoilLayers_WithOuterLoopIntersectingX_ReturnsBottomAndLayerWithTop() + { + // Setup + var layer = new SoilLayer2D + { + OuterLoop = new HashSet + { + new Point3D + { + X = 0.1, Z = new Random(22).NextDouble() + } + } + }; + double bottom; + + // Call + var result = layer.AsPipingSoilLayers(0.0, out bottom); + + // Assert + CollectionAssert.IsEmpty(result); + Assert.AreEqual(double.MaxValue, bottom); + } } } \ No newline at end of file Index: test/Plugins/Wti/Wti.IO.Test/Builders/SoilProfileBuilderTest.cs =================================================================== diff -u -r46c2fd882297a1243b82d8cacfbb4fc1881f2157 -r947dc8ecd6e0b674f39bf1a22aef489ec8cc21fa --- test/Plugins/Wti/Wti.IO.Test/Builders/SoilProfileBuilderTest.cs (.../SoilProfileBuilderTest.cs) (revision 46c2fd882297a1243b82d8cacfbb4fc1881f2157) +++ test/Plugins/Wti/Wti.IO.Test/Builders/SoilProfileBuilderTest.cs (.../SoilProfileBuilderTest.cs) (revision 947dc8ecd6e0b674f39bf1a22aef489ec8cc21fa) @@ -48,5 +48,44 @@ // Assert Assert.Throws(test); } + + [Test] + public void Build_WithSingleLayerOnlyOuterLoop_ReturnsProfileWithBottomAndALayer() + { + // Setup + var profileName = "SomeProfile"; + var builder = new SoilProfileBuilder(profileName, 0.0); + builder.Add(new SoilLayer2D + { + OuterLoop = new HashSet + { + new Point3D + { + X = -0.5, Z = 1.0 + }, + new Point3D + { + X = 0.5, Z = 1.0 + }, + new Point3D + { + X = 0.5, Z = -1.0 + }, + new Point3D + { + X = -0.5, Z = -1.0 + } + } + }); + + // Call + PipingSoilProfile soilProfile = builder.Build(); + + // Assert + Assert.AreEqual(profileName, soilProfile.Name); + Assert.AreEqual(1, soilProfile.Layers.Count()); + Assert.AreEqual(1.0, soilProfile.Layers.ToArray()[0].Top); + Assert.AreEqual(-1.0, soilProfile.Bottom); + } } } \ No newline at end of file Index: test/Plugins/Wti/Wti.IO.Test/Calculation/Math2DTest.cs =================================================================== diff -u -r65260b1c152bf584ac4d6bdb68f6e4d115c513a0 -r947dc8ecd6e0b674f39bf1a22aef489ec8cc21fa --- test/Plugins/Wti/Wti.IO.Test/Calculation/Math2DTest.cs (.../Math2DTest.cs) (revision 65260b1c152bf584ac4d6bdb68f6e4d115c513a0) +++ test/Plugins/Wti/Wti.IO.Test/Calculation/Math2DTest.cs (.../Math2DTest.cs) (revision 947dc8ecd6e0b674f39bf1a22aef489ec8cc21fa) @@ -122,7 +122,7 @@ var segments = ToSegmentCoordinatesCollections(coordinates); // Call - var result = Math2D.LineSegmentIntersectionWithLineSegment(segments[0], segments[1], segments[2], segments[3]); + var result = Math2D.LineSegmentIntersectionWithLineSegment(segments[0], segments[1]); // Assert CollectionAssert.AreEqual(new[] @@ -140,7 +140,7 @@ var segments = ToSegmentCoordinatesCollections(coordinates); // Call - var result = Math2D.LineSegmentIntersectionWithLineSegment(segments[0], segments[1], segments[2], segments[3]); + var result = Math2D.LineSegmentIntersectionWithLineSegment(segments[0], segments[1]); // Assert Assert.AreEqual(0, result.Length); @@ -154,7 +154,7 @@ var segments = ToSegmentCoordinatesCollections(coordinates); // Call - var result = Math2D.LineSegmentIntersectionWithLineSegment(segments[0], segments[1], segments[2], segments[3]); + var result = Math2D.LineSegmentIntersectionWithLineSegment(segments[0], segments[1]); // Assert Assert.AreEqual(0, result.Length); @@ -165,29 +165,21 @@ double[] segmentX = { coordinates[0], - coordinates[2] - }; - double[] segmentY = - { - coordinates[1], - coordinates[3] - }; - double[] otherSegmentX = - { + coordinates[2], coordinates[4], coordinates[6] }; - double[] otherSegmentY = + double[] segmentY = { + coordinates[1], + coordinates[3], coordinates[5], coordinates[7] }; return new[] { segmentX, - segmentY, - otherSegmentX, - otherSegmentY + segmentY }; } } Index: test/Plugins/Wti/Wti.IO.Test/PipingSoilLayerReaderTest.cs =================================================================== diff -u -r65260b1c152bf584ac4d6bdb68f6e4d115c513a0 -r947dc8ecd6e0b674f39bf1a22aef489ec8cc21fa --- test/Plugins/Wti/Wti.IO.Test/PipingSoilLayerReaderTest.cs (.../PipingSoilLayerReaderTest.cs) (revision 65260b1c152bf584ac4d6bdb68f6e4d115c513a0) +++ test/Plugins/Wti/Wti.IO.Test/PipingSoilLayerReaderTest.cs (.../PipingSoilLayerReaderTest.cs) (revision 947dc8ecd6e0b674f39bf1a22aef489ec8cc21fa) @@ -34,7 +34,7 @@ } [Test] - public void Read_XmlDocumentWithoutSaneContent_ReturnsLayerWithoutLoops() + public void Read_XmlDocumentWithoutSaneContent_ReturnsLayerWithoutOuterLoopAndEmptyInnerLoops() { // Setup var xmlDoc = GetBytes(""); @@ -46,7 +46,7 @@ // Assert Assert.NotNull(result); Assert.IsNull(result.OuterLoop); - Assert.IsNull(result.InnerLoop); + CollectionAssert.IsEmpty(result.InnerLoops); } [Test] @@ -62,11 +62,11 @@ // Assert Assert.NotNull(result); CollectionAssert.IsEmpty(result.OuterLoop); - Assert.IsNull(result.InnerLoop); + CollectionAssert.IsEmpty(result.InnerLoops); } [Test] - public void Read_XmlDocumentWithEmptyInnerLoop_ReturnsLayerWithEmptyInnerLoop() + public void Read_XmlDocumentWithEmptyInnerLoop_ReturnsLayerWithOneEmptyInnerLoop() { // Setup var xmlDoc = GetBytes(""); @@ -78,7 +78,8 @@ // Assert Assert.NotNull(result); Assert.IsNull(result.OuterLoop); - CollectionAssert.IsEmpty(result.InnerLoop); + Assert.AreEqual(1, result.InnerLoops.Count); + CollectionAssert.IsEmpty(result.InnerLoops[0]); } [Test] @@ -94,7 +95,8 @@ // Assert Assert.NotNull(result); CollectionAssert.IsEmpty(result.OuterLoop); - CollectionAssert.IsEmpty(result.InnerLoop); + Assert.AreEqual(1, result.InnerLoops.Count); + CollectionAssert.IsEmpty(result.InnerLoops[0]); } [Test] @@ -137,7 +139,8 @@ // Assert Assert.NotNull(result); - CollectionAssert.AreEqual(new HashSet { new Point3D { X = 0, Y = 0.1, Z = 1.1 } }, result.InnerLoop); + Assert.AreEqual(1, result.InnerLoops.Count); + CollectionAssert.AreEqual(new HashSet { new Point3D { X = 0, Y = 0.1, Z = 1.1 } }, result.InnerLoops[0]); } [Test] @@ -167,7 +170,8 @@ // Assert Assert.NotNull(result); - CollectionAssert.AreEqual(new HashSet { new Point3D { X = 0, Y = 0.1, Z = 1.1 }, new Point3D { X = 1.0, Y = 0.1, Z = 1.1 } }, result.InnerLoop); + Assert.AreEqual(1, result.InnerLoops.Count); + CollectionAssert.AreEqual(new HashSet { new Point3D { X = 0, Y = 0.1, Z = 1.1 }, new Point3D { X = 1.0, Y = 0.1, Z = 1.1 } }, result.InnerLoops[0]); } static byte[] GetBytes(string str)