简体   繁体   English

C# 多边形中的点

[英]C# Point in polygon

I'm trying to determine if a point is inside a polygon.我正在尝试确定一个点是否在多边形内。 the Polygon is defined by an array of Point objects. Polygon 由一组 Point 对象定义。 I can easily figure out if the point is inside the bounded box of the polygon, but I'm not sure how to tell if it's inside the actual polygon or not.我可以很容易地确定该点是否在多边形的有界框内,但我不确定如何判断它是否在实际多边形内。 If possible, I'd like to only use C# and WinForms.如果可能,我只想使用 C# 和 WinForms。 I'd rather not call on OpenGL or something to do this simple task.我不想拨打 OpenGL 或其他电话来完成这项简单的任务。

Here's the code I have so far:这是我到目前为止的代码:

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

I've checked codes here and all have problems.我已经检查过这里的代码,并且都有问题。

The best method is:最好的方法是:

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

The accepted answer did not work for me in my project.接受的答案在我的项目中对我不起作用。 I ended up using the code found 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;
}

See this it's in c++ and can be done in c# in a same way.看到是在 c++ 中,可以以相同的方式在 c# 中完成。

for convex polygon is too easy:对于凸多边形太容易了:

If the polygon is convex then one can consider the polygon as a "path" from the first vertex.如果多边形是凸的,则可以将多边形视为从第一个顶点开始的“路径”。 A point is on the interior of this polygons if it is always on the same side of all the line segments making up the path.如果点始终位于组成路径的所有线段的同一侧,则该点位于该多边形的内部。

Given a line segment between P0 (x0,y0) and P1 (x1,y1), another point P (x,y) has the following relationship to the line segment.给定 P0 (x0,y0) 和 P1 (x1,y1) 之间的线段,另一点 P (x,y) 与线段有以下关系。 Compute (y - y0) (x1 - x0) - (x - x0) (y1 - y0)计算 (y - y0) (x1 - x0) - (x - x0) (y1 - y0)

if it is less than 0 then P is to the right of the line segment, if greater than 0 it is to the left, if equal to 0 then it lies on the line segment.如果小于 0 则 P 位于线段的右侧,如果大于 0 则位于左侧,如果等于 0 则位于线段上。

Here is its code in c#, I didn't check edge cases.这是它在 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;
        }

I test it with simple rectangle works fine:我用简单的矩形测试它工作正常:

            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

Explanation on the linq query:关于 linq 查询的说明:

poly.Skip(1) ==> creates a new list started from position 1 of the poly list and then by (point.Y - poly[i].Y)*(pX - poly[i].X) - (point.X - poly[i].X) * (pY - poly[i].Y) we'll going to calculate the direction (which mentioned in referenced paragraph). poly.Skip(1) ==> 创建一个新列表,从poly列表的位置1开始,然后通过(point.Y - poly[i].Y)*(pX - poly[i].X) - (point.X - poly[i].X) * (pY - poly[i].Y)我们将计算方向(在参考段落中提到)。 similar example (with another operation):类似的例子(与另一个操作):

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

You can use the ray casting algorithm.您可以使用光线投射算法。 It is well-described in the wikipedia page for the Point in polygon problem .它在 wikipedia page for the Point in polygon problem 中有很好的描述。

It's as simple as counting the number of times a ray from outside to that point touches the polygon boundaries.就像计算从外部到该点的光线接触多边形边界的次数一样简单。 If it touches an even number of times, the point is outside the polygon.如果它接触偶数次,则该点在多边形之外。 If it touches an odd number of times, the point is inside.如果它接触奇数次,则该点在里面。

To count the number of times the ray touches, you check intersections between the ray and all of the polygon sides.要计算光线接触的次数,请检查光线与所有多边形边之间的交点。

meowNET anwser does not include Polygon vertices in the polygon and points exactly on horizontal edges. meowNET anwser 不包括多边形中的多边形顶点,并且精确地指向水平边缘。 If you need an exact "inclusive" algorithm:如果您需要一个精确的“包含”算法:

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

My answer is taken from here: Link我的回答来自这里: 链接

I took the C code and converted it to C# and made it work我拿了 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;
    }

You can test it with this example:你可以用这个例子来测试它:

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

Complete algorithm along with C code is available at http://alienryderflex.com/polygon/完整算法和 C 代码可在http://alienryderflex.com/polygon/ 获得
Converting it to c# / winforms would be trivial.将其转换为 c#/winforms 将是微不足道的。

