detect if a point is inside a MKPolygon overlay

2019-01-21 14:51发布

问题:

I want to be able to tell if tap is within a MKPolygon.

I have a MKPolygon:

CLLocationCoordinate2D  points[4];

points[0] = CLLocationCoordinate2DMake(41.000512, -109.050116);
points[1] = CLLocationCoordinate2DMake(41.002371, -102.052066);
points[2] = CLLocationCoordinate2DMake(36.993076, -102.041981);
points[3] = CLLocationCoordinate2DMake(36.99892, -109.045267);

MKPolygon* poly = [MKPolygon polygonWithCoordinates:points count:4];

[self.mapView addOverlay:poly];  

//create UIGestureRecognizer to detect a tap
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(foundTap:)];
tapRecognizer.numberOfTapsRequired = 1;
tapRecognizer.numberOfTouchesRequired = 1;
[self.mapView addGestureRecognizer:tapRecognizer];

its just a basic outline of the state Colorado.

I got the tap to lat/long conversion set up:

-(IBAction)foundTap:(UITapGestureRecognizer *)recognizer
{
    CGPoint point = [recognizer locationInView:self.mapView];

    CLLocationCoordinate2D tapPoint = [self.mapView convertPoint:point toCoordinateFromView:self.view];
}

but i am unsure how to tech if my tap point is within the MKPolygon. there does not seem to be a method to do this check, so i'm guessing i need to convert the MKPolygon to a CGRect and use CGRectContainsPoint.

MKPolygon has a .points property but i can't seem to get them back out.

any suggestions?

EDIT:

Both solutions below work in iOS 6 or lower, but breaks in iOS 7. In iOS 7 the polygon.path property allways returns NULL. Ms Anna was kind enough to provide a solution in another SO question here. It involves creating your own path from the polygon points to pass into CGPathContainsPoint().

image of my polygon:

回答1:

I created this MKPolygon category in case anyone wants to use it. Seems to work well. You have to account for the interior polygons (i.e. holes in the polygon):

@interface MKPolygon (PointInPolygon)
  -(BOOL) pointInPolygon:(CLLocationCoordinate2D) point mapView: (MKMapView*) mapView;
@end

@implementation MKPolygon (PointInPolygon)

-(BOOL) pointInPolygon:(CLLocationCoordinate2D) point mapView: (MKMapView*) mapView {
    MKMapPoint mapPoint = MKMapPointForCoordinate(point);
    MKPolygonView * polygonView = (MKPolygonView*)[mapView viewForOverlay:self];
    CGPoint polygonViewPoint = [polygonView pointForMapPoint:mapPoint];
    return CGPathContainsPoint(polygonView.path, NULL, polygonViewPoint, NO) && 
        ![self pointInInteriorPolygons:point mapView:mapView];
}

-(BOOL) pointInInteriorPolygons:(CLLocationCoordinate2D) point mapView: (MKMapView*) mapView {
    return [self pointInInteriorPolygonIndex:0 point:point mapView:mapView];
}

-(BOOL) pointInInteriorPolygonIndex:(int) index point:(CLLocationCoordinate2D) point mapView: (MKMapView*) mapView {
    if(index >= [self.interiorPolygons count])
        return NO;
    return [[self.interiorPolygons objectAtIndex:index] pointInPolygon:point mapView:mapView] || [self pointInInteriorPolygonIndex:(index+1) point:point mapView:mapView];
}

@end


回答2:

Your foundTap method:

-(IBAction)foundTap:(UITapGestureRecognizer *)recognizer
{
    CGPoint point = [recognizer locationInView:self.mapView];

    CLLocationCoordinate2D tapPoint = [self.mapView convertPoint:point toCoordinateFromView:self.view];

    [self pointInsideOverlay:tapPoint];

    if (isInside) 
     {
       ....
     }
}

Here is a method to call from the previous to check if the point is inside the overlay:

-(void)pointInsideOverlay:(CLLocationCoordinate2D )tapPoint 
{
    isInside = FALSE; 

    MKPolygonView *polygonView = (MKPolygonView *)[mapView viewForOverlay:polygonOverlay];

    MKMapPoint mapPoint = MKMapPointForCoordinate(tapPoint);

    CGPoint polygonViewPoint = [polygonView pointForMapPoint:mapPoint];

    BOOL mapCoordinateIsInPolygon = CGPathContainsPoint(polygonView.path, NULL, polygonViewPoint, NO);

        if ( !mapCoordinateIsInPolygon )

            //we are finding points that are inside the overlay
        {
            isInside = TRUE;
        }
}


