Index: Core/Common/src/Core.Common.Geometry/AdvancedMath2D.cs =================================================================== diff -u -r2016e52c40bafa21e1c6d49c9cdc54cc31c6f3a0 -red0d7390c14c7d453b3b21800a636de5c16e3ab5 --- Core/Common/src/Core.Common.Geometry/AdvancedMath2D.cs (.../AdvancedMath2D.cs) (revision 2016e52c40bafa21e1c6d49c9cdc54cc31c6f3a0) +++ Core/Common/src/Core.Common.Geometry/AdvancedMath2D.cs (.../AdvancedMath2D.cs) (revision ed0d7390c14c7d453b3b21800a636de5c16e3ab5) @@ -149,6 +149,39 @@ return new Point2D(interiorPoint.X, interiorPoint.Y); } + /// + /// Gets an indicator whether the given lies in the polygon. + /// + /// The point to check. + /// The outer ring of the polygon. + /// The inner rings of the polygon. + /// true when the lies in the polygon; false otherwise. + /// Thrown when any parameter is null. + public static bool PointInPolygon(Point2D point, IEnumerable outerRing, IEnumerable> innerRings) + { + if (point == null) + { + throw new ArgumentNullException(nameof(point)); + } + + if (outerRing == null) + { + throw new ArgumentNullException(nameof(outerRing)); + } + + if (innerRings == null) + { + throw new ArgumentNullException(nameof(innerRings)); + } + + Polygon outerPolygon = PointsToPolygon(outerRing); + IEnumerable innerPolygons = innerRings.Select(PointsToPolygon).ToArray(); + + var polygon = new Polygon(outerPolygon.Shell, innerPolygons.Select(p => p.Shell).ToArray()); + + return polygon.Covers(new Point(point.X, point.Y)); + } + private static IEnumerable GetPointsFromLine(IEnumerable line, double completingPointsLevel) { foreach (Point2D point in line) Index: Core/Common/test/Core.Common.Geometry.Test/AdvancedMath2DTest.cs =================================================================== diff -u -rb9ceec3c71d972d3d467c1520033ac48cf77b2e9 -red0d7390c14c7d453b3b21800a636de5c16e3ab5 --- Core/Common/test/Core.Common.Geometry.Test/AdvancedMath2DTest.cs (.../AdvancedMath2DTest.cs) (revision b9ceec3c71d972d3d467c1520033ac48cf77b2e9) +++ Core/Common/test/Core.Common.Geometry.Test/AdvancedMath2DTest.cs (.../AdvancedMath2DTest.cs) (revision ed0d7390c14c7d453b3b21800a636de5c16e3ab5) @@ -68,10 +68,10 @@ }; // Call - TestDelegate test = () => AdvancedMath2D.PolygonIntersectionWithPolygon(polyB, polyA); + void Call() => AdvancedMath2D.PolygonIntersectionWithPolygon(polyB, polyA); // Assert - Assert.Throws(test); + Assert.Throws(Call); } [Test] @@ -259,26 +259,24 @@ public void FromXToXY_WithoutPoints_ThrowsArgumentNullException() { // Call - TestDelegate test = () => AdvancedMath2D.FromXToXY(null, new Point2D(0, 0), 3, 2); + void Call() => AdvancedMath2D.FromXToXY(null, new Point2D(0, 0), 3, 2); // Assert - string paramName = TestHelper.AssertThrowsArgumentExceptionAndTestMessage( - test, - "Cannot transform to coordinates without a source.").ParamName; - Assert.AreEqual("xCoordinates", paramName); + var exception = TestHelper.AssertThrowsArgumentExceptionAndTestMessage( + Call, "Cannot transform to coordinates without a source."); + Assert.AreEqual("xCoordinates", exception.ParamName); } [Test] public void FromXToXY_WithoutReferencePoint_ThrowsArgumentNullException() { // Call - TestDelegate test = () => AdvancedMath2D.FromXToXY(new double[0], null, 3, 2); + void Call() => AdvancedMath2D.FromXToXY(new double[0], null, 3, 2); // Assert - string paramName = TestHelper.AssertThrowsArgumentExceptionAndTestMessage( - test, - "Cannot transform to coordinates without a reference point.").ParamName; - Assert.AreEqual("referencePoint", paramName); + var exception = TestHelper.AssertThrowsArgumentExceptionAndTestMessage( + Call, "Cannot transform to coordinates without a reference point."); + Assert.AreEqual("referencePoint", exception.ParamName); } [Test] @@ -380,10 +378,10 @@ public void CompleteLineToPolygon_WithoutLine_ThrowsArgumentNullException() { // Call - TestDelegate test = () => AdvancedMath2D.CompleteLineToPolygon(null, double.NaN).ToArray(); + void Call() => AdvancedMath2D.CompleteLineToPolygon(null, double.NaN).ToArray(); // Assert - var exception = Assert.Throws(test); + var exception = Assert.Throws(Call); Assert.AreEqual("line", exception.ParamName); } @@ -394,11 +392,11 @@ IEnumerable points = Enumerable.Repeat(new Point2D(3, 2), pointCount); // Call - TestDelegate test = () => AdvancedMath2D.CompleteLineToPolygon(points, double.NaN).ToArray(); + void Call() => AdvancedMath2D.CompleteLineToPolygon(points, double.NaN).ToArray(); // Assert const string message = "The line needs to have at least two points to be able to create a complete polygon."; - var exception = TestHelper.AssertThrowsArgumentExceptionAndTestMessage(test, message); + var exception = TestHelper.AssertThrowsArgumentExceptionAndTestMessage(Call, message); Assert.AreEqual("line", exception.ParamName); } @@ -468,12 +466,7 @@ public void GetPolygonInteriorPoint_TrianglePolygon_ReturnsInteriorPoint() { // Setup - var outerRing = new[] - { - new Point2D(0, 0), - new Point2D(3, 4), - new Point2D(6, 0) - }; + Point2D[] outerRing = CreateTrianglePolygon(); // Call Point2D interiorPoint = AdvancedMath2D.GetPolygonInteriorPoint(outerRing, new IEnumerable[0]); @@ -486,43 +479,100 @@ public void GetPolygonInteriorPoint_PolygonWithHoles_ReturnsInteriorPoint() { // Setup - var outerRing = new[] - { - new Point2D(0, 0), - new Point2D(0, 4), - new Point2D(2, 6), - new Point2D(4, 4), - new Point2D(4, 0), - new Point2D(2, -2) - }; + Point2D[] outerRing = CreateCustomPolygon(); + Point2D[][] innerRings = CreateInnerRings(); + + // Call + Point2D interiorPoint = AdvancedMath2D.GetPolygonInteriorPoint(outerRing, innerRings); - var innerRing1 = new[] - { - new Point2D(1, 3), - new Point2D(2, 4), - new Point2D(3, 3), - new Point2D(2, 2) - }; + // Assert + Assert.AreEqual(new Point2D(0.75, 2.5), interiorPoint); + } - var innerRing2 = new[] - { - new Point2D(1, 1), - new Point2D(2, 2), - new Point2D(3, 1), - new Point2D(2, 0) - }; + [Test] + public void PointInPolygon_PointNull_ThrowsArgumentNullException() + { + // Call + void Call() => AdvancedMath2D.PointInPolygon(null, Enumerable.Empty(), Enumerable.Empty>()); + // Assert + var exception = Assert.Throws(Call); + Assert.AreEqual("point", exception.ParamName); + } + + [Test] + public void PointInPolygon_OuterRingNull_ThrowsArgumentNullException() + { // Call - Point2D interiorPoint = AdvancedMath2D.GetPolygonInteriorPoint(outerRing, new[] - { - innerRing1, - innerRing2 - }); + void Call() => AdvancedMath2D.PointInPolygon(new Point2D(0, 0), null, Enumerable.Empty>()); // Assert - Assert.AreEqual(new Point2D(0.75, 2.5), interiorPoint); + var exception = Assert.Throws(Call); + Assert.AreEqual("outerRing", exception.ParamName); } + [Test] + public void PointInPolygon_InnerRingsNull_ThrowsArgumentNullException() + { + // Call + void Call() => AdvancedMath2D.PointInPolygon(new Point2D(0, 0), Enumerable.Empty(), null); + + // Assert + var exception = Assert.Throws(Call); + Assert.AreEqual("innerRings", exception.ParamName); + } + + [Test] + [TestCaseSource(nameof(GetPolygons))] + public void PointInPolygon_PointInPolygon_ReturnsTrue(IEnumerable outerRing, IEnumerable> innerRings) + { + // Setup + var point = new Point2D(1, 1); + + // Call + bool pointInPolygon = AdvancedMath2D.PointInPolygon(point, outerRing, innerRings); + + // Assert + Assert.IsTrue(pointInPolygon); + } + + [Test] + [TestCaseSource(nameof(GetPolygons))] + public void PointInPolygon_PointOutsidePolygon_ReturnsFalse(IEnumerable outerRing, IEnumerable> innerRings) + { + // Setup + var point = new Point2D(-1, -1); + + // Call + bool pointInPolygon = AdvancedMath2D.PointInPolygon(point, outerRing, innerRings); + + // Assert + Assert.IsFalse(pointInPolygon); + } + + [Test] + public void PointInPolygon_PointInHole_ReturnsFalse() + { + // Setup + Point2D[] outerRing = CreateCustomPolygon(); + Point2D[][] innerRings = CreateInnerRings(); + + var point = new Point2D(2, 3); + + // Call + bool pointInPolygon = AdvancedMath2D.PointInPolygon(point, outerRing, innerRings); + + // Assert + Assert.IsFalse(pointInPolygon); + } + + private static IEnumerable GetPolygons() + { + yield return new TestCaseData(CreateBasePolygon(), Enumerable.Empty>()); + yield return new TestCaseData(CreateTrianglePolygon(), Enumerable.Empty>()); + yield return new TestCaseData(CreateCustomPolygon(), CreateInnerRings()); + } + private static double[] ThreeRandomXCoordinates() { var random = new Random(21); @@ -559,5 +609,49 @@ new Point2D(4, 0) }; } + + private static Point2D[] CreateTrianglePolygon() + { + return new[] + { + new Point2D(0, 0), + new Point2D(3, 4), + new Point2D(6, 0) + }; + } + + private static Point2D[] CreateCustomPolygon() + { + return new[] + { + new Point2D(0, 0), + new Point2D(0, 4), + new Point2D(2, 6), + new Point2D(4, 4), + new Point2D(4, 0), + new Point2D(2, -2) + }; + } + + private static Point2D[][] CreateInnerRings() + { + return new[] + { + new[] + { + new Point2D(1, 3), + new Point2D(2, 4), + new Point2D(3, 3), + new Point2D(2, 2) + }, + new[] + { + new Point2D(1, 1), + new Point2D(2, 2), + new Point2D(3, 1), + new Point2D(2, 0) + } + }; + } } } \ No newline at end of file