My business critical implementation of PointInPolygon function working on integers (as OP seems to be using) is unit tested for horizontal, vertical and diagonal lines, points on the line are included in the test (function returns true).我对处理整数的 PointInPolygon 函数的关键业务实现(就像 OP 似乎正在使用的那样)针对水平线、垂直线和对角线进行了单元测试,测试中包含线上的点(函数返回 true)。

This seems to be an old question but all previous examples of tracing have some flaws: do not consider horizontal or vertical polygon lines, polygon boundary line or the order of edges (clockwise, counterclockwise).这似乎是一个老问题,但之前所有的追踪示例都有一些缺陷:不考虑水平或垂直多边形线、多边形边界线或边的顺序(顺时针、逆时针)。

The following function passes the tests I came up with (square, rhombus, diagonal cross, total 124 tests) with points on edges, vertices and just inside and outside edge and vertex.下面的函数通过了我提出的测试(正方形、菱形、对角线交叉、总共 124 个测试),在边缘、顶点以及内外边缘和顶点上都有点。

The code does the following for every consecutive pair of polygon coordinates:代码对每对连续的多边形坐标执行以下操作:

  1. Checks if polygon vertex equals the point检查多边形顶点是否等于点
  2. Checks if the point is on a horizontal or vertical line检查点是在水平线上还是垂直线上
  3. Calculates (as double) and collects intersects with conversion to integer计算(双精度)并收集相交并转换为整数
  4. Sorts intersects so the order of edges is not affecting the algorithm排序相交,所以边的顺序不影响算法
  5. Checks if the point is on the even intersect (equals - in polygon)检查点是否在偶数相交上(等于 - 在多边形中)
  6. Checks if the number of intersects before point x coordinate is odd - in polygon检查点 x 坐标之前的相交数是否为奇数 - 在多边形中

Algorithm can be easily adapted for floats and doubles if necessary.如有必要,算法可以很容易地适应浮点数和双数。

As a side note - I wonder how much software was created in the past nearly 10 years which check for a point in polygon and fail in some cases.作为旁注 - 我想知道在过去近 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;
    }

All you really need are 4 lines to implement the winding number method.您真正需要的只是 4 行代码来实现绕组数方法。 But first, reference the System.Numerics to use complex library.但首先,请参考 System.Numerics 以使用复杂库。 The code below assumes that you have translate a loop of points (stored in cpyArr) so that your candidate point stands at 0,0.下面的代码假设您已经翻译了一个点循环(存储在 cpyArr 中),以便您的候选点位于 0,0。

  1. For each point pair, create a complex number c1 using the first point and c2 using the 2nd point ( the first 2 lines within the loop)对于每个点对,使用第一个点创建一个复数 c1,使用第二个点创建一个复数 c2(循环中的前 2 条线)

  2. Now here is some complex number theory.现在这里是一些复数理论。 Think of c1 and c2 as complex number representation of vectors.将 c1 和 c2 视为向量的复数表示。 To get from vector c1 to vector c2, you can multiply c1 by a complex number Z (c1 Z=c2).要从向量 c1 到向量 c2,您可以将 c1 乘以复数 Z (c1 Z=c2)。 Z rotates c1 so that it points at c2. Z 旋转 c1 使其指向 c2。 Then it also stretches or squishes c1 so that it matces c2.然后它还会拉伸或挤压 c1,使其与 c2 匹配。 To get such a magical number Z, you divide c2 by c1 (since c1 Z=c2, Z=c2/c1).要得到这样一个神奇的数字 Z,您需要将 c2 除以 c1(因为 c1 Z=c2,Z=c2/c1)。 You can look up your high school notes on dividing complex number or use that method provided by Microsoft.您可以查找有关除法的高中笔记或使用 Microsoft 提供的该方法。 After you get that number, all we really care is the phase angle.得到这个数字后,我们真正关心的是相位角。

  3. To use the winding method, we add up all the phases and we should +/- 2 pi if the point is within the area.要使用缠绕方法,我们将所有相位相加,如果点在该区域内,我们应该 +/- 2 pi。 Otherwise, the sum should be 0否则,总和应为 0

  4. I added edge cases, 'literally'.我添加了边缘情况,“字面意思”。 If your phase angle is +/- pi, you're right on the edge between the points pair.如果您的相位角是 +/- pi,那么您就在点对之间的边缘。 In that case, I'd say the point is a part of the area and break out of the loop在那种情况下,我会说该点是该区域的一部分并跳出循环

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