回答3:

I am getting MKPolygon Data points from xml file in string. I parse data string to Array of points and use approach give in http://alienryderflex.com/polygon/

It works for me....

-(BOOL)isPoint:(CLLocationCoordinate2D)findLocation inPloygon:(NSArray*)polygon{

    NSMutableArray *tempPolygon=[NSMutableArray arrayWithArray:polygon];
    int   i, j=(int)tempPolygon.count-1 ;
    bool  oddNodes=NO;
    double x=findLocation.latitude;
    double y=findLocation.longitude;

    for (i=0; i<tempPolygon.count; i++) {
        NSString*coordString=[tempPolygon objectAtIndex:i];
        NSArray*pointsOfCoordString=[coordString componentsSeparatedByString:@","];
        CLLocationCoordinate2D point=CLLocationCoordinate2DMake([[pointsOfCoordString objectAtIndex:1] doubleValue], [[pointsOfCoordString objectAtIndex:0] doubleValue]);
        NSString*nextCoordString=[tempPolygon objectAtIndex:j];
        NSArray*nextPointsOfCoordString=[nextCoordString componentsSeparatedByString:@","];
        CLLocationCoordinate2D nextPoint=CLLocationCoordinate2DMake([[nextPointsOfCoordString objectAtIndex:1] doubleValue], [[nextPointsOfCoordString objectAtIndex:0] doubleValue]);


        if ((point.longitude<y && nextPoint.longitude>=y)
            ||  (nextPoint.longitude<y && point.longitude>=y)) {
            if (point.latitude+(y-point.longitude)/(nextPoint.longitude-point.longitude)*(nextPoint.latitude-point.latitude)<x) {
                oddNodes=!oddNodes; }}
        j=i; }


    return oddNodes;

}

my polygon(NSArray) objects are in string for e.g. @"-89.860021,44.944266,0"



回答4:

Determining whether a point is in an arbitrary polygon is non-trivial and it is unsurprising that Apple doesn't supply it as part of MKPolygon. You can access the points, which allows you to iterate over the edges.

To determine whether a point p is inside a polygon s, consider each edge as a directed line segment in s. If a ray from p in any fixed direction (typically parallel to either the X or Y axis) intersects the segment, take the sign of the Z component of the cross product of the ray with that directed line segment. If the Z component is > 0, add 1 to a counter. If it is < 0, subtract 1. The trick in implementing this is to avoid issues when the edge is nearly parallel to the ray, or when the ray passes through a vertex (it has to count only once, not once for each edge).

When you have done this for all edges in s, you will have counted the number of times your ray intersects the outline of the polygon, where if the edge was going from left to right you added, and if it was going from right to left, you subtracted. If the resulting sum is zero, you are outside the polygon. Otherwise you are inside it.

There are numerous optimizations possible. One such is to do a quick bounding box test before the more complete test. Another is to have a data structure with bounds on all the edges to trivially discard edges that do not intersect the ray.

Edit: the Z component of A X B ( the cross product of A with B ) is given by:

a.x * b.y - a.y * b.x

since all you care about is the sign, you can check

a.x * b.y > a.y * b.x


回答5:

here is swift 3 updated version thanks to @Steve Stomp

extension MKPolygon {

    func contains(coordinate: CLLocationCoordinate2D) -> Bool {

        let polygonRenderer = MKPolygonRenderer(polygon: self)
        let currentMapPoint: MKMapPoint = MKMapPointForCoordinate(coordinate)
        let polygonViewPoint: CGPoint = polygonRenderer.point(for: currentMapPoint)

        return  polygonRenderer.path.contains(polygonViewPoint)
    }
}


回答6:

This worked for me in Swift:

extension MKPolygon {

    func isCoordinateInsidePolyon(coordinate: CLLocationCoordinate2D) -> Bool {

        var inside = false

        let polygonRenderer = MKPolygonRenderer(polygon: self)
        let currentMapPoint: MKMapPoint = MKMapPointForCoordinate(coordinate)
        let polygonViewPoint: CGPoint = polygonRenderer.pointForMapPoint(currentMapPoint)

        if CGPathContainsPoint(polygonRenderer.path, nil, polygonViewPoint, true) {
            inside = true
        }

        return inside
    }
}