C#指向多边形

时间:2010-11-22 06:59:22

标签: c# .net algorithm

我正在尝试确定一个点是否在多边形内。 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;
}

12 个答案:

答案 0 :(得分:55)

我在这里检查了代码并且都有问题。

最好的方法是:

    /// <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;
    }

答案 1 :(得分:26)

在我的项目中,接受的答案对我不起作用。我最终使用了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;
}

答案 2 :(得分:8)

请参阅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) ==&gt;创建一个新的列表,从1列表的位置poly开始,然后按 (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

答案 3 :(得分:7)

您可以使用光线投射算法。在维基百科页面中详细描述了Point in polygon problem

就像计算从外部到该点的光线接触多边形边界的次数一样简单。如果触摸偶数次,则该点在多边形之外。如果触及奇数次,则该点在内部。

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

答案 4 :(得分:3)

http://alienryderflex.com/polygon/提供完整算法和C代码 将它转换为c#/ winforms将是微不足道的。

答案 5 :(得分:3)

我的答案来自这里: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 }));

答案 6 :(得分:1)

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

The Point in Polygon Problem for Arbitrary Polygons

该算法实现起来很有意义,非常值得。然而,它是如此复杂,以至于我直接对它的任何部分都毫无意义。我会坚持说,如果你想要最有效和多功能的算法,我确信这就是它。

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

祝你好运。

答案 7 :(得分:1)

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) && (a.X <= point.X) && (point.X <= b.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;
}

答案 8 :(得分:1)

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

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

以下函数通过了我提出的测试(正方形,菱形,对角线交叉,总共124个测试),这些测试的点位于边,顶点以及内部和外部边沿以及顶点上。

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

  1. 检查多边形顶点是否等于该点
  2. 检查点是否在水平或垂直线上
  3. 计算(两倍)并收集相交并转换为整数
  4. 排序相交,因此边的顺序不影响算法
  5. 检查点是否在偶数相交处(等于-在多边形中)
  6. 检查点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 :(得分:1)

您真正需要的只有4行代码来实现绕组号方法。但是首先,请参考System.Numerics以使用复杂的库。下面的代码假定您已经翻译了一个点循环(存储在cpyArr中),以便您的候选点位于0,0。

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

  2. 现在这里是一些复数理论。将c1和c2视为向量的复数表示。要从向量c1到达向量c2,可以将c1乘以复数Z(c1 Z = c2)。 Z旋转c1使其指向c2。然后,它也会拉伸或挤压c1,使其与c2匹配。要获得如此神奇的数字Z,请将c2除以c1(因为c1 Z = c2,Z = c2 / c1)。您可以查找有关复数除法的高中笔记,也可以使用Microsoft提供的方法。得到这个数字后,我们真正关心的就是相位角。

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

  4. 我在“字面上”添加了一些小写字母。如果您的相位角为+/- pi,那么您就位于点对之间的边缘。在那种情况下,我会说这个点是该区域的一部分,并且会跳出循环

     public static bool IsOriginInPolygon(double[,] cpyArr){
     var sum = 0.0;
     var tolerance = 1e-4;
    
     for (int i = 0; i < cpyArr.GetLength(0)- 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;
     }
    

答案 10 :(得分:1)

对于使用 NET Core 的用户,可以从 NET Core 3.0 获得 Region.IsVisible。添加包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 毫秒)。但是,我还是更喜欢这个,因为少担心一件事。

答案 11 :(得分:0)

这是一个老问题,但是我优化了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。