Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Features/polygon intersection #200

Open
wants to merge 11 commits into
base: develop
Choose a base branch
from
Prev Previous commit
Next Next commit
Fixed polygon intersection + tests
pietervdvn committed Jun 21, 2018
commit 9e24268cc5c94b71c13b604b80a140b4f4495277
104 changes: 96 additions & 8 deletions src/Itinero/LocalGeo/Extensions.cs
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@
using System.Collections.Generic;
using System.Linq;
using Itinero.LocalGeo.Operations;
using Itinero.Profiles.Lua.Tree.Statements;

namespace Itinero.LocalGeo
{
@@ -327,15 +328,15 @@ public static IEnumerable<Coordinate> Intersect(this Polygon polygon, float lati
/// </summary>
public static bool PointIn(this Polygon poly, Coordinate point)
{
return PointInPolygon.PointIn(poly, point);
return PointInPolygon.ContainsPoint(poly, point);
}

/// <summary>
/// Returns true if the given point lies within the ring.
/// </summary>
public static bool PointIn(List<Coordinate> ring, Coordinate point)
{
return PointInPolygon.PointIn(ring, point);
return PointInPolygon.ContainsPoint(ring, point);
}

/// <summary>
@@ -363,7 +364,7 @@ public static float AngleSum(this Polygon p)
}

/// <summary>
/// Calculates the sum of the angles. Is positive if the polygon points are saved clockwise
/// Calculates the sum of the angles. Always returns a positive result. Use with caution, it doesn't always work as intended
/// Only considers the outer hull!
/// The polygon should be closed
/// </summary>
@@ -381,20 +382,22 @@ public static float AngleSum(this List<Coordinate> ring)
var nxt = ring[(i + 1) % n]; // ignore that last element, the closing of the loop
var prod1 = (prev - cur) / prev.DistanceInDegrees(cur);
var prod2 = (cur - nxt) / cur.DistanceInDegrees(nxt);
sum += (float) Math.Acos(Coordinate.DotProduct(prod1,prod2));
var dotProd = Coordinate.DotProduct(prod1, prod2);
var sign = Math.Sign(Math.Asin(dotProd));
sum += (float) Math.Acos(dotProd) * sign;
}

return sum;
}

public static bool IsClockwise(this Polygon p)
{
return p.AngleSum() > 0;
return p.ExteriorRing.IsClockwise();
}

public static bool IsClockwise(this List<Coordinate> p)
{
return p.AngleSum() > 0;
{
return p.SignedSurfaceArea() > 0;
}


@@ -425,7 +428,92 @@ public static List<Polygon> IntersectionsWith(this Polygon a, Polygon b)
{
b.ExteriorRing.Reverse();
}
return a.Intersect(b);

return a.Intersect(b, out var union);
}

public static Polygon UnionWith(this Polygon a, Polygon b)
{
if (!a.IsClockwise())
{
a.ExteriorRing.Reverse();
}

if (!b.IsClockwise())
{
b.ExteriorRing.Reverse();
}

a.Intersect(b, out var union);
return union;
}

public static List<Polygon> DifferencesWith(this Polygon a, Polygon b)
{
if (!a.IsClockwise())
{
a.ExteriorRing.Reverse();
}

if (!b.IsClockwise())
{
b.ExteriorRing.Reverse();
}

return a.Intersect(b, out var union, true);
}

public static Coordinate NorthernMost(this Polygon a)
{
var min = 0;
var northernMost = a.ExteriorRing[0];
for (var i = 1; i < a.ExteriorRing.Count - 1; i++)
{
var coor = a.ExteriorRing[i];
if (northernMost.Latitude == coor.Latitude)
{
if (northernMost.Longitude > coor.Longitude)
{
northernMost = coor;
min = i;
}
}
else if (northernMost.Latitude < coor.Latitude)
{
northernMost = coor;
min = i;
}
}

return northernMost;
}

/// <summary>
/// Will rotate the exterior ring so that 'i' is the new start (and end, in case of a closed polygon).
/// Result will always be closed
/// </summary>
/// <param name="a"></param>
/// <param name="newStart"></param>
public static void RotateExteriorRing(this Polygon a, int newStart)
{

if (a.IsClosed())
{
a.ExteriorRing.RemoveAt(a.ExteriorRing.Count);
}

var newExterior = new List<Coordinate>();
for (var i = newStart; i < a.ExteriorRing.Count; i++)
{
newExterior.Add(a.ExteriorRing[i]);
}

for (var i = 0; i < newStart; i++)
{
newExterior.Add(a.ExteriorRing[i]);
}
newExterior.Add(newExterior[0]);
a.ExteriorRing = newExterior;
}
}
}
4 changes: 2 additions & 2 deletions src/Itinero/LocalGeo/Operations/Intersections.cs
Original file line number Diff line number Diff line change
@@ -83,7 +83,7 @@ internal static IEnumerable<Coordinate> IntersectWithLines(this Polygon polygon,

var previousDistance = 0f;
var previous = line.Coordinate1;
var previousInside = polygon.PointIn(previous);
var previousInside = polygon.ContainsPoint(previous);

var cleanIntersections = new List<Coordinate>();
foreach (var intersection in sortedList)
@@ -97,7 +97,7 @@ internal static IEnumerable<Coordinate> IntersectWithLines(this Polygon polygon,
// calculate in or out.
var middle = new Coordinate((previous.Latitude + intersection.Value.Latitude) / 2,
(previous.Longitude + intersection.Value.Longitude) / 2);
var middleInside = polygon.PointIn(middle);
var middleInside = polygon.ContainsPoint(middle);
if (previousInside != middleInside ||
cleanIntersections.Count == 0)
{ // in or out change or this is the first intersection.
12 changes: 6 additions & 6 deletions src/Itinero/LocalGeo/Operations/PointInPolygon.cs
Original file line number Diff line number Diff line change
@@ -32,11 +32,11 @@ internal static class PointInPolygon
/// Note that polygons spanning a pole, without a point at the pole itself, will fail to detect points within the polygon;
/// (e.g. Polygon=[(lat=80°, 0), (80, 90), (80, 180)] will *not* detect the point (85, 90))
/// </summary>
internal static bool PointIn(this Polygon poly, Coordinate point)
internal static bool ContainsPoint(this Polygon poly, Coordinate point)
{
// For startes, the point should lie within the outer

var inOuter = PointIn(poly.ExteriorRing, point);
var inOuter = ContainsPoint(poly.ExteriorRing, point);
if (!inOuter)
{
return false;
@@ -45,7 +45,7 @@ internal static bool PointIn(this Polygon poly, Coordinate point)
// and it should *not* lay within any inner ring
for (var i = 0; i < poly.InteriorRings.Count; i++)
{
var inInner = PointIn(poly.InteriorRings[i], point);
var inInner = ContainsPoint(poly.InteriorRings[i], point);
if (inInner)
{
return false;
@@ -57,7 +57,7 @@ internal static bool PointIn(this Polygon poly, Coordinate point)
/// <summary>
/// Returns true if the given point lies within the ring.
/// </summary>
internal static bool PointIn(List<Coordinate> ring, Coordinate point)
internal static bool ContainsPoint(List<Coordinate> ring, Coordinate point)
{

// Coordinate of the point. Longitude might be changed in the antemeridian-crossing case
@@ -106,7 +106,7 @@ internal static bool PointIn(List<Coordinate> ring, Coordinate point)
// no intersections passed yet -> not within the polygon
var result = false;

for (int i = 0; i < ring.Count; i++)
for (var i = 0; i < ring.Count; i++)
{
var start = ring[i];
var end = ring[(i + 1) % ring.Count];
@@ -144,7 +144,7 @@ internal static bool PointIn(List<Coordinate> ring, Coordinate point)

// Analogously, at least one point of the segments should be on the right (east) of the point;
// otherwise, no intersection is possible (as the raycast goes right)
if (!(Math.Max(stLong, endLong) >= longitude))
if (!(Math.Max(stLong, endLong) > longitude))
{
continue;
}
21 changes: 14 additions & 7 deletions src/Itinero/LocalGeo/Operations/PolygonAreaCalcutor.cs
Original file line number Diff line number Diff line change
@@ -12,23 +12,30 @@ namespace Itinero.LocalGeo.Operations
internal static class PolygonAreaCalcutor
{
/// <summary>
/// Calculates the surface area of a closed, not-self-intersecting polygon
/// Calculates the surface area of a closed, not-self-intersecting polygon.
/// Will return a negative result if the polygon is counterclockwise
/// </summary>
/// <param name="points"></param>
/// <returns></returns>
internal static float SurfaceArea(this List<Coordinate> points)
public static float SignedSurfaceArea(this List<Coordinate> points)
{
var l = points.Count;
var area = 0f;
for (var i = 1; i < l+1; i++)
{
var p = points[i % l];
var pi = points[(i + 1) % l];
var pm = points[(i - 1)];
area += p.Longitude * (pi.Latitude - pm.Latitude);
var cur = points[i % l];
var nxt = points[(i + 1) % l];
var prev = points[(i - 1)];
area += cur.Longitude * (prev.Latitude - nxt.Latitude);
}

return Math.Abs(area / 2);
return area / 2;
}

public static float SurfaceArea(this List<Coordinate> points)
{
return Math.Abs(points.SignedSurfaceArea());
}

}
}
Loading