Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

detect if a point is inside a MKPolygon overlay

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:

enter image description here

like image 914
Padin215 Avatar asked Apr 11 '12 16:04

Padin215


2 Answers

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;
        }
}
like image 25
Hari Avatar answered Oct 03 '22 02:10

Hari


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
like image 139
Daniel Boyd Avatar answered Oct 03 '22 02:10

Daniel Boyd