How can I determine whether a 2D Point is within a Polygon?

前端 未结 30 2077
醉梦人生
醉梦人生 2020-11-21 05:06

I\'m trying to create a fast 2D point inside polygon algorithm, for use in hit-testing (e.g. Polygon.contains(p:Point)). Suggestions for effective tech

相关标签:
30条回答
  • 2020-11-21 05:58

    I realize this is old, but here is a ray casting algorithm implemented in Cocoa, in case anyone is interested. Not sure it is the most efficient way to do things, but it may help someone out.

    - (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;
    }
    
    0 讨论(0)
  • 2020-11-21 05:58

    VBA VERSION:

    Note: Remember that if your polygon is an area within a map that Latitude/Longitude are Y/X values as opposed to X/Y (Latitude = Y, Longitude = X) due to from what I understand are historical implications from way back when Longitude was not a measurement.

    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
    
    0 讨论(0)
  • 2020-11-21 05:58

    For Detecting hit on Polygon we need to test two things:

    1. If Point is inside polygon area. (can be accomplished by Ray-Casting Algorithm)
    2. If Point is on the polygon border(can be accomplished by same algorithm which is used for point detection on polyline(line)).
    0 讨论(0)
  • 2020-11-21 05:59

    The trivial solution would be to divide the polygon to triangles and hit test the triangles as explained here

    If your polygon is CONVEX there might be a better approach though. Look at the polygon as a collection of infinite lines. Each line dividing space into two. for every point it's easy to say if its on the one side or the other side of the line. If a point is on the same side of all lines then it is inside the polygon.

    0 讨论(0)
  • 2020-11-21 05:59

    Obj-C version of nirg's answer with sample method for testing points. Nirg's answer worked well for me.

    - (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

    0 讨论(0)
  • 2020-11-21 06:00

    The answer depends on if you have the simple or complex polygons. Simple polygons must not have any line segment intersections. So they can have the holes but lines can't cross each other. Complex regions can have the line intersections - so they can have the overlapping regions, or regions that touch each other just by a single point.

    For simple polygons the best algorithm is Ray casting (Crossing number) algorithm. For complex polygons, this algorithm doesn't detect points that are inside the overlapping regions. So for complex polygons you have to use Winding number algorithm.

    Here is an excellent article with C implementation of both algorithms. I tried them and they work well.

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

    0 讨论(0)
提交回复
热议问题