C# 使用LINQ的多边形中的点或多边形上的点

C# 使用LINQ的多边形中的点或多边形上的点,c#,.net,linq,algorithm,geometry,C#,.net,Linq,Algorithm,Geometry,如前一个问题所述,我正在研究一些基于点列表的数学算法。我目前正在研究多边形中的点。我有如何做到这一点的代码,并在这里找到了一些很好的参考,比如这个链接。所以,我可以计算出一个点是否在多边形中。作为确定的一部分,我想确定点是否实际位于多边形上。这我也能做到。如果我能做到这一切,你会问我什么问题 我可以使用LINQ高效地完成它吗?我已经可以这样做了(假设我前面的问题中描述的成对扩展方法以及我的问题/答案所链接的链接,并假设一个位置类型具有X和Y成员)。我没有做过很多测试,所以lambda可能不是10

如前一个问题所述,我正在研究一些基于点列表的数学算法。我目前正在研究多边形中的点。我有如何做到这一点的代码,并在这里找到了一些很好的参考,比如这个链接。所以,我可以计算出一个点是否在多边形中。作为确定的一部分,我想确定点是否实际位于多边形上。这我也能做到。如果我能做到这一切,你会问我什么问题

我可以使用LINQ高效地完成它吗?我已经可以这样做了(假设我前面的问题中描述的成对扩展方法以及我的问题/答案所链接的链接,并假设一个位置类型具有X和Y成员)。我没有做过很多测试,所以lambda可能不是100%正确。此外,它没有考虑到非常小的差异

编辑 请注意,此代码的最新编辑已从TakeWhile更改为TakeWhileInclusive。请参阅本文末尾的扩展方法

//
// 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;
  }
}
//
//从pt向左延伸一条光线。该光线是否与线段p1->p2相交?
//根据定义,从pt延伸的光线不能与水平段相交,因此
//第一个检查是看线段是否水平。
//如果是,就不可能有交叉口。
//如果不是,可能会有一个十字路口。
//
公共静态布尔点(位置p1、位置p2、位置pt)
{
布尔相交=假;
如果(p1.Y!=p2.Y)
{
//pt是否在(垂直)p1和p2之间?
//如果是这样,则来自pt的光线可能相交。
//否则,来自pt的光线将无法相交。
if((p1.Y>=pt.Y&&p2.Y=pt.Y))
{
if(p1.Xp2段上。我仍然在
//为了测试PointInPolygon算法,我仍然可以模拟“on”
//通过传递一个等于多边形上的一个点的pt来区分大小写。
返回(pt==p1 | | pt==p2);
}
公共静态点多边形位置点多边形(IEnumerable pt,GTPosition pt)
{
//
//应用乔丹曲线定理确定点是否在多边形中。本质上,
//这个算法从pt无限延伸出一条射线。在我的实现中,我向左延伸。
//如果点位于多边形内部,则光线将与多边形相交奇数次。
//如果点位于多边形外部,则光线将与多边形相交偶数次。
//理想情况下,我们不仅能够报告点是否位于多边形的内部或外部,而且能够报告
//此外,如果它位于多边形上(即它位于其中一个线段上),请注意“开”和
//内/外是唯一的。点要么在多边形上,要么在多边形的内部或外部
//不能是“内部和打开”或“外部和打开”。
//因此,算法如下所示:
// 1。将多边形的点视为构成段的两个点(P1->P2,P2--P3,等)。
//2.对于每个段,进行两次计算:
//从pt向左延伸的光线是否与线段相交?
//pt是否在段p[i],p[i+1]上?
//3.计算交点总数。如果奇数,则点在内侧。如果偶数,则点在外侧。
//4.这里是棘手的部分,如果点在任何一段上,那么我们可以停止。如果它在
//第一段,则无需进行开/关计算和交点
//其余分段的计算。
//
变量结果=成对点((p1,p2)=>
{
int intersect=0;
int on=0;
on=点分段(p1、p2、pt)?1:0;
如果(on==0)
{
//如果我们已经知道pt在p1->p2上,就不需要确定交叉点。
intersect=点INTERSECTS段(p1、p2、pt)?1:0;
}
返回新的{on,intersect};
})
TakeWhileInclusive((项)=>on=0)/只考虑段直到(或如果)PT在段上。
.Aggregate((curr,next)=>new//记录总共有多少个交叉口。
{ 
on=curr.on+next.on,
intersect=当前intersect+下一个intersect
});
如果(result.on!=0)
{
返回点polyGonLocation.On;
}
其他的
{
返回结果%2==0?PointInPolygonLocation.Outside:PointInPolygonLocation.Inside;
}
}
此函数PointInPolygon获取输入位置pt,迭代位置值的输入序列,并使用Jordan曲线
// 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: https://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);
}
   public static PointInPolygonLocation PointInPolygon(IEnumerable<Position> pts, Position pt)
    {
        bool isIn = pts.Pairwise((p1, p2) => Tuple.Create(p1, p2)).Any(tuple => tuple.Item1.Y != tuple.Item2.Y &&
        ((tuple.Item1.Y >= pt.Y && tuple.Item2.Y < pt.Y) || (tuple.Item1.Y < pt.Y && tuple.Item2.Y >= pt.Y))
        && ((tuple.Item1.X < tuple.Item1.X && tuple.Item2.X < pt.X) || ((pt.Y - tuple.Item1.Y) * ((tuple.Item1.X - tuple.Item2.X) / (tuple.Item1.Y - tuple.Item2.Y)) * tuple.Item1.X) < pt.X));
        return isIn ? PointInPolygonLocation.Inside : PointInPolygonLocation.Outside;
    }
   public static PointInPolygonLocation PointInPolygon(IEnumerable<Position> pts, Position pt)
    {
        bool isIn = pts.Pairwise((p1, p2) => Tuple.Create(p1, p2)).Any(tuple => ReadablePredicate(tuple.Item1, tuple.Item2, pt));
        return isIn ? PointInPolygonLocation.Inside : PointInPolygonLocation.Outside;
    }
    public static bool ReadablePredicate(Position p1, Position p2, Position pt)
    {
        if (p1.Y == p2.Y)
            return false;
        if (!((p1.Y >= pt.Y && p2.Y < pt.Y) || (p1.Y < pt.Y && p2.Y >= pt.Y)))
            return false;
        if (p1.X < pt.X && p2.X < pt.X) // Originally was (p1.X < p1.X && ...). that makes no sense, so I assumed you meant p1.X < pt.X
            return true;
        if ((p1.X < pt.X || p2.X < pt.X) && ((pt.Y - p1.Y) * ((p1.X - p2.X) / (p1.Y - p2.Y)) * p1.X) < pt.X)
            return true;
        return false;
    }