How to check if MKCoordinateRegion contains CLLocationCoordinate2D without using MKMapView?

22,465

Solution 1

In case there is anybody else confused with latitudes and longitues, here is tested, working solution:

MKCoordinateRegion region = self.mapView.region;

CLLocationCoordinate2D location = user.gpsposition.coordinate;
CLLocationCoordinate2D center   = region.center;
CLLocationCoordinate2D northWestCorner, southEastCorner;

northWestCorner.latitude  = center.latitude  - (region.span.latitudeDelta  / 2.0);
northWestCorner.longitude = center.longitude - (region.span.longitudeDelta / 2.0);
southEastCorner.latitude  = center.latitude  + (region.span.latitudeDelta  / 2.0);
southEastCorner.longitude = center.longitude + (region.span.longitudeDelta / 2.0);

if (
    location.latitude  >= northWestCorner.latitude && 
    location.latitude  <= southEastCorner.latitude &&

    location.longitude >= northWestCorner.longitude && 
    location.longitude <= southEastCorner.longitude
    )
{
    // User location (location) in the region - OK :-)
    NSLog(@"Center (%f, %f) span (%f, %f) user: (%f, %f)| IN!", region.center.latitude, region.center.longitude, region.span.latitudeDelta, region.span.longitudeDelta, location.latitude, location.longitude);

}else {

    // User location (location) out of the region - NOT ok :-(
    NSLog(@"Center (%f, %f) span (%f, %f) user: (%f, %f)| OUT!", region.center.latitude, region.center.longitude, region.span.latitudeDelta, region.span.longitudeDelta, location.latitude, location.longitude);
}

Solution 2

I'm posting this answer as the accepted solution is not valid in my opinion. This answer is also not perfect but it handles the case when coordinates wrap around 360 degrees boundaries, which is enough to be suitable in my situation.

+ (BOOL)coordinate:(CLLocationCoordinate2D)coord inRegion:(MKCoordinateRegion)region
{
    CLLocationCoordinate2D center = region.center;
    MKCoordinateSpan span = region.span;

    BOOL result = YES;
    result &= cos((center.latitude - coord.latitude)*M_PI/180.0) > cos(span.latitudeDelta/2.0*M_PI/180.0);
    result &= cos((center.longitude - coord.longitude)*M_PI/180.0) > cos(span.longitudeDelta/2.0*M_PI/180.0);
    return result;
}

Solution 3

You can convert your location to a point with MKMapPointForCoordinate, then use MKMapRectContainsPoint on the mapview's visibleMapRect. This is completely off the top of my head. Let me know if it works.

Solution 4

The other answers all have faults. The accepted answer is a little verbose, and fails near the international dateline. The cosine answer is workable, but fails for very small regions (because delta cosine is sine which tends towards zero near zero, meaning for smaller angular differences we expect zero change) This answer should work correctly for all situations, and is simpler.

Swift:

/* Standardises and angle to [-180 to 180] degrees */
class func standardAngle(var angle: CLLocationDegrees) -> CLLocationDegrees {
    angle %= 360
    return angle < -180 ? -360 - angle : angle > 180 ? 360 - 180 : angle
}

/* confirms that a region contains a location */
class func regionContains(region: MKCoordinateRegion, location: CLLocation) -> Bool {
    let deltaLat = abs(standardAngle(region.center.latitude - location.coordinate.latitude))
    let deltalong = abs(standardAngle(region.center.longitude - location.coordinate.longitude))
    return region.span.latitudeDelta >= deltaLat && region.span.longitudeDelta >= deltalong
}

Objective C:

/* Standardises and angle to [-180 to 180] degrees */
+ (CLLocationDegrees)standardAngle:(CLLocationDegrees)angle {
    angle %= 360
    return angle < -180 ? -360 - angle : angle > 180 ? 360 - 180 : angle
}

/* confirms that a region contains a location */
+ (BOOL)region:(MKCoordinateRegion*)region containsLocation:(CLLocation*)location {
    CLLocationDegrees deltaLat = fabs(standardAngle(region.center.latitude - location.coordinate.latitude))
    CLLocationDegrees deltalong = fabs(standardAngle(region.center.longitude - location.coordinate.longitude))
    return region.span.latitudeDelta >= deltaLat && region.span.longitudeDelta >= deltalong
}

