如何确定2D点是否在多边形内?

时间:2008-10-20 05:20:49

标签: performance graphics collision-detection polygon point-in-polygon

我正在尝试在多边形算法中创建一个快速 2D点,用于命中测试(例如Polygon.contains(p:Point))。对于有效技术的建议将不胜感激。

39 个答案:

答案 0 :(得分:675)

答案 1 :(得分:560)

我认为以下代码是最佳解决方案(取自here):

int pnpoly(int nvert, float *vertx, float *verty, float testx, float testy)
{
  int i, j, c = 0;
  for (i = 0, j = nvert-1; i < nvert; j = i++) {
    if ( ((verty[i]>testy) != (verty[j]>testy)) &&
     (testx < (vertx[j]-vertx[i]) * (testy-verty[i]) / (verty[j]-verty[i]) + vertx[i]) )
       c = !c;
  }
  return c;
}

参数

  • nvert :多边形中的顶点数。是否在上面提到的文章中讨论了最后是否重复第一个顶点。
  • vertx,verty :包含多边形顶点的x坐标和y坐标的数组。
  • testx,testy :测试点的X坐标和y坐标。

它既短又高效,适用于凸多边形和凹多边形。如前所述,您应首先检查边界矩形并分别处理多边形孔。

这背后的想法非常简单。作者将其描述如下:

  

我从测试点水平运行半无限光线(增加x,固定y),并计算它穿过的边数。在每个交叉点,射线在内部和外部之间切换。这被称为乔丹曲线定理。

每次水平光线穿过任何边缘时,变量c从0切换到1,从1切换到0。所以基本上它是跟踪交叉的边数是偶数还是奇数。 0表示偶数,1表示奇数。

答案 2 :(得分:61)

以下是answer given by nirg的C#版本,来自this RPI professor。请注意,使用该RPI源代码需要归因。

顶部添加了边界框检查。但是,正如James Brown指出的那样,主代码几乎与边界框检查本身一样快,因此边界框检查实际上可以减慢整体操作,如果您检查的大多数点都在边界框内。因此,您可以将边界框退出,或者如果它们不经常更改形状,则可以预先计算多边形的边界框。

public bool IsPointInPolygon( Point p, Point[] polygon )
{
    double minX = polygon[ 0 ].X;
    double maxX = polygon[ 0 ].X;
    double minY = polygon[ 0 ].Y;
    double maxY = polygon[ 0 ].Y;
    for ( int i = 1 ; i < polygon.Length ; i++ )
    {
        Point q = polygon[ i ];
        minX = Math.Min( q.X, minX );
        maxX = Math.Max( q.X, maxX );
        minY = Math.Min( q.Y, minY );
        maxY = Math.Max( q.Y, maxY );
    }

    if ( p.X < minX || p.X > maxX || p.Y < minY || p.Y > maxY )
    {
        return false;
    }

    // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
    bool inside = false;
    for ( int i = 0, j = polygon.Length - 1 ; i < polygon.Length ; j = i++ )
    {
        if ( ( polygon[ i ].Y > p.Y ) != ( polygon[ j ].Y > p.Y ) &&
             p.X < ( polygon[ j ].X - polygon[ i ].X ) * ( p.Y - polygon[ i ].Y ) / ( polygon[ j ].Y - polygon[ i ].Y ) + polygon[ i ].X )
        {
            inside = !inside;
        }
    }

    return inside;
}

答案 3 :(得分:43)

以下是M. Katz基于Nirg方法的答案的JavaScript变体:

function pointIsInPoly(p, polygon) {
    var isInside = false;
    var minX = polygon[0].x, maxX = polygon[0].x;
    var minY = polygon[0].y, maxY = polygon[0].y;
    for (var n = 1; n < polygon.length; n++) {
        var q = polygon[n];
        minX = Math.min(q.x, minX);
        maxX = Math.max(q.x, maxX);
        minY = Math.min(q.y, minY);
        maxY = Math.max(q.y, maxY);
    }

    if (p.x < minX || p.x > maxX || p.y < minY || p.y > maxY) {
        return false;
    }

    var i = 0, j = polygon.length - 1;
    for (i, j; i < polygon.length; j = i++) {
        if ( (polygon[i].y > p.y) != (polygon[j].y > p.y) &&
                p.x < (polygon[j].x - polygon[i].x) * (p.y - polygon[i].y) / (polygon[j].y - polygon[i].y) + polygon[i].x ) {
            isInside = !isInside;
        }
    }

    return isInside;
}

答案 4 :(得分:28)

计算点p与每个多边形顶点之间的方向角度和。如果总取向角度是360度,则该点在内部。如果总数为0,则该点在外面。

我更喜欢这种方法,因为它更稳健,更少依赖于数值精度。

计算交叉点数均匀度的方法是有限的,因为在计算交叉点数量时你可以“击中”一个顶点。

EDIT:By The Way,这种方法适用于凹凸多边形。

编辑:我最近在这个主题上发现了一个完整的Wikipedia article

答案 5 :(得分:18)

bobobobo引用的Eric Haines article非常出色。特别有趣的是比较算法性能的表格;角度求和方法与其他方法相比非常糟糕。同样有趣的是,使用查找网格进一步将多边形细分为“in”和“out”扇区的优化可以使测试速度极快,即使在具有&gt;的多边形上也是如此。 1000方面。

无论如何,这是早期的,但我的投票是针对“交叉”方法,这正是Mecki所描述的。但是我发现它最为谦逊described and codified by David Bourke。我喜欢不需要真正的三角函数,它适用于凸面和凹面,并且随着边数的增加,它的表现也相当不错。

