1

I am trying to find a simple method to determine whether a CLLocationCoordinate2D lies within the boundaries of an arbitrary shape defined by a series of other CLLocationCoordinate2D's. The shapes may be large enough that great-circle paths need to be considered.

CL used to have a circular region and the containsCoordinate: call to test against, but this has been deprecated in iOS7 and the dox do not contain a hint of what might replace it. I cannot find any other examples, notably one that works on polygons.

There are many similar questions here on SO, but they are not related to iOS specifically, and again, I can't seem to find one that works generally on great-circle polys.

Maury Markowitz
  • 9,082
  • 11
  • 46
  • 98
  • Can you please give an example or picture of what you mean by "shapes may be large enough that great-circle paths need to be considered"? Have you tried [this CGPathContainsPoint approach](http://stackoverflow.com/questions/19014926/detecting-a-point-in-a-mkpolygon-broke-with-ios7-cgpathcontainspoint)? You can use CLLocationCoordinate2D values instead of MKMapPoint values in the path (if you prefer) though it should work either way. –  Sep 16 '14 at 14:38
  • Yes, the shapes may cover a significant area of Ontario or Alberta. The largest one today covers most of Pickering and Ajax in Ontario, but future ones may be larger. A suitable example might be Algonquin Park, in terms of both size and the number of points I expect to see in the poly. – Maury Markowitz Sep 16 '14 at 14:43
  • I think the CGPathContainsPoint method will work for you. –  Sep 16 '14 at 14:51
  • That only handles cartesians, not great circle. – Maury Markowitz Sep 16 '14 at 14:53
  • Also, that requires a mapView, which I don't have. – Maury Markowitz Sep 16 '14 at 15:46
  • No, creating a CGPathRef and using CGPathContainsPoint does not require a map view or MapKit. –  Sep 16 '14 at 15:55
  • Ok I tried it, but the poly seems to have odd points. I fed in my lat/lon from the CLLocationCoordinate2D, and get very large numbers back out. Like (MKMapPoint) mp = (x = 75211673.599999994, y = 97946201.318290412). However, when the code translates my test point, it does not change, (CGPoint) mapPointAsCGP = (x=43.8451614, y=-79.0061264). Is that expected? The test always returns false. – Maury Markowitz Sep 16 '14 at 16:30
  • The actual MKMapPoint values are not expected to be "user-friendly" -- don't worry about that. I think the units might be getting mixed up between the polygon points and the test point. I'll try to post an example as an answer soon. –  Sep 16 '14 at 16:38
  • That looks like what's happening. FYI, I did this to make the MKPoly... CLLocationCoordinate2D *coords = malloc(sizeof(CLLocationCoordinate2D) * 4); coords[0] = CLLocationCoordinate2DMake(self.latitudeNorth, self.longitudeWest); coords[1] = CLLocationCoordinate2DMake(self.latitudeNorth, self.longitudeEast); coords[2] = CLLocationCoordinate2DMake(self.latitudeSouth, self.longitudeEast); coords[3] = CLLocationCoordinate2DMake(self.latitudeSouth, self.longitudeWest); MKPolygon *poly = [MKPolygon polygonWithCoordinates:coords count:4]; free(coords); – Maury Markowitz Sep 16 '14 at 16:41

2 Answers2

4

Here's an example (using Algonquin Provincial Park) of an approach that may work for you.

To use CGPathContainsPoint for this purpose, an MKMapView is not required.

Nor is it necessary to create an MKPolygon or even to use the CLLocationCoordinate2D or MKMapPoint structs. They just make the code easier to understand.

The screenshot below was created from the data only for illustration purposes.

int numberOfCoordinates = 10;

//This example draws a crude polygon with 10 coordinates
//around Algonquin Provincial Park.  Use as many coordinates
//as you like to achieve the accuracy you require.
CLLocationCoordinate2D algonquinParkCoordinates[numberOfCoordinates];
algonquinParkCoordinates[0] = CLLocationCoordinate2DMake(46.105,    -79.4);
algonquinParkCoordinates[1] = CLLocationCoordinate2DMake(46.15487,  -78.80759);
algonquinParkCoordinates[2] = CLLocationCoordinate2DMake(46.16629,  -78.12095);
algonquinParkCoordinates[3] = CLLocationCoordinate2DMake(46.11964,  -77.70896);
algonquinParkCoordinates[4] = CLLocationCoordinate2DMake(45.74140,  -77.45627);
algonquinParkCoordinates[5] = CLLocationCoordinate2DMake(45.52630,  -78.22532);
algonquinParkCoordinates[6] = CLLocationCoordinate2DMake(45.18662,  -78.06601);
algonquinParkCoordinates[7] = CLLocationCoordinate2DMake(45.11689,  -78.29123);
algonquinParkCoordinates[8] = CLLocationCoordinate2DMake(45.42230,  -78.69773);
algonquinParkCoordinates[9] = CLLocationCoordinate2DMake(45.35672,  -78.90647);

//Create CGPath from the above coordinates...
CGMutablePathRef mpr = CGPathCreateMutable();

for (int p=0; p < numberOfCoordinates; p++)
{
    CLLocationCoordinate2D c = algonquinParkCoordinates[p];

    if (p == 0)
        CGPathMoveToPoint(mpr, NULL, c.longitude, c.latitude);
    else
        CGPathAddLineToPoint(mpr, NULL, c.longitude, c.latitude);
}

//set up some test coordinates and test them...
int numberOfTests = 7;
CLLocationCoordinate2D testCoordinates[numberOfTests];
testCoordinates[0] = CLLocationCoordinate2DMake(45.5, -78.5);
testCoordinates[1] = CLLocationCoordinate2DMake(45.3, -79.1);
testCoordinates[2] = CLLocationCoordinate2DMake(45.1, -77.9);
testCoordinates[3] = CLLocationCoordinate2DMake(47.3, -79.6);
testCoordinates[4] = CLLocationCoordinate2DMake(45.5, -78.7);
testCoordinates[5] = CLLocationCoordinate2DMake(46.8, -78.4);
testCoordinates[6] = CLLocationCoordinate2DMake(46.1, -78.2);

for (int t=0; t < numberOfTests; t++)
{
    CGPoint testCGPoint = CGPointMake(testCoordinates[t].longitude, testCoordinates[t].latitude);

    BOOL tcInPolygon = CGPathContainsPoint(mpr, NULL, testCGPoint, FALSE);

    NSLog(@"tc[%d] (%f,%f) in polygon = %@",
          t,
          testCoordinates[t].latitude,
          testCoordinates[t].longitude,
          (tcInPolygon ? @"Yes" : @"No"));
}

CGPathRelease(mpr);

Here are the results of the above test:

tc[0] (45.500000,-78.500000) in polygon = Yes
tc[1] (45.300000,-79.100000) in polygon = No
tc[2] (45.100000,-77.900000) in polygon = No
tc[3] (47.300000,-79.600000) in polygon = No
tc[4] (45.500000,-78.700000) in polygon = Yes
tc[5] (46.800000,-78.400000) in polygon = No
tc[6] (46.100000,-78.200000) in polygon = Yes


This screenshot is to illustrate the data only (actual MKMapView is not required to run the code above):

enter image description here

  • So it seems that simply leaving everything as points, even though they're -ve values, was the trick. Worked like a champ Anna, thanks! – Maury Markowitz Sep 16 '14 at 20:18
  • Hi Anna....If we want to hit only on the line draw by these points not the inside of the graph...Means when we tap on the line of points not inside of the graph drawn by them. – ashForIos Apr 07 '16 at 13:15
3

Anna's solution converted to Swift 3.0:

extension CLLocationCoordinate2D {

    func contained(by vertices: [CLLocationCoordinate2D]) -> Bool {
        let path = CGMutablePath()

        for vertex in vertices {
            if path.isEmpty {
                path.move(to: CGPoint(x: vertex.longitude, y: vertex.latitude))
            } else {
                path.addLine(to: CGPoint(x: vertex.longitude, y: vertex.latitude))
            }
        }

        let point = CGPoint(x: self.longitude, y: self.latitude)
        return path.contains(point)
    }
}
Pang
  • 9,564
  • 146
  • 81
  • 122
picciano
  • 22,341
  • 9
  • 69
  • 82