This method fails for regions that include either pole though, but then the coordinate system itself fails at the poles. For most applications, this solution should suffice. (Note, not tested on Objective C)

Solution 5

I've used this code to determine if a coordinate is within a circular region (a coordinate with a radius around it).

- (BOOL)location:(CLLocation *)location isNearCoordinate:(CLLocationCoordinate2D)coordinate withRadius:(CLLocationDistance)radius
{
    CLCircularRegion *circularRegion = [[CLCircularRegion alloc] initWithCenter:location.coordinate radius:radius identifier:@"radiusCheck"];

    return [circularRegion containsCoordinate:coordinate];
}
Share:
22,465
Lukasz
Author by

Lukasz

Updated on February 22, 2020

Comments

  • Lukasz
    Lukasz over 4 years

    I need to check if user location belongs to the MKCoordinateRegion. I was surprised not to find simple function for this, something like: CGRectContainsCGPoint(rect, point).

    I found following piece of code:

    CLLocationCoordinate2D topLeftCoordinate = 
        CLLocationCoordinate2DMake(region.center.latitude 
                                   + (region.span.latitudeDelta/2.0), 
                                   region.center.longitude 
                                   - (region.span.longitudeDelta/2.0));
    
    
        CLLocationCoordinate2D bottomRightCoordinate = 
        CLLocationCoordinate2DMake(region.center.latitude 
                                   - (region.span.latitudeDelta/2.0), 
                                   region.center.longitude 
                                   + (region.span.longitudeDelta/2.0));
    
            if (location.latitude < topLeftCoordinate.latitude || location.latitude > bottomRightCoordinate.latitude || location.longitude < bottomRightCoordinate.longitude || location.longitude > bottomRightCoordinate.longitude) {
    
        // Coordinate fits into the region
    
        }
    

    But, I am not sure if it is accurate as documentation does not specify exactly how the region rectangle is calculated.

    There must be simpler way to do it. Have I overlooked some function in the MapKit framework documentation?

  • Lukasz
    Lukasz about 12 years
    It feels completely overwhelming to initialize whole MKMapView and set it up just for such a simple check. I need to calculate this outside any view controller.
  • nevan king
    nevan king about 12 years
    Sorry, I thought you were working with a mapview already in place. If you only have that region, you'll have to rely on it to be accurate. Why do you think the region is no good? Where did you get the region from?
  • Lukasz
    Lukasz about 12 years
    The region is OK. I am just not sure if I am checking against it correctly. The documentation of MKCoordinateRegion does not specify exactly how the latitude and longitude spans constructs area rectangle.
  • nevan king
    nevan king about 12 years
    The way you're doing it is fine. The region specifies a span which is in degrees, the same as latitude and longitude. They convert directly. I'm not quite sure about the logic of your if statement. Shouldn't they be && instead of ||?
  • lichen19853
    lichen19853 about 11 years
    I doubt this would work: 1. why should location.latitude >= northWestCorner.latitude? Shouldn't it be sounthEastCorner.latitude? 2. What if calculated minimum longitude is -2.0, maximum longitude is 2.0, and your location.longitude is 359.0?
  • james Burns
    james Burns over 10 years
    This appears to be a great, simple solution. I have to test more, but may be the best solution to this tricky problem.
  • MarekR
    MarekR about 10 years
    @lichen19853 is right that it will fail when tested around 360 degrees. See my answer below for a slightly more correct solution.
  • Brainware
    Brainware over 8 years
    This should be the accepted solution. The cos() function takes care of the 0 to 360 degree issue. Even though it performs a non-linear scale on the distance, it is compared to an equally scaled delta, so it works like a charm.
  • Verticon
    Verticon almost 8 years
    In the standardAngle method, for the case where the normalized angle > 180: shouldn't the return value be 360 - angle instead of 360 - 180?
  • Verticon
    Verticon almost 7 years
    This worked for me for a small, well defined rectangular region in my city. I cannot attest to the general case.
  • Nikita Ivaniushchenko
    Nikita Ivaniushchenko about 6 years
    You should use span.delta/2 when comparing to latitude or longitude.
  • Nikita Ivaniushchenko
    Nikita Ivaniushchenko about 6 years
    Use region.span.latitudeDelta/2 and region.span.longitudeDelta in last line