顺便说一句,这是Eric Haines的文章中关于随机多边形测试的性能表之一。

                       number of edges per polygon
                         3       4      10      100    1000
MacMartin               2.9     3.2     5.9     50.6    485
Crossings               3.1     3.4     6.8     60.0    624
Triangle Fan+edge sort  1.1     1.8     6.5     77.6    787
Triangle Fan            1.2     2.1     7.3     85.4    865
Barycentric             2.1     3.8    13.8    160.7   1665
Angle Summation        56.2    70.4   153.6   1403.8  14693

Grid (100x100)          1.5     1.5     1.6      2.1      9.8
Grid (20x20)            1.7     1.7     1.9      5.7     42.2
Bins (100)              1.8     1.9     2.7     15.1    117
Bins (20)               2.1     2.2     3.7     26.3    278

答案 6 :(得分:17)

这个问题非常有趣。我有另一个可行的想法,不同于这篇文章的其他答案。

这个想法是使用角度之和来决定目标是在内部还是外部。如果目标位于区域内,则目标和每两个边界点形成的角度之和将为360.如果目标位于外部,则总和将不是360.角度具有方向。如果角度向后,角度是负角度。这就像计算winding number

一样

请参阅此图片以基本了解该想法: enter image description here

我的算法假设顺时针是正方向。这是一个潜在的输入:

[[-122.402015, 48.225216], [-117.032049, 48.999931], [-116.919132, 45.995175], [-124.079107, 46.267259], [-124.717175, 48.377557], [-122.92315, 47.047963], [-122.402015, 48.225216]]

以下是实现这个想法的python代码:

def isInside(self, border, target):
degree = 0
for i in range(len(border) - 1):
    a = border[i]
    b = border[i + 1]

    # calculate distance of vector
    A = getDistance(a[0], a[1], b[0], b[1]);
    B = getDistance(target[0], target[1], a[0], a[1])
    C = getDistance(target[0], target[1], b[0], b[1])

    # calculate direction of vector
    ta_x = a[0] - target[0]
    ta_y = a[1] - target[1]
    tb_x = b[0] - target[0]
    tb_y = b[1] - target[1]

    cross = tb_y * ta_x - tb_x * ta_y
    clockwise = cross < 0

    # calculate sum of angles
    if(clockwise):
        degree = degree + math.degrees(math.acos((B * B + C * C - A * A) / (2.0 * B * C)))
    else:
        degree = degree - math.degrees(math.acos((B * B + C * C - A * A) / (2.0 * B * C)))

if(abs(round(degree) - 360) <= 3):
    return True
return False

答案 7 :(得分:8)

answer by nirg的快速版本:

extension CGPoint {
    func isInsidePolygon(vertices: [CGPoint]) -> Bool {
        guard !vertices.isEmpty else { return false }
        var j = vertices.last!, c = false
        for i in vertices {
            let a = (i.y > y) != (j.y > y)
            let b = (x < (j.x - i.x) * (y - i.y) / (j.y - i.y) + i.x)
            if a && b { c = !c }
            j = i
        }
        return c
    }
}

答案 8 :(得分:7)

当我在Michael Stonebraker下担任研究员时,我做了一些工作 - 你知道,教授提出了IngresPostgreSQL等等。

我们意识到最快的方法是首先做一个边界框,因为它的速度非常快。如果它在边界框之外,它就在外面。否则,你会做更努力的工作......

如果你想要一个很好的算法,请查看开源项目PostgreSQL源代码以进行地理工作......

我想指出,我们从来没有对左右手的任何见解(也可以表达为“内部”与“外部”问题......


更新

BKB的链接提供了大量合理的算法。我正在研究地球科学问题,因此需要一个在纬度/经度上工作的解决方案,它有一个特殊的手性问题 - 是较小区域内还是较大区域内的区域?答案是顶点的“方向”很重要 - 它既可以是左手也可以是右手,这样你就可以将任一区域指定为任何给定多边形的“内部”。因此,我的工作使用了该页面上列举的解决方案三。

此外,我的工作使用了“在线”测试的单独功能。

...自从有人问:我们发现当顶点数量超过一定数量时,边界框测试最好 - 如果需要,在进行更长时间的测试之前做一个非常快速的测试......边界框是由简单地取最大x,最小x,最大y和最小y并将它们组合在一起制作一个盒子的四个点......

接下来的其他提示:我们在网格空间中进行了所有更复杂和“光线调暗”的计算,所有这些计算都在平面上的正点上,然后重新投射回“真实的”经度/纬度,从而避免当经过一条经度线180和处理极地区域时可能出现错误。工作得很好!

答案 9 :(得分:7)

非常喜欢Nirg发布并由bobobobo编辑的解决方案。我只是让它变得友好,并且对我的使用更加清晰:

function insidePoly(poly, pointx, pointy) {
    var i, j;
    var inside = false;
    for (i = 0, j = poly.length - 1; i < poly.length; j = i++) {
        if(((poly[i].y > pointy) != (poly[j].y > pointy)) && (pointx < (poly[j].x-poly[i].x) * (pointy-poly[i].y) / (poly[j].y-poly[i].y) + poly[i].x) ) inside = !inside;
    }
    return inside;
}

答案 10 :(得分:5)

David Segond的答案几乎是标准的一般答案,而Richard T是最常见的优化,尽管其他一些也是如此。其他强有力的优化基于不太通用的解决方案。例如,如果要检查具有大量点的相同多边形,对多边形进行三角测量可以大大加快速度,因为有许多非常快速的TIN搜索算法。另一个是如果多边形和点在低分辨率的有限平面上,比如屏幕显示,您可以将多边形绘制到给定颜色的内存映射显示缓冲区中,并检查给定像素的颜色以查看它是否位于在多边形中。

与许多优化一样,这些优化基于特定情况而非一般情况,并根据摊销时间而非单次使用产生收益。

在这个领域工作,我发现Joeseph O'Rourkes的计算几何在C'ISBN 0-521-44034-3中是一个很好的帮助。

答案 11 :(得分:3)

nirg的Obj-C版本的答案与测试点的示例方法。 Nirg的回答对我很有用。

- (BOOL)isPointInPolygon:(NSArray *)vertices point:(CGPoint)test {
    NSUInteger nvert = [vertices count];
    NSInteger i, j, c = 0;
    CGPoint verti, vertj;

    for (i = 0, j = nvert-1; i < nvert; j = i++) {
        verti = [(NSValue *)[vertices objectAtIndex:i] CGPointValue];
        vertj = [(NSValue *)[vertices objectAtIndex:j] CGPointValue];
        if (( (verti.y > test.y) != (vertj.y > test.y) ) &&
        ( test.x < ( vertj.x - verti.x ) * ( test.y - verti.y ) / ( vertj.y - verti.y ) + verti.x) )
            c = !c;
    }

    return (c ? YES : NO);
}

- (void)testPoint {

    NSArray *polygonVertices = [NSArray arrayWithObjects:
        [NSValue valueWithCGPoint:CGPointMake(13.5, 41.5)],
        [NSValue valueWithCGPoint:CGPointMake(42.5, 56.5)],
        [NSValue valueWithCGPoint:CGPointMake(39.5, 69.5)],
        [NSValue valueWithCGPoint:CGPointMake(42.5, 84.5)],
        [NSValue valueWithCGPoint:CGPointMake(13.5, 100.0)],
        [NSValue valueWithCGPoint:CGPointMake(6.0, 70.5)],
        nil
    ];

    CGPoint tappedPoint = CGPointMake(23.0, 70.0);

    if ([self isPointInPolygon:polygonVertices point:tappedPoint]) {
        NSLog(@"YES");
    } else {
        NSLog(@"NO");
    }
}

sample polygon

答案 12 :(得分:3)

简单的解决方案是将多边形划分为三角形并按照解释的方式测试三角形here

如果你的多边形是 CONVEX ,那么可能会有更好的方法。将多边形视为无限线的集合。每条线将空间分成两部分。对于每一点,很容易说它是在线的一侧还是另一侧。如果一个点位于所有线的同一侧,则它位于多边形内。

答案 13 :(得分:3)

我意识到这是旧的,但这是一个在Cocoa中实现的光线投射算法,以防任何人感兴趣。不确定这是最有效的做事方式,但它可以帮助别人。

- (BOOL)shape:(NSBezierPath *)path containsPoint:(NSPoint)point
{
    NSBezierPath *currentPath = [path bezierPathByFlatteningPath];
    BOOL result;
    float aggregateX = 0; //I use these to calculate the centroid of the shape
    float aggregateY = 0;
    NSPoint firstPoint[1];
    [currentPath elementAtIndex:0 associatedPoints:firstPoint];
    float olderX = firstPoint[0].x;
    float olderY = firstPoint[0].y;
    NSPoint interPoint;
    int noOfIntersections = 0;

    for (int n = 0; n < [currentPath elementCount]; n++) {
        NSPoint points[1];
        [currentPath elementAtIndex:n associatedPoints:points];
        aggregateX += points[0].x;
        aggregateY += points[0].y;
    }

    for (int n = 0; n < [currentPath elementCount]; n++) {
        NSPoint points[1];

        [currentPath elementAtIndex:n associatedPoints:points];
        //line equations in Ax + By = C form
        float _A_FOO = (aggregateY/[currentPath elementCount]) - point.y;  
        float _B_FOO = point.x - (aggregateX/[currentPath elementCount]);
        float _C_FOO = (_A_FOO * point.x) + (_B_FOO * point.y);

        float _A_BAR = olderY - points[0].y;
        float _B_BAR = points[0].x - olderX;
        float _C_BAR = (_A_BAR * olderX) + (_B_BAR * olderY);

        float det = (_A_FOO * _B_BAR) - (_A_BAR * _B_FOO);
        if (det != 0) {
            //intersection points with the edges
            float xIntersectionPoint = ((_B_BAR * _C_FOO) - (_B_FOO * _C_BAR)) / det;
            float yIntersectionPoint = ((_A_FOO * _C_BAR) - (_A_BAR * _C_FOO)) / det;
            interPoint = NSMakePoint(xIntersectionPoint, yIntersectionPoint);
            if (olderX <= points[0].x) {
                //doesn't matter in which direction the ray goes, so I send it right-ward.
                if ((interPoint.x >= olderX && interPoint.x <= points[0].x) && (interPoint.x > point.x)) {  
                    noOfIntersections++;
                }
            } else {
                if ((interPoint.x >= points[0].x && interPoint.x <= olderX) && (interPoint.x > point.x)) {
                     noOfIntersections++;
                } 
            }
        }
        olderX = points[0].x;
        olderY = points[0].y;
    }
    if (noOfIntersections % 2 == 0) {
        result = FALSE;
    } else {
        result = TRUE;
    }
    return result;
}

答案 14 :(得分:3)

我也认为360求和只适用于凸多边形,但事实并非如此。

这个网站有一个很好的图表,显示了这一点,以及对命中测试的一个很好的解释: Gamasutra - Crashing into the New Year: Collision Detection

答案 15 :(得分:2)

没有什么比问题的归纳定义更美妙了。为了完整起见,你在prolog中有一个版本,它也可以澄清 ray casting 背后的背景:

基于http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html

中简单算法的模拟

一些助手谓词:

exor(A,B):- \+A,B;A,\+B.
in_range(Coordinate,CA,CB) :- exor((CA>Coordinate),(CB>Coordinate)).

inside(false).
inside(_,[_|[]]).
inside(X:Y, [X1:Y1,X2:Y2|R]) :- in_range(Y,Y1,Y2), X > ( ((X2-X1)*(Y-Y1))/(Y2-Y1) +      X1),toggle_ray, inside(X:Y, [X2:Y2|R]); inside(X:Y, [X2:Y2|R]).

get_line(_,_,[]).
get_line([XA:YA,XB:YB],[X1:Y1,X2:Y2|R]):- [XA:YA,XB:YB]=[X1:Y1,X2:Y2]; get_line([XA:YA,XB:YB],[X2:Y2|R]).

给出2点A和B(线(A,B))的线的等式是:

                    (YB-YA)
           Y - YA = ------- * (X - XA) 
                    (XB-YB) 

线的旋转方向很重要 设置为时钟方式的边界和反时钟方式的孔。 我们要检查点(X,Y),即测试点是否在左边 我们行的半平面(这是一个品味的问题,它也可能是 右边,但也必须改变边界线的方向 这种情况),这是从点到右(或左)投射光线 并确认与线的交叉点。我们选择投射 光线在水平方向(再次是味道的问题, 它也可以在垂直方向上完成,具有类似的限制),所以我们有:

               (XB-XA)
           X < ------- * (Y - YA) + XA
               (YB-YA) 

现在我们需要知道该点是否位于左侧(或右侧) 只有线段,而不是整个平面,所以我们需要 将搜索仅限制为此段,但这很容易 在该段内只有一个点可以更高 垂直轴上的Y比。因为这是一个更强的限制 需要成为第一个检查,所以我们首先只采取那些线 满足此要求,然后检查其可能性。由约旦 曲线定理投射到多边形的任何光线必须在a处相交 偶数行。所以我们完成了,我们将光线投射到 正确,然后每次它与一条线相交,切换它的状态。 但是在我们的实现中,我们要检查它的长度 满足给定限制的解决方案包并决定 坚持它。对于多边形中的每一行,必须这样做。

is_left_half_plane(_,[],[],_).
is_left_half_plane(X:Y,[XA:YA,XB:YB], [[X1:Y1,X2:Y2]|R], Test) :- [XA:YA, XB:YB] =  [X1:Y1, X2:Y2], call(Test, X , (((XB - XA) * (Y - YA)) / (YB - YA) + XA)); 
                                                        is_left_half_plane(X:Y, [XA:YA, XB:YB], R, Test).

in_y_range_at_poly(Y,[XA:YA,XB:YB],Polygon) :- get_line([XA:YA,XB:YB],Polygon),  in_range(Y,YA,YB).
all_in_range(Coordinate,Polygon,Lines) :- aggregate(bag(Line),    in_y_range_at_poly(Coordinate,Line,Polygon), Lines).

traverses_ray(X:Y, Lines, Count) :- aggregate(bag(Line), is_left_half_plane(X:Y, Line, Lines, <), IntersectingLines), length(IntersectingLines, Count).

% This is the entry point predicate
inside_poly(X:Y,Polygon,Answer) :- all_in_range(Y,Polygon,Lines), traverses_ray(X:Y, Lines, Count), (1 is mod(Count,2)->Answer=inside;Answer=outside).

答案 16 :(得分:2)

Java版本:

public class Geocode {
    private float latitude;
    private float longitude;

    public Geocode() {
    }

    public Geocode(float latitude, float longitude) {
        this.latitude = latitude;
        this.longitude = longitude;
    }

    public float getLatitude() {
        return latitude;
    }

    public void setLatitude(float latitude) {
        this.latitude = latitude;
    }

    public float getLongitude() {
        return longitude;
    }

    public void setLongitude(float longitude) {
        this.longitude = longitude;
    }
}

public class GeoPolygon {
    private ArrayList<Geocode> points;

    public GeoPolygon() {
        this.points = new ArrayList<Geocode>();
    }

    public GeoPolygon(ArrayList<Geocode> points) {
        this.points = points;
    }

    public GeoPolygon add(Geocode geo) {
        points.add(geo);
        return this;
    }

    public boolean inside(Geocode geo) {
        int i, j;
        boolean c = false;
        for (i = 0, j = points.size() - 1; i < points.size(); j = i++) {
            if (((points.get(i).getLongitude() > geo.getLongitude()) != (points.get(j).getLongitude() > geo.getLongitude())) &&
                    (geo.getLatitude() < (points.get(j).getLatitude() - points.get(i).getLatitude()) * (geo.getLongitude() - points.get(i).getLongitude()) / (points.get(j).getLongitude() - points.get(i).getLongitude()) + points.get(i).getLatitude()))
                c = !c;
        }
        return c;
    }

}

答案 17 :(得分:2)

nirg答案的C#版本在这里:我将只分享代码。这可能会节省一些时间。

public static bool IsPointInPolygon(IList<Point> polygon, Point 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;
        }

答案 18 :(得分:1)

我已经制作了nirg's c ++ code

的Python实现

输入

  • bounding_points:组成多边形的节点。
  • bounding_box_positions:要过滤的候选点。 (在我从边界框创建的实现中。

    (输入是元组列表,格式为:[(xcord, ycord), ...]

返回

  • 多边形内的所有点。
def polygon_ray_casting(self, bounding_points, bounding_box_positions):
    # Arrays containing the x- and y-coordinates of the polygon's vertices.
    vertx = [point[0] for point in bounding_points]
    verty = [point[1] for point in bounding_points]
    # Number of vertices in the polygon
    nvert = len(bounding_points)
    # Points that are inside
    points_inside = []

    # For every candidate position within the bounding box
    for idx, pos in enumerate(bounding_box_positions):
        testx, testy = (pos[0], pos[1])
        c = 0
        for i in range(0, nvert):
            j = i - 1 if i != 0 else nvert - 1
            if( ((verty[i] > testy ) != (verty[j] > testy))   and
                    (testx < (vertx[j] - vertx[i]) * (testy - verty[i]) / (verty[j] - verty[i]) + vertx[i]) ):
                c += 1
        # If odd, that means that we are inside the polygon
        if c % 2 == 1: 
            points_inside.append(pos)


    return points_inside

同样,这个想法取自here

答案 19 :(得分:1)

VBA VERSION:

注意:请记住,如果您的多边形是地图中的一个区域,纬度/经度是Y / X值而不是X / Y(纬度= Y,经度= X),因为根据我的理解,历史意义来自于当经度不是衡量标准的时候。

CLASS MODULE:CPoint

Private pXValue As Double
Private pYValue As Double

'''''X Value Property'''''

Public Property Get X() As Double
    X = pXValue
End Property

Public Property Let X(Value As Double)
    pXValue = Value
End Property

'''''Y Value Property'''''

Public Property Get Y() As Double
    Y = pYValue
End Property

Public Property Let Y(Value As Double)
    pYValue = Value
End Property

MODULE:

Public Function isPointInPolygon(p As CPoint, polygon() As CPoint) As Boolean

    Dim i As Integer
    Dim j As Integer
    Dim q As Object
    Dim minX As Double
    Dim maxX As Double
    Dim minY As Double
    Dim maxY As Double
    minX = polygon(0).X
    maxX = polygon(0).X
    minY = polygon(0).Y
    maxY = polygon(0).Y

    For i = 1 To UBound(polygon)
        Set q = polygon(i)
        minX = vbMin(q.X, minX)
        maxX = vbMax(q.X, maxX)
        minY = vbMin(q.Y, minY)
        maxY = vbMax(q.Y, maxY)
    Next i

    If p.X < minX Or p.X > maxX Or p.Y < minY Or p.Y > maxY Then
        isPointInPolygon = False
        Exit Function
    End If


    ' SOURCE: http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html

    isPointInPolygon = False
    i = 0
    j = UBound(polygon)

    Do While i < UBound(polygon) + 1
        If (polygon(i).Y > p.Y) Then
            If (polygon(j).Y < p.Y) Then
                If p.X < (polygon(j).X - polygon(i).X) * (p.Y - polygon(i).Y) / (polygon(j).Y - polygon(i).Y) + polygon(i).X Then
                    isPointInPolygon = True
                    Exit Function
                End If
            End If
        ElseIf (polygon(i).Y < p.Y) Then
            If (polygon(j).Y > p.Y) Then
                If p.X < (polygon(j).X - polygon(i).X) * (p.Y - polygon(i).Y) / (polygon(j).Y - polygon(i).Y) + polygon(i).X Then
                    isPointInPolygon = True
                    Exit Function
                End If
            End If
        End If
        j = i
        i = i + 1
    Loop   
End Function

Function vbMax(n1, n2) As Double
    vbMax = IIf(n1 > n2, n1, n2)
End Function

Function vbMin(n1, n2) As Double
    vbMin = IIf(n1 > n2, n2, n1)
End Function


Sub TestPointInPolygon()

    Dim i As Integer
    Dim InPolygon As Boolean

'   MARKER Object
    Dim p As CPoint
    Set p = New CPoint
    p.X = <ENTER X VALUE HERE>
    p.Y = <ENTER Y VALUE HERE>

'   POLYGON OBJECT
    Dim polygon() As CPoint
    ReDim polygon(<ENTER VALUE HERE>) 'Amount of vertices in polygon - 1
    For i = 0 To <ENTER VALUE HERE> 'Same value as above
       Set polygon(i) = New CPoint
       polygon(i).X = <ASSIGN X VALUE HERE> 'Source a list of values that can be looped through
       polgyon(i).Y = <ASSIGN Y VALUE HERE> 'Source a list of values that can be looped through
    Next i

    InPolygon = isPointInPolygon(p, polygon)
    MsgBox InPolygon

End Sub

答案 20 :(得分:1)

这可能是源自herefrom this page的C代码的优化版本。

我的C ++版本使用std::vector<std::pair<double, double>>和两个double作为x和y。逻辑应该与原始C代码完全相同,但是我发现我的代码更易于阅读。我不能代表表演。

bool point_in_poly(std::vector<std::pair<double, double>>& verts, double point_x, double point_y)
{
    bool in_poly = false;
    auto num_verts = verts.size();
    for (int i = 0, j = num_verts - 1; i < num_verts; j = i++) {
        double x1 = verts[i].first;
        double y1 = verts[i].second;
        double x2 = verts[j].first;
        double y2 = verts[j].second;

        if (((y1 > point_y) != (y2 > point_y)) &&
            (point_x < (x2 - x1) * (point_y - y1) / (y2 - y1) + x1))
            in_poly = !in_poly;
    }
    return in_poly;
}

原始的C代码是

int pnpoly(int nvert, float *vertx, float *verty, float testx, float testy)
{
  int i, j, c = 0;
  for (i = 0, j = nvert-1; i < nvert; j = i++) {
    if ( ((verty[i]>testy) != (verty[j]>testy)) &&
     (testx < (vertx[j]-vertx[i]) * (testy-verty[i]) / (verty[j]-verty[i]) + vertx[i]) )
       c = !c;
  }
  return c;
}

答案 21 :(得分:1)

.Net端口:

    static void Main(string[] args)
    {

        Console.Write("Hola");
        List<double> vertx = new List<double>();
        List<double> verty = new List<double>();

        int i, j, c = 0;

        vertx.Add(1);
        vertx.Add(2);
        vertx.Add(1);
        vertx.Add(4);
        vertx.Add(4);
        vertx.Add(1);

        verty.Add(1);
        verty.Add(2);
        verty.Add(4);
        verty.Add(4);
        verty.Add(1);
        verty.Add(1);

        int nvert = 6;  //Vértices del poligono

        double testx = 2;
        double testy = 5;


        for (i = 0, j = nvert - 1; i < nvert; j = i++)
        {
            if (((verty[i] > testy) != (verty[j] > testy)) &&
             (testx < (vertx[j] - vertx[i]) * (testy - verty[i]) / (verty[j] - verty[i]) + vertx[i]))
                c = 1;
        }
    }

答案 22 :(得分:0)

此问题中的大多数答案不能很好地处理所有极端情况。一些微妙的情况如下: ray casting corner cases 这是一个JavaScript版本,所有案例都处理得很好。

/** Get relationship between a point and a polygon using ray-casting algorithm
 * @param {{x:number, y:number}} P: point to check
 * @param {{x:number, y:number}[]} polygon: the polygon
 * @returns -1: outside, 0: on edge, 1: inside
 */
function relationPP(P, polygon) {
    const between = (p, a, b) => p >= a && p <= b || p <= a && p >= b
    let inside = false
    for (let i = polygon.length-1, j = 0; j < polygon.length; i = j, j++) {
        const A = polygon[i]
        const B = polygon[j]
        // corner cases
        if (P.x == A.x && P.y == A.y || P.x == B.x && P.y == B.y) return 0
        if (A.y == B.y && P.y == A.y && between(P.x, A.x, B.x)) return 0

        if (between(P.y, A.y, B.y)) { // if P inside the vertical range
            // filter out "ray pass vertex" problem by treating the line a little lower
            if (P.y == A.y && B.y >= A.y || P.y == B.y && A.y >= B.y) continue
            // calc cross product `PA X PB`, P lays on left side of AB if c > 0 
            const c = (A.x - P.x) * (B.y - P.y) - (B.x - P.x) * (A.y - P.y)
            if (c == 0) return 0
            if ((A.y < B.y) == (c > 0)) inside = !inside
        }
    }

    return inside? 1 : -1
}

答案 23 :(得分:0)

为完整起见,这是nirg提供并由Mecki讨论的算法的lua实现:

function pnpoly(area, test)
    local inside = false
    local tx, ty = table.unpack(test)
    local j = #area
    for i=1, #area do
        local vxi, vyi = table.unpack(area[i])
        local vxj, vyj = table.unpack(area[j])
        if (vyi > ty) ~= (vyj > ty)
        and tx < (vxj - vxi)*(ty - vyi)/(vyj - vyi) + vxi
        then
            inside = not inside
        end
        j = i
    end
    return inside
end

变量area是一个点表,这些点又存储为2D表。示例:

> A = {{2, 1}, {1, 2}, {15, 3}, {3, 4}, {5, 3}, {4, 1.5}}
> T = {2, 1.1}
> pnpoly(A, T)
true

GitHub Gist的link

答案 24 :(得分:0)

如果您使用的是Google Map SDK,并且想检查点是否在多边形内,则可以尝试使用GMSGeometryContainsLocation。它很棒!!这是这样的,

if GMSGeometryContainsLocation(point, polygon, true) {
    print("Inside this polygon.")
} else {
    print("outside this polygon")
}

这里是参考:https://developers.google.com/maps/documentation/ios-sdk/reference/group___geometry_utils#gaba958d3776d49213404af249419d0ffd

答案 25 :(得分:0)

这似乎可以在R中使用(抱歉,很抱歉,希望看到更好的版本!)。

pnpoly <- function(nvert,vertx,verty,testx,testy){
          c <- FALSE
          j <- nvert 
          for (i in 1:nvert){
              if( ((verty[i]>testy) != (verty[j]>testy)) && 
   (testx < (vertx[j]-vertx[i])*(testy-verty[i])/(verty[j]-verty[i])+vertx[i]))
            {c <- !c}
             j <- i}
   return(c)}

答案 26 :(得分:0)

from typing import Iterable

def pnpoly(verts, x, y):
    #check if x and/or y is iterable
    xit, yit = isinstance(x, Iterable), isinstance(y, Iterable)
    #if not iterable, make an iterable of length 1
    X = x if xit else (x, )
    Y = y if yit else (y, )
    #store verts length as a range to juggle j
    r = range(len(verts))
    #final results if x or y is iterable
    results = []
    #traverse x and y coordinates
    for xp in X:
        for yp in Y:
            c = 0 #reset c at every new position
            for i in r:
                j = r[i-1] #set j to position before i
                #store a few arguments to shorten the if statement
                yneq       = (verts[i][1] > yp) != (verts[j][1] > yp)
                xofs, yofs = (verts[j][0] - verts[i][0]), (verts[j][1] - verts[i][1])
                #if we have crossed a line, increment c
                if (yneq and (xp < xofs * (yp - verts[i][1]) / yofs + verts[i][0])):
                    c += 1
            #if c is odd store the coordinates        
            if c%2:
                results.append((xp, yp))
    #return either coordinates or a bool, depending if x or y was an iterable
    return results if (xit or yit) else bool(c%2)

这个python版本是通用的。您可以为 True/False 结果输入单个 x 和单个 y 值,也可以使用 range 表示 xy 来遍历整个点网格。如果使用范围,则返回所有 list 点的 True x/y 对。 vertices 参数需要 x/y 对的二维 Iterable,例如:[(x1,y1), (x2,y2), ...]

示例用法:

vertices = [(25,25), (75,25), (75,75), (25,75)]
pnpoly(vertices, 50, 50) #True
pnpoly(vertices, range(100), range(100)) #[(25,25), (25,26), (25,27), ...]

实际上,即使这些也可以。

pnpoly(vertices, 50, range(100)) #check 0 to 99 y at x of 50
pnpoly(vertices, range(100), 50) #check 0 to 99 x at y of 50

答案 27 :(得分:0)

令人惊讶的是,没有人提过这个问题,但是对于需要数据库的实用主义者来说:MongoDB对包括此在内的Geo查询提供了出色的支持。

您正在寻找的是:

  

db.neighborhoods.findOne({几何:{$ geoIntersects:{$ geometry:{   类型:“点”,坐标:[“经度”,“纬度”]}}}   })

Neighborhoods是一个以标准GeoJson格式存储一个或多个多边形的集合。如果查询返回null,则不相交。

这里有很好的记录: https://docs.mongodb.com/manual/tutorial/geospatial-tutorial/

在330个不规则多边形网格中分类的6,000多个点的性能不到一分钟,根本没有优化,包括用各自的多边形更新文档的时间。

答案 28 :(得分:0)

这是@nirg答案的golang版本(受@@ m-katz的C#代码启发)

func isPointInPolygon(polygon []point, testp point) bool {
    minX := polygon[0].X
    maxX := polygon[0].X
    minY := polygon[0].Y
    maxY := polygon[0].Y

    for _, p := range polygon {
        minX = min(p.X, minX)
        maxX = max(p.X, maxX)
        minY = min(p.Y, minY)
        maxY = max(p.Y, maxY)
    }

    if testp.X < minX || testp.X > maxX || testp.Y < minY || testp.Y > maxY {
        return false
    }

    inside := false
    j := len(polygon) - 1
    for i := 0; i < len(polygon); i++ {
        if (polygon[i].Y > testp.Y) != (polygon[j].Y > testp.Y) && testp.X < (polygon[j].X-polygon[i].X)*(testp.Y-polygon[i].Y)/(polygon[j].Y-polygon[i].Y)+polygon[i].X {
            inside = !inside
        }
        j = i
    }

    return inside
}

答案 29 :(得分:0)

另一个 numpyic 实现,我认为它是迄今为止所有答案中最简洁的一个。

例如,假设我们有一个带有多边形凹陷的多边形,如下所示: enter image description here

大多边形顶点的二维坐标是

[[139, 483], [227, 792], [482, 849], [523, 670], [352, 330]]

方形空心顶点的坐标为

[[248, 518], [336, 510], [341, 614], [250, 620]]

三角形空心顶点的坐标为

[[416, 531], [505, 517], [495, 616]]

假设我们要测试两个点 [296, 557][422, 730],如果它们在红色区域内(不包括边缘)。如果我们定位这两个点,它将如下所示: enter image description here

显然,[296, 557] 不在读取区域内,而 [422, 730] 在。

我的解决方案基于 winding number algorithm。以下是我仅使用 numpy 的 4 行 Python 代码:

def detect(points, *polygons):
    import numpy as np
    endpoint1 = np.r_[tuple(np.roll(p, 1, 0) for p in polygons)][:, None] - points
    endpoint2 = np.r_[polygons][:, None] - points
    p1, p2 = np.cross(endpoint1, endpoint2), np.einsum('...i,...i', endpoint1, endpoint2)
    return ~((p1.sum(0) < 0) ^ (abs(np.arctan2(p1, p2).sum(0)) > np.pi) | ((p1 == 0) & (p2 <= 0)).any(0))

测试实现:

points = [[296, 557], [422, 730]]
polygon1 = [[139, 483], [227, 792], [482, 849], [523, 670], [352, 330]]
polygon2 = [[248, 518], [336, 510], [341, 614], [250, 620]]
polygon3 = [[416, 531], [505, 517], [495, 616]]

print(detect(points, polygon1, polygon2, polygon3))

输出:

[False  True]

答案 30 :(得分:0)

nirg解决方案的Scala版本(假设边界矩形预检是单独完成的):

def inside(p: Point, polygon: Array[Point], bounds: Bounds): Boolean = {

  val length = polygon.length

  @tailrec
  def oddIntersections(i: Int, j: Int, tracker: Boolean): Boolean = {
    if (i == length)
      tracker
    else {
      val intersects = (polygon(i).y > p.y) != (polygon(j).y > p.y) && p.x < (polygon(j).x - polygon(i).x) * (p.y - polygon(i).y) / (polygon(j).y - polygon(i).y) + polygon(i).x
      oddIntersections(i + 1, i, if (intersects) !tracker else tracker)
    }
  }

  oddIntersections(0, length - 1, tracker = false)
}

答案 31 :(得分:0)

答案取决于你是否有简单或复杂的多边形。简单多边形不得有任何线段交叉点。所以他们可以有洞,但线不能相互交叉。复杂区域可以具有线交叉 - 因此它们可以具有重叠区域,或者仅通过单个点彼此接触的区域。

对于简单多边形,最好的算法是Ray cast(交叉数)算法。对于复杂多边形,此算法不会检测重叠区域内的点。因此,对于复杂多边形,您必须使用绕组数算法。

这是一篇优秀的文章,其中包含两种算法的C实现。我尝试了它们并且效果很好。

http://geomalgorithms.com/a03-_inclusion.html

答案 32 :(得分:0)

使用(Qt 4.3+)时,可以使用QPolygon&#39; s函数containsPoint

答案 33 :(得分:0)

如果您正在寻找一个java脚本库,那么Polygon类的javascript google maps v3扩展可以检测一个点是否位于其中。

var polygon = new google.maps.Polygon([], "#000000", 1, 1, "#336699", 0.3);
var isWithinPolygon = polygon.containsLatLng(40, -90);

Google Extention Github

答案 34 :(得分:0)

您可以通过检查通过将所需点连接到多边形顶点而形成的区域是否与多边形本身的区域匹配来执行此操作。

或者您可以检查从您的点到每对两个连续多边形顶点到您的检查点的内角之和是否总和为360,但我觉得第一个选项更快,因为它不涉及分裂或三角函数逆的计算。

我不知道如果你的多边形里面有一个洞会发生什么,但在我看来,主要想法可以适应这种情况

您也可以在数学社区发布问题。我打赌他们有百万种方法可以做到这一点

答案 35 :(得分:0)

MasterTable中处理以下特殊情况:

  1. 光线与多边形的一侧重叠。
  2. 该点位于多边形内部,光线穿过多边形的顶点。
  3. 该点位于多边形之外,光线只接触多边形的一个角度。
  4. 检查Ray casting algorithm。本文提供了一种解决问题的简便方法,因此上述案例不需要特殊处理。

答案 36 :(得分:0)

对于检测多边形上的命中,我们需要测试两件事:

  1. 如果Point位于多边形区域内。 (可以通过射线投射算法完成)
  2. 如果Point位于多边形边界上(可以通过用于折线(线)上的点检测的相同算法完成)。

答案 37 :(得分:0)

在C中没有使用光线投射的多边形测试中的一个点。它可以用于重叠区域(自交叉),请参阅use_holes参数。

/* math lib (defined below) */
static float dot_v2v2(const float a[2], const float b[2]);
static float angle_signed_v2v2(const float v1[2], const float v2[2]);
static void copy_v2_v2(float r[2], const float a[2]);

/* intersection function */
bool isect_point_poly_v2(const float pt[2], const float verts[][2], const unsigned int nr,
                         const bool use_holes)
{
    /* we do the angle rule, define that all added angles should be about zero or (2 * PI) */
    float angletot = 0.0;
    float fp1[2], fp2[2];
    unsigned int i;
    const float *p1, *p2;

    p1 = verts[nr - 1];

    /* first vector */
    fp1[0] = p1[0] - pt[0];
    fp1[1] = p1[1] - pt[1];

    for (i = 0; i < nr; i++) {
        p2 = verts[i];

        /* second vector */
        fp2[0] = p2[0] - pt[0];
        fp2[1] = p2[1] - pt[1];

        /* dot and angle and cross */
        angletot += angle_signed_v2v2(fp1, fp2);

        /* circulate */
        copy_v2_v2(fp1, fp2);
        p1 = p2;
    }

    angletot = fabsf(angletot);
    if (use_holes) {
        const float nested = floorf((angletot / (float)(M_PI * 2.0)) + 0.00001f);
        angletot -= nested * (float)(M_PI * 2.0);
        return (angletot > 4.0f) != ((int)nested % 2);
    }
    else {
        return (angletot > 4.0f);
    }
}

/* math lib */

static float dot_v2v2(const float a[2], const float b[2])
{
    return a[0] * b[0] + a[1] * b[1];
}

static float angle_signed_v2v2(const float v1[2], const float v2[2])
{
    const float perp_dot = (v1[1] * v2[0]) - (v1[0] * v2[1]);
    return atan2f(perp_dot, dot_v2v2(v1, v2));
}

static void copy_v2_v2(float r[2], const float a[2])
{
    r[0] = a[0];
    r[1] = a[1];
}

注意:这是一个不太理想的方法,因为它包含了很多对atan2f的调用,但是开发者阅读这个帖子可能会感兴趣(在我的测试中它比使用该行慢〜23倍)交集方法)。

答案 38 :(得分:-1)

这仅适用于凸形,但Minkowski Portal Refinement和GJK也是测试点是否在多边形中的绝佳选择。使用minkowski减法从多边形中减去该点,然后运行这些算法以查看多边形是否包含原点。

另外,有趣的是,您可以使用支持函数更隐式地描述您的形状,这些函数将方向向量作为输入并沿着该向量吐出最远点。这允许您描述任何凸起的形状..弯曲,由多边形或混合制成。您还可以执行操作以组合简单支持函数的结果,以生成更复杂的形状。

更多信息: http://xenocollide.snethen.com/mpr2d.html

此外,游戏编程宝石7讨论了如何在3d中执行此操作(:

相关问题