As noted in an earlier question, How to Zip enumerable with itself, I am working on some math algorithms based on lists of points. I am currently working on point in polygon. I have the code for how to do that and have found several good references here on SO, such as this link Hit test. So, I can figure out whether or not a point is in a polygon. As part of determining that, I want to determine if the point is actually on the polygon. This I can also do. If I can do all of that, what is my question you might ask?
Can I do it efficiently using LINQ? I can already do something like the following (assuming a Pairwise extension method as described in my earlier question as well as in links to which my question/answers links, and assuming a Position type that has X and Y members). I have not tested much, so the lambda might not be 100% correct. Also, it does not take very small differences into account.
EDIT Note that most recent edit of this code has changed from TakeWhile to TakeWhileInclusive. See the extension method towards the end of this post.
//
// Extend a ray from pt towards the left. Does this ray intersect the segment p1->p2?
// By definition, the ray extending from pt cannot intersect a horizontal segment, so our
// first check is to see whether or not the segment is horizontal.
// If it is, there cannot be an intersection.
// If it is not, there could be an intersection.
//
public static bool PointIntersectSegment(Position p1, Position p2, Position pt)
{
bool intersect = false;
if (p1.Y != p2.Y)
{
// Is pt between (vertically) p1 and p2?
// If so, the ray from pt might intersect.
// If not, the ray from pt cannot intersect.
if ((p1.Y >= pt.Y && p2.Y < pt.Y) || (p1.Y < pt.Y && p2.Y >= pt.Y))
{
if (p1.X < pt.X && p2.X < pt.X)
{
// If the segment is to the left of pt, then the ray extending leftwards from pt will intersect it.
intersect = true;
}
else
if ((p1.X < pt.X || p2.X < pt.X))
{
// If either end of the segment is to the left of pt, calculate intersection (x only) of the
// ray from pt and the segment. If the intersection (x only) is to the left of pt, then
// the ray extending leftwards from pt will intersect it.
double inverseSlope = (p1.X - p2.X) / (p1.Y - p2.Y);
double intersectionX = (pt.Y - p1.Y) * inverseSlope + p1.X;
if (intersectionX < pt.X)
{
intersect = true;
}
}
}
}
return intersect;
}
public static bool PointOnSegment(Position p1, Position p2, Position pt)
{
// Obviously, this is not really going to tell us if pt is on the segment p1->p2. I am still
// working on that. For testing the PointInPolygon algorithm, I can still simulate the "on"
// case by passing in a pt that is equal to one of the points on the polygon.
return (pt == p1 || pt == p2);
}
public static PointInPolygonLocation PointInPolygon(IEnumerable<Position> pts, GTPosition pt)
{
//
// Implemention of the Jordan Curve theorem to determine if a point is in a polygon. In essence,
// this algorithm extends a ray infinitely from pt. In my implementation I am extending to the left.
// If the point is inside the polygon, then the ray will intersect the polygon a odd number of times.
// If the point is outside the polygon, then the ray will intersect the polygon an even number of times.
// Ideally, we would be able to report not only if the point is inside or outside of the polygon, but
// also if it is "on" the polygon (i.e. it lies on one of the segments). Note that "on" and
// inside/outside are exlusive. The point is either on the polygon or it is inside or outside, it
// cannot be "inside and on" or "outside and on".
// So, the algorithm is as follows:
// 1. Consider the points of the polygon as pairs making up the segments (p1->p2, p2->p3, etc).
// 2. For each segment, perform two calculations:
// Does the ray extending left from pt intersect the segment?
// Is pt on the segment p[i], p[i+1]?
// 3. Count the total number of intersections. If odd, point is inside. If even, point is outside.
// 4. Here is the tricky part, if the point is on any segment, then we can stop. If it is on the
// first segment, then there is no need to go through the on/off calculation and the intersection
// calculation for the rest of the segments.
//
var result = pts.Pairwise( (p1, p2) =>
{
int intersect = 0;
int on = 0;
on = PointOnSegment(p1, p2, pt) ? 1 : 0;
if (on == 0)
{
//Don't really need to determine intersection if we already know that pt is on p1->p2.
intersect = PointIntersectSegment(p1, p2, pt) ? 1 : 0;
}
return new { on, intersect };
})
.TakeWhileInclusive((item) => item.on == 0) //Only consider segments until (or if) pt is on a segment.
.Aggregate((curr, next) => new //Keep a running total of how many intersections.
{
on = curr.on + next.on,
intersect = curr.intersect + next.intersect
});
if (result.on != 0)
{
return PointInPolygonLocation.On;
}
else
{
return result.intersect % 2 == 0 ? PointInPolygonLocation.Outside : PointInPolygonLocation.Inside;
}
}
This function, PointInPolygon, takes the input Position, pt, iterates over the input sequence of position values, and uses the Jordan Curve method to determine how many times a ray extended from pt to the left intersects the polygon. The lambda expression will yield, into the "zipped" list, 1 for every segment that is crossed, and 0 for the rest. The sum of these values determines if pt is inside or outside of the polygon (odd == inside, even == outside). So far, so good.
Now, for any consecutive pairs of position values in the sequence (i.e. in any execution of the lambda), we can also determine if pt is ON the segment p1, p2. If that is the case, we can stop the calculation because we have our answer.
Ultimately, my question is this: Can I perform this calculation (maybe using Aggregate?) such that we will only iterate over the sequence no more than 1 time AND can we stop the iteration if we encounter a segment that pt is ON? In other words, if pt is ON the very first segment, there is no need to examine the rest of the segments because we have the answer.
It might very well be that this operation (particularly the requirement/desire to possibly stop the iteration early) does not really lend itself well to the LINQ approach.
It just occurred to me that maybe the lambda expression could yield a tuple, the intersection value (1 or 0 or maybe true or false) and the "on" value (true or false). Maybe then I could use TakeWhile(anontype.PointOnPolygon == false). If I Sum the tuples and if ON == 1, then the point is ON the polygon. Otherwise, the oddness or evenness of the sum of the other part of the tuple tells if the point is inside or outside.
EDIT Cleaned up the code formatting, moved the lambda to a standalone function, added a new function to if the point is on a segment. The new function is really a dummy, it just compares the input point to the start and end points of the segment and returns true if either is equal. So, it should probably be called PointIsOnEndpointOfSegment. There is already a lot of code here and I didn't want to cloud the issue with more fooling around with x's and y's to do the "real" ON calculation.
With the code as it is now, I can do the following: 1. View sequence of points as a sequence of pairs (segments) (via Pairwise). 2. In the Pairwise result selector, I compute an anonymous type containing the value intersection=1 if the ray extending leftwards from pt intersects the segment and the value on=1 if the point is "on" the segment. 3. Express the intersection/on anonymous types as a sequence.
I thought I could use TakeWhile to take all of the anonymous types until (or if) one of the anonymous types indicates that the pt was ON a segment. Then, using Aggregate, sum the values of intersection and on. If the sum of on != 0, then the point was on one of the segments. Otherwise, the sum of intersection will tell us if the point is inside (odd) or outside (even - or zero).
Unfortunately, I ran into the a similar problem with TakeWhile as was described here:
How to TakeWhile PLUS one more element.
My TakeWhile takes all of the items from the sequence up to, but not including, the item (if any) that indicates an intersection. So, when I aggregate the results, the intersecting item is not there.
It looks like one way that people have handled this situation before has been to write a TakeUntil extension that is like TakeWhile, but it includes the first item that fails the predicate. Would such a TakeUntil extension force an evaluation of the entire sequence?
This has mainly been an exercise to see if this algorithm could be implemented using Linq subject to the following requirements: 1. Entire sequence of input points is iterated ONLY if the input point is NOT on any of the segments. 2. If the input point is on one of the segments, the iteration over the input points should stop. If the input point is on the first segment of a 1000 point polygon, there is no need to do the intersection and "on" calculations for the rest the segments.
We have implemented this algorithm previously in C++ using a for loop over the points, breaking out if the point is ever on a segment. I could certainly implement the same way, I just wanted to see if I could LINQify if without incurring more interations than the "old" for loop way.
I will probably try the TakeUntil approach just for kicks and see what happens.
Edit
With this code (TakeWhileInclusive IEnumerable that is virtually identical to the TakeUntil described in the link about TakeWhile PLUS one more element):
// Mimic TakeWhile's overload which takes an index as a parameter to the predicate.
public static IEnumerable<T> TakeWhileInclusive<T>(this IEnumerable<T> seq, Func<T, int, bool> predicate)
{
int i = 0;
foreach( T e in seq)
{
if (!predicate(e,i))
{
// If here, then this is first item to fail predicate. Yield this item and then break.
yield return e;
yield break;
}
// yield each item from the input sequence until end of sequence or first failure (see above).
yield return e;
i++;
}
}
//
// First saw this here: http://blog.foxxtrot.net/2009/06/inclusively-take-elements-using-linq-and-custom-extensions.html
// TakeWhileInclusive - IEnumerable extension to mimic TakeWhile, but also to return the first
// item that failed the predicate.
// e.g. seq = 1 2 3 4
// TakeWhile(p => p != 3) will yield 1 2
// TakeWhileInclusive(p => p != 3) will yield 1 2 3
// e.g. seq = 0 0 0 1 0
// TakeWhile(p => p == 0) will yield 0 0 0
// TakeWhileInclusive(p => p == 0) will yield 0 0 0 1
// Similar to TakeUntil from here: http://stackoverflow.com/questions/2242318/how-could-i-take-1-more-item-from-linqs-takewhile
//
public static IEnumerable<T> TakeWhileInclusive<T>(this IEnumerable<T> seq, Func<T, bool> predicate)
{
return seq.Select((x, i) => new { Item = x, Index = i })
.TakeWhileInclusive((x, i) => predicate(x.Item))
.Select(x => x.Item);
}
And the corresponding change in my original code (replace TakeWhile with TakeWhileInclusive), I can get the answer (either the point is on the polygon or it is inside or outside) and the iteration stops (via TakeWhileInclusive) after it hits the segment of the polygon that the point is ON. I will note again that my "PointOnSegment" code is bogus, but is fine for testing as long as I am testing a point that is equal to one of the polygon points.
I will leave this for now in case anyone else wants to comment. Right now I am inclined to accept my own answer as it does what I intended to do. Whether or not this particular approach is ultimately a good one, I don't know yet.