多边形中的 C# 点

Posted

技术标签:

【中文标题】多边形中的 C# 点【英文标题】:C# Point in polygon 【发布时间】:2011-05-13 16:25:22 【问题描述】:

我正在尝试确定一个点是否在多边形内。 Polygon 由 Point 对象数组定义。我可以很容易地确定该点是否在多边形的有界框内,但我不确定如何判断它是否在实际多边形内。如果可能的话,我只想使用 C# 和 WinForms。我宁愿不调用 OpenGL 或其他东西来完成这个简单的任务。

这是我目前的代码:

private void CalculateOuterBounds()

    //m_aptVertices is a Point[] which holds the vertices of the polygon.
    // and X/Y min/max are just ints
    Xmin = Xmax = m_aptVertices[0].X;
    Ymin = Ymax = m_aptVertices[0].Y;

    foreach(Point pt in m_aptVertices)
    
        if(Xmin > pt.X)
            Xmin = pt.X;

        if(Xmax < pt.X)
            Xmax = pt.X;

        if(Ymin > pt.Y)
            Ymin = pt.Y;

        if(Ymax < pt.Y)
            Ymax = pt.Y;
    


public bool Contains(Point pt)

    bool bContains = true; //obviously wrong at the moment :)

    if(pt.X < Xmin || pt.X > Xmax || pt.Y < Ymin || pt.Y > Ymax)
        bContains = false;
    else
    
        //figure out if the point is in the polygon
    

    return bContains;

【问题讨论】:

您总是可以只使用Region 类。 @Saeed 我相信它们都是凸的。 @leppie,我不熟悉 Region 类。想为我写一些代码吗? @jb:不,很容易学。 【参考方案1】:

我检查了这里的代码,都有问题。

最好的方法是:

    /// <summary>
    /// Determines if the given point is inside the polygon
    /// </summary>
    /// <param name="polygon">the vertices of polygon</param>
    /// <param name="testPoint">the given point</param>
    /// <returns>true if the point is inside the polygon; otherwise, false</returns>
    public static bool IsPointInPolygon4(PointF[] polygon, PointF testPoint)
    
        bool result = false;
        int j = polygon.Count() - 1;
        for (int i = 0; i < polygon.Count(); i++)
        
            if (polygon[i].Y < testPoint.Y && polygon[j].Y >= testPoint.Y || polygon[j].Y < testPoint.Y && polygon[i].Y >= testPoint.Y)
            
                if (polygon[i].X + (testPoint.Y - polygon[i].Y) / (polygon[j].Y - polygon[i].Y) * (polygon[j].X - polygon[i].X) < testPoint.X)
                
                    result = !result;
                
            
            j = i;
        
        return result;
    

【讨论】:

这很好用,回来确保你不会像我一样用 Ints 不假思索地实现它!一定要使用浮动。导致除法的舍入错误使该方法在一小部分时间内失败......非常烦人! 像魅力一样工作。我正在使用它来确定给定位置是否位于封闭多边形内(映射解决方案)。现在,最困难的部分。理解代码:P 这是迄今为止最好的解决方案,恕我直言。 接受的答案对我来说不好,但你的答案是完美的。谢谢! 次要 nit:polygon.Count() 可能是 polygon.Length。 Length 正在调用 System.Array.get_Length,它直接(并且有效地)获取长度。而 .Count() 调用的是 IEnumerable 上的扩展方法,效率较低。【参考方案2】:

接受的答案在我的项目中对我不起作用。我最终使用了here 找到的代码。

public static bool IsInPolygon(Point[] poly, Point p)

    Point p1, p2;
    bool inside = false;

    if (poly.Length < 3)
    
        return inside;
    

    var oldPoint = new Point(
        poly[poly.Length - 1].X, poly[poly.Length - 1].Y);

    for (int i = 0; i < poly.Length; i++)
    
        var newPoint = new Point(poly[i].X, poly[i].Y);

        if (newPoint.X > oldPoint.X)
        
            p1 = oldPoint;
            p2 = newPoint;
        
        else
        
            p1 = newPoint;
            p2 = oldPoint;
        

        if ((newPoint.X < p.X) == (p.X <= oldPoint.X)
            && (p.Y - (long) p1.Y)*(p2.X - p1.X)
            < (p2.Y - (long) p1.Y)*(p.X - p1.X))
        
            inside = !inside;
        

        oldPoint = newPoint;
    

    return inside;

【讨论】:

好答案。但是,为什么您需要在计算中的某些坐标上添加long?如果你有十进制坐标,它会把事情搞砸。是复制/粘贴错误还是我遗漏了什么? 这很好用,我非常高兴。谢谢!! 如果所讨论的多边形少于三个点,则它是无效的,而不是测试的情况。【参考方案3】:

请参阅this,它是用 c++ 编写的,并且可以以相同的方式在 c# 中完成。

对于凸多边形来说太容易了:

如果多边形是凸的,那么可以 将多边形视为从 第一个顶点。一个点在 如果它是这个多边形的内部 总是站在所有人的同一边 构成路径的线段。

给定 P0 之间的线段 (x0,y0) 和 P1 (x1,y1),另一个点 P(x,y) 有如下关系 到线段。计算 (y - y0) (x1 - x0) - (x - x0) (y1 - y0)

如果小于 0 则 P 为 线段的右侧,如果更大 大于 0 它在左边,如果等于 0 则位于线段上。

这是它的 c# 代码,我没有检查边缘情况。

        public static bool IsInPolygon(Point[] poly, Point point)
        
           var coef = poly.Skip(1).Select((p, i) => 
                                           (point.Y - poly[i].Y)*(p.X - poly[i].X) 
                                         - (point.X - poly[i].X) * (p.Y - poly[i].Y))
                                   .ToList();

            if (coef.Any(p => p == 0))
                return true;

            for (int i = 1; i < coef.Count(); i++)
            
                if (coef[i] * coef[i - 1] < 0)
                    return false;
            
            return true;
        

我用简单的矩形测试它工作正常:

            Point[] pts = new Point[]  new Point  X = 1, Y = 1 , 
                                        new Point  X = 1, Y = 3 , 
                                        new Point  X = 3, Y = 3 , 
                                        new Point  X = 3, Y = 1  ;
            IsInPolygon(pts, new Point  X = 2, Y = 2 ); ==> true
            IsInPolygon(pts, new Point  X = 1, Y = 2 ); ==> true
            IsInPolygon(pts, new Point  X = 0, Y = 2 ); ==> false

关于 linq 查询的说明:

poly.Skip(1) ==> 创建一个新列表,从poly 列表的位置1 开始,然后按 (point.Y - poly[i].Y)*(p.X - poly[i].X) - (point.X - poly[i].X) * (p.Y - poly[i].Y) 我们将计算方向(在参考段落中提到)。 类似的例子(有另一个操作):

lst = 2,4,8,12,7,19
lst.Skip(1) ==> 4,8,12,7,19
lst.Skip(1).Select((p,i)=>p-lst[i]) ==> 2,4,4,-5,12

【讨论】:

嗯,它有效,但我不完全确定如何。介意解释一下吗?主要是 coef linq 语句部分。 不喜欢这段代码缺乏可调试性。宁愿看到没有 linq 语法的代码 这未通过我的一项测试。考虑一个靠近矩形角的点。多边形 = [0, 0, 2, 0, 2, 2, 0, 2] 和点 = 3, 2。该算法将这一点作为内部返回:/ @JacobMcKay:正如我写的那样,代码可能不安全,因为当时我在一分钟内写了它并且没有尝试不同的测试(只是一个测试),这就是我写的:“我没有检查边缘情况。”该代码只是解释如何实现该想法的示例。当然,它需要测试并覆盖边缘情况。 对于那些想知道这个解决方案有什么问题的人(对于凸多边形): 1. 它完全忽略了最后一条线段 2. 如果该点打开,将触发“在线段在线”检查器完全是线,而不仅仅是线段(因此它可以匹配形状之外的点)【参考方案4】:

您可以使用光线投射算法。在Point in polygon problem 的***页面中有详细描述。

这就像计算从外部到该点的光线接触多边形边界的次数一样简单。如果它接触偶数次,则该点位于多边形之外。如果它接触奇数次,则该点在里面。

要计算射线接触的次数,请检查射线与所有多边形边之间的交点。

【讨论】:

【参考方案5】:

meowNET anwser 不包括多边形中的多边形顶点,并且点正好在水平边缘上。如果您需要一个精确的“包容性”算法:

    public static bool IsInPolygon(this Point point, IEnumerable<Point> polygon)
    
        bool result = false;
        var a = polygon.Last();
        foreach (var b in polygon)
        
            if ((b.X == point.X) && (b.Y == point.Y))
                return true;

            if ((b.Y == a.Y) && (point.Y == a.Y))
            
                if ((a.X <= point.X) && (point.X <= b.X))
                    return true;

                if ((b.X <= point.X) && (point.X <= a.X))
                    return true;
            

            if ((b.Y < point.Y) && (a.Y >= point.Y) || (a.Y < point.Y) && (b.Y >= point.Y))
            
                if (b.X + (point.Y - b.Y) / (a.Y - b.Y) * (a.X - b.X) <= point.X)
                    result = !result;
            
            a = b;
        
        return result;
    

【讨论】:

我用航空温度包络线(=多边形)对此进行了测试,这是唯一通过我所有单元测试的算法。所有其他人都未能检测到外边缘上的某些点。 @Marco 虽然其他算法应该是一致的 - 例如,它们应该包括底部和左侧边缘上的点,而不是顶部和右侧边缘上的点。就是这样,如果你有两个镶嵌多边形,任何给定的点都被报告为肯定在一个而不是另一个。如果您有一个包含所有边缘的算法,它将双重报告一个点在多边形接触的两个多边形中 需要添加|| (a.X >= point.X) && (point.X >= b.X)) 用于水平线检查 谢谢JLi,你是对的。我编辑了答案以考虑 a.X>b.X 的情况。 (我选择拆分成几个“如果”以最大限度地提高可读性)【参考方案6】:

完整的算法和 C 代码可在http://alienryderflex.com/polygon/ 获得 将其转换为 c# / winforms 将是微不足道的。

【讨论】:

这正是 wpf 无限方便的场景:msdn.microsoft.com/en-us/library/ms608753.aspx【参考方案7】:

我的回答取自这里:Link

我把 C 代码转换成 C# 并让它工作

static bool pnpoly(PointD[] poly, PointD pnt )
    
        int i, j;
        int nvert = poly.Length;
        bool c = false;
        for (i = 0, j = nvert - 1; i < nvert; j = i++)
        
            if (((poly[i].Y > pnt.Y) != (poly[j].Y > pnt.Y)) &&
             (pnt.X < (poly[j].X - poly[i].X) * (pnt.Y - poly[i].Y) / (poly[j].Y - poly[i].Y) + poly[i].X))
                c = !c; 
        
        return c;
    

你可以用这个例子来测试它:

PointD[] pts = new PointD[]  new PointD  X = 1, Y = 1 , 
                                    new PointD  X = 1, Y = 2 , 
                                    new PointD  X = 2, Y = 2 , 
                                    new PointD  X = 2, Y = 3 ,
                                    new PointD  X = 3, Y = 3 ,
                                    new PointD  X = 3, Y = 1 ;

        List<bool> lst = new List<bool>();
        lst.Add(pnpoly(pts, new PointD  X = 2, Y = 2 ));
        lst.Add(pnpoly(pts, new PointD  X = 2, Y = 1.9 ));
        lst.Add(pnpoly(pts, new PointD  X = 2.5, Y = 2.5 ));
        lst.Add(pnpoly(pts, new PointD  X = 1.5, Y = 2.5 ));
        lst.Add(pnpoly(pts, new PointD  X = 5, Y = 5 ));

【讨论】:

这正是@meowNET 在下面所做的,不是吗? 不是真的,它相似但不一样。仔细看看@N4ppeL 我就是这么做的。我认为你错了。很容易看出循环是相同的。那么您的(polygon[i].Y &gt; point.Y) != (polygon[j].Y &gt; point.Y) 与下面的第一个相同,而您的后半部分和第二个 if 仅在 > 和 【参考方案8】:

我对整数工作的 PointInPolygon 函数的关键业务实现(就像 OP 似乎正在使用的那样)针对水平、垂直和对角线进行了单元测试,线上的点包含在测试中(函数返回 true)。

这似乎是一个老问题,但之前的所有追踪示例都有一些缺陷:不考虑水平或垂直多边形线、多边形边界线或边的顺序(顺时针、逆时针)。

以下函数通过了我提出的测试(正方形、菱形、对角交叉,总共 124 次测试),边、顶点以及边和顶点的内部和外部都有点。

代码对每对连续的多边形坐标执行以下操作:

    检查多边形顶点是否等于点 检查点是在水平线上还是垂直线上 计算(作为双精度)并收集相交并转换为整数 排序相交,因此边的顺序不会影响算法 检查点是否在偶数相交上(等于 - 在多边形中) 检查点 x 坐标之前的相交数是否为奇数 - 在多边形中

如有必要,算法可以很容易地适应浮点数和双精度数。

附带说明 - 我想知道在过去近 10 年中创建了多少软件来检查多边形中的点并在某些情况下失败。

    public static bool IsPointInPolygon(Point point, IList<Point> polygon)
    
        var intersects = new List<int>();
        var a = polygon.Last();
        foreach (var b in polygon)
        
            if (b.X == point.X && b.Y == point.Y)
            
                return true;
            

            if (b.X == a.X && point.X == a.X && point.X >= Math.Min(a.Y, b.Y) && point.Y <= Math.Max(a.Y, b.Y))
            
                return true;
            

            if (b.Y == a.Y && point.Y == a.Y && point.X >= Math.Min(a.X, b.X) && point.X <= Math.Max(a.X, b.X))
            
                return true;
            

            if ((b.Y < point.Y && a.Y >= point.Y) || (a.Y < point.Y && b.Y >= point.Y))
            
                var px = (int)(b.X + 1.0 * (point.Y - b.Y) / (a.Y - b.Y) * (a.X - b.X));
                intersects.Add(px);
            

            a = b;
        

        intersects.Sort();
        return intersects.IndexOf(point.X) % 2 == 0 || intersects.Count(x => x < point.X) % 2 == 1;
    

【讨论】:

【参考方案9】:

对于那些使用 NET Core 的用户,Region.IsVisible 可从 NET Core 3.0 获得。添加包System.Drawing.Common后,

using System;
using System.Drawing;
using System.Drawing.Drawing2D;

namespace Example

    class Program
    
        static bool IsPointInsidePolygon(Point[] polygon, Point point)
        
            var path = new GraphicsPath();
            path.AddPolygon(polygon);

            var region = new Region(path);
            return region.IsVisible(point);
        

        static void Main(string[] args)
        
            Point vt1 = new Point(0, 0);
            Point vt2 = new Point(100, 0);
            Point vt3 = new Point(100, 100);
            Point vt4 = new Point(0, 100);
            Point[] polygon =  vt1, vt2, vt3, vt4 ;

            Point pt = new Point(50, 50);

            bool isPointInsidePolygon = IsPointInsidePolygon(polygon, pt);
            Console.WriteLine(isPointInsidePolygon);
        
    

不太重要的是,添加 System.Drawing.Common 包会使发布文件夹的大小增加 400 KB。也许与自定义代码相比,此实现也可能更慢(在 i7-8665u 上,函数定时为 18 毫秒)。但是,我还是更喜欢这样,因为少了一件需要担心的事情。

【讨论】:

【参考方案10】:

你真正需要的是 4 行来实现缠绕数方法。但首先,参考 System.Numerics 以使用复杂库。下面的代码假设您已经平移了一个点循环(存储在 cpyArr 中),因此您的候选点位于 0,0。

    对于每个点对,使用第一个点创建一个复数 c1,使用第二个点创建一个复数 c2(循环中的前 2 行)

    现在这里有一些复数论。将 c1 和 c2 视为向量的复数表示。要从向量 c1 到向量 c2,可以将 c1 乘以复数 Z (c1Z=c2)。 Z 旋转 c1 使其指向 c2。然后它还会拉伸或挤压 c1,使其与 c2 相匹配。为了得到这样一个神奇的数字 Z,你需要将 c2 除以 c1(因为 c1Z=c2,Z=c2/c1)。你可以查看你高中关于除复数的笔记或使用微软提供的方法。得到这个数字后,我们真正关心的是相位角。

    要使用缠绕方法,我们将所有相位相加,如果点在该区域内,我们应该 +/- 2 pi。否则,总和应为 0

    我添加了极端情况,“字面意思”。如果您的相位角是 +/- pi,那么您就在点对之间的边缘。在那种情况下,我会说该点是该区域的一部分并跳出循环

      /// <param name="cpyArr">An array of 2 coordinates (points)</param>
      public static bool IsOriginInPolygon(double[,] cpyArr)
      
          var sum = 0.0;
          var tolerance = 1e-4;
    
          var length = cpyArr.GetLength(0);
          for (var i = 0; i < length-1; i++)
          
              //convert vertex point pairs to complex numbers for simplified coding
              var c2 = new Complex(cpyArr[i+1, 0], cpyArr[i+1, 1]);
              var c1 = new Complex(cpyArr[i, 0], cpyArr[i, 1]);
              //find the rotation angle from c1 to c2 when viewed from the origin
              var phaseDiff = Complex.Divide(c2, c1).Phase;
              //add the rotation angle to the sum
              sum += phaseDiff;
              //immediately exit the loop if the origin is on the edge of polygon or it is one of the vertices of the polygon
              if (Math.Abs(Math.Abs(phaseDiff) - Math.PI) < tolerance || c1.Magnitude < tolerance || c2.Magnitude < tolerance)
              
                  sum = Math.PI * 2;
                  break;
              
          
          return Math.Abs((Math.PI*2 ) - Math.Abs(sum)) < tolerance;
      
    

【讨论】:

您好,非常感谢您的回答!这个问题暗示了一个应该返回布尔值的函数,你介意更新你的答案吗?【参考方案11】:

我推荐 Kai Hormann(埃尔兰根大学)和 Alexander Agathos(雅典大学)撰写的这篇精彩的 15 页论文。它整合了所有最佳算法,并允许您检测缠绕和光线投射解决方案。

The Point in Polygon Problem for Arbitrary Polygons

该算法的实现很有趣,而且非常值得。然而,它是如此复杂,以至于我直接对它的任何部分都毫无意义。相反,我会坚持说,如果您想要最有效和最通用的算法,我敢肯定就是这样。

算法变得复杂,因为它是高度优化的,因此需要大量阅读才能理解和实现。然而,它结合了光线投射和缠绕数算法的优点,结果是一个数字同时提供了两个答案。如果结果大于零且为奇数,则该点被完全包含,但如果结果为偶数,则该点包含在折回自身的多边形部分中。

祝你好运。

【讨论】:

【参考方案12】:

这是一个老问题,但我优化了 Saeed 的答案:

    public static bool IsInPolygon(this List<Point> poly, Point point)
    
        var coef = poly.Skip(1).Select((p, i) =>
                                        (point.y - poly[i].y) * (p.x - poly[i].x)
                                      - (point.x - poly[i].x) * (p.y - poly[i].y));

        var coefNum = coef.GetEnumerator();

        if (coef.Any(p => p == 0))
            return true;

        int lastCoef = coefNum.Current,
            count = coef.Count();

        coefNum.MoveNext();

        do
        
            if (coefNum.Current - lastCoef < 0)
                return false;

            lastCoef = coefNum.Current;
        
        while (coefNum.MoveNext());

        return true;
    

使用 IEnumerators 和 IEnumerables。

【讨论】:

【参考方案13】:

如果您在画布上绘制形状,这是一种快速简便的解决方案。

    private void Canvas_MouseMove(object sender, MouseEventArgs e)

    if (e.OriginalSource is Polygon)
    
        //do something
    

“多边形”可以是 System.Windows.Shapes 中的任何形状。

【讨论】:

以上是关于多边形中的 C# 点的主要内容,如果未能解决你的问题,请参考以下文章

多边形特例中的点

离散点外包凸多边形生成算法(C#或者C++),要有详细代码和说明,最好有可运行的样例程序好的另外加分,急

C# 计算地图上某个坐标点的是否在多边形内

基于C#的多边形冲突检测

当测试点位于多边形边缘时,多边形算法中的点返回真

跪求!!用C#在ArcEngine环境下开发一个求“图形(包括各种不规则多边形)”最小外接圆面积的方法函数