For those using NET Core, Region.IsVisible is available from NET Core 3.0.对于使用 NET Core 的用户,可以从 NET Core 3.0 获得Region.IsVisible After adding package System.Drawing.Common ,添加包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);
        }
    }
}

Of lesser importance is that, adding System.Drawing.Common package increased size of publish folder by 400 KB.不太重要的是,添加 System.Drawing.Common 包将发布文件夹的大小增加了 400 KB。 Maybe compared to custom code, this implementation could also be slower (above function timed to be 18 ms on i7-8665u).也许与自定义代码相比,此实现也可能更慢(i7-8665u 上的上述函数时间为 18 毫秒)。 But still, I prefer this, for one less thing to worry about.但是,我还是更喜欢这个,因为少担心一件事。

I recommend this wonderful 15-page paper by Kai Hormann (University of Erlangen) and Alexander Agathos (University of Athens).我推荐 Kai Hormann(埃尔兰根大学)和 Alexander Agathos(雅典大学)撰写的这篇 15 页的精彩论文。 It consolidates all the best algorithms and will allow you to detect both winding and ray-casting solutions.它整合了所有最好的算法,并允许您检测缠绕和光线投射解决方案。

The Point in Polygon Problem for Arbitrary Polygons任意多边形的多边形问题的要点

The algorithm is interesting to implement, and well worth it.该算法实施起来很有趣,而且非常值得。 However, it is so complex that it is pointless for me to any portion of it directly.然而,它是如此复杂,以至于我对它的任何部分都没有直接的意义。 I'll instead stick with saying that if you want THE most efficient and versatile algorithm, I am certain this is it.我会坚持说,如果你想要最有效和最通用的算法,我敢肯定这就是它。

The algorithm gets complex because is is very highly optimized, so it will require a lot of reading to understand and implement.该算法变得复杂,因为它是高度优化的,因此需要大量阅读才能理解和实现。 However, it combines the benefits of both the ray-cast and winding number algorithms and the result is a single number that provides both answers at once.然而,它结合了光线投射和缠绕数算法的优点,结果是一个单一的数字,可以同时提供两个答案。 If the result is greater than zero and odd, then the point is completely contained, but if the result is an even number, then the point is contained in a section of the polygon that folds back on itself.如果结果大于零且为奇数,则该点被完全包含,但如果结果是偶数,则该点包含在多边形自身折叠的部分中。

Good luck.祝你好运。

This is an old question, but I optimized Saeed answer:这是一个老问题,但我优化了 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;
    }

Using IEnumerators and IEnumerables.使用 IEnumerators 和 IEnumerables。

If you are drawing Shapes on a Canvas this is a quick and easy Solution.如果您在画布上绘制形状,这是一个快速简便的解决方案。

    private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
    if (e.OriginalSource is Polygon)
    {
        //do something
    }
}

"Polygon" can be any shape from System.Windows.Shapes. “多边形”可以是 System.Windows.Shapes 中的任何形状。

Here's some modern C# code:这是一些现代的 C# 代码:

public record Point(double X, double Y);
public record Box(Point LowerLeft, Point UpperRight)
{
    public Box(Point[] points)
        : this(
                new Point(
                    points.Select(x => x.X).Min(),
                    points.Select(x => x.Y).Min()),
                new Point(
                    points.Select(x => x.X).Max(),
                    points.Select(x => x.Y).Max()))
    {
    }

    public bool ContainsPoint(Point point)
    {
        return point.X >= LowerLeft.X
            && point.X <= UpperRight.X
            && point.Y >= LowerLeft.Y
            && point.Y <= UpperRight.Y;
    }
}

public record Polygon(Point[] Points, Box Box)
{
    public Polygon(Point[] points)
        : this(points, new(points))
    {
    }

    public bool ContainsPoint(Point point)
    {
        do
        {
            if (Box.ContainsPoint(point) == false)
            {
                break;
            }

            bool result = false;
            int j = Points.Length - 1;
            for (int i = 0; i < Points.Length; i++)
            {
                if ((Points[i].Y < point.Y && Points[j].Y >= point.Y)
                    || (Points[j].Y < point.Y && Points[i].Y >= point.Y))
                {
                    if (Points[i].X +
                        ((point.Y - Points[i].Y) / (Points[j].Y - Points[i].Y) * (Points[j].X - Points[i].X))
                        < point.X)
                    {
                        result = !result;
                    }
                }

                j = i;
            }

            return result;
        }
        while (false);

        return false;
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM