Reputation: 19916
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?
Upvotes: 20
Views: 24025
Reputation: 20128
MarekR
's answer works for me. This is the extension I've put it in:
extension MKCoordinateRegion {
func contains(coordinate:CLLocationCoordinate2D) -> Bool {
cos((center.latitude - coordinate.latitude) * Double.pi/180) > cos(span.latitudeDelta / 2.0*Double.pi/180) &&
cos((center.longitude - coordinate.longitude) * Double.pi/180) > cos(span.longitudeDelta / 2.0*Double.pi/180)
}
}
Upvotes: 1
Reputation: 1790
Works for me like a charm (Swift 5)
func check(
location: CLLocationCoordinate2D,
contains childLocation: CLLocationCoordinate2D,
with radius: Double)
-> Bool
{
let region = CLCircularRegion(center: location, radius: radius, identifier: "SearchId")
return region.contains(childLocation)
}
Upvotes: 2
Reputation: 425
I had problem with same calculations. I like conception proposed by Owen Godfrey here, bun even Fernando here missed the fact that latitude is wraped diferently than longitude and has diferent range. To clarify my proposal I post it with tests so you can check it out by your self.
import XCTest
import MapKit
// MARK - The Solution
extension CLLocationDegrees {
enum WrapingDimension: Double {
case latitude = 180
case longitude = 360
}
/// Standardises and angle to [-180 to 180] or [-90 to 90] degrees
func wrapped(diemension: WrapingDimension) -> CLLocationDegrees {
let length = diemension.rawValue
let halfLenght = length/2.0
let angle = self.truncatingRemainder(dividingBy: length)
switch diemension {
case .longitude:
// return angle < -180.0 ? 360.0 + angle : angle > 180.0 ? -360.0 + angle : angle
return angle < -halfLenght ? length + angle : angle > halfLenght ? -length + angle : angle
case .latitude:
// return angle < -90.0 ? -180.0 - angle : angle > 90.0 ? 180.0 - angle : angle
return angle < -halfLenght ? -length - angle : angle > halfLenght ? length - angle : angle
}
}
}
extension MKCoordinateRegion {
/// confirms that a region contains a location
func contains(_ coordinate: CLLocationCoordinate2D) -> Bool {
let deltaLat = abs((self.center.latitude - coordinate.latitude).wrapped(diemension: .latitude))
let deltalong = abs((self.center.longitude - coordinate.longitude).wrapped(diemension: .longitude))
return self.span.latitudeDelta/2.0 >= deltaLat && self.span.longitudeDelta/2.0 >= deltalong
}
}
// MARK - Unit tests
class MKCoordinateRegionContaingTests: XCTestCase {
func testRegionContains() {
var region = MKCoordinateRegionMake(CLLocationCoordinate2DMake(0, 0), MKCoordinateSpan(latitudeDelta: 1, longitudeDelta: 1))
var coords = CLLocationCoordinate2DMake(0, 0)
XCTAssert(region.contains(coords))
coords = CLLocationCoordinate2DMake(0.5, 0.5)
XCTAssert(region.contains(coords))
coords = CLLocationCoordinate2DMake(-0.5, 0.5)
XCTAssert(region.contains(coords))
coords = CLLocationCoordinate2DMake(0.5, 0.5000001)
XCTAssert(!region.contains(coords)) // NOT Contains
coords = CLLocationCoordinate2DMake(0.5, -0.5000001)
XCTAssert(!region.contains(coords)) // NOT Contains
coords = CLLocationCoordinate2DMake(1, 1)
XCTAssert(!region.contains(coords)) // NOT Contains
region = MKCoordinateRegionMake(CLLocationCoordinate2DMake(0, 180), MKCoordinateSpan(latitudeDelta: 1, longitudeDelta: 1))
coords = CLLocationCoordinate2DMake(0, 180.5)
XCTAssert(region.contains(coords))
coords.longitude = 179.5
XCTAssert(region.contains(coords))
coords.longitude = 180.5000001
XCTAssert(!region.contains(coords)) // NOT Contains
coords.longitude = 179.5000001
XCTAssert(region.contains(coords))
coords.longitude = 179.4999999
XCTAssert(!region.contains(coords)) // NOT Contains
region = MKCoordinateRegionMake(CLLocationCoordinate2DMake(90, -180), MKCoordinateSpan(latitudeDelta: 1, longitudeDelta: 1))
coords = CLLocationCoordinate2DMake(90.5, -180.5)
XCTAssert(region.contains(coords))
coords = CLLocationCoordinate2DMake(89.5, -180.5)
XCTAssert(region.contains(coords))
coords = CLLocationCoordinate2DMake(90.50000001, -180.5)
XCTAssert(!region.contains(coords)) // NOT Contains
coords = CLLocationCoordinate2DMake(89.50000001, -180.5)
XCTAssert(region.contains(coords))
coords = CLLocationCoordinate2DMake(89.49999999, -180.5)
XCTAssert(!region.contains(coords)) // NOT Contains
}
func testStandardAngle() {
var angle = 180.5.wrapped(diemension: .longitude)
var required = -179.5
XCTAssert(self.areAngleEqual(angle, required))
angle = 360.5.wrapped(diemension: .longitude)
required = 0.5
XCTAssert(self.areAngleEqual(angle, required))
angle = 359.5.wrapped(diemension: .longitude)
required = -0.5
XCTAssert(self.areAngleEqual(angle, required))
angle = 179.5.wrapped(diemension: .longitude)
required = 179.5
XCTAssert(self.areAngleEqual(angle, required))
angle = 90.5.wrapped(diemension: .latitude)
required = 89.5
XCTAssert(self.areAngleEqual(angle, required))
angle = 90.5000001.wrapped(diemension: .latitude)
required = 89.4999999
XCTAssert(self.areAngleEqual(angle, required))
angle = -90.5.wrapped(diemension: .latitude)
required = -89.5
XCTAssert(self.areAngleEqual(angle, required))
angle = -90.5000001.wrapped(diemension: .latitude)
required = -89.4999999
XCTAssert(self.areAngleEqual(angle, required))
}
/// compare doubles with presition to 8 digits after the decimal point
func areAngleEqual(_ a:Double, _ b:Double) -> Bool {
let presition = 0.00000001
let equal = Int(a / presition) == Int(b / presition)
print(String(format:"%14.9f %@ %14.9f", a, equal ? "==" : "!=", b) )
return equal
}
}
Upvotes: 1
Reputation: 14068
Based on Lukasz solution, but in Swift, in case anybody can make use of Swift:
func isInRegion (region : MKCoordinateRegion, coordinate : CLLocationCoordinate2D) -> Bool {
let center = region.center;
let northWestCorner = CLLocationCoordinate2D(latitude: center.latitude - (region.span.latitudeDelta / 2.0), longitude: center.longitude - (region.span.longitudeDelta / 2.0))
let southEastCorner = CLLocationCoordinate2D(latitude: center.latitude + (region.span.latitudeDelta / 2.0), longitude: center.longitude + (region.span.longitudeDelta / 2.0))
return (
coordinate.latitude >= northWestCorner.latitude &&
coordinate.latitude <= southEastCorner.latitude &&
coordinate.longitude >= northWestCorner.longitude &&
coordinate.longitude <= southEastCorner.longitude
)
}
Upvotes: 0
Reputation: 272
Owen Godfrey, the objective-C code doesn´t work, this is the good code: Fails on Objective-C, this is the good code:
/* Standardises and angle to [-180 to 180] degrees */
- (CLLocationDegrees)standardAngle:(CLLocationDegrees)angle {
angle=fmod(angle,360);
return angle < -180 ? -360 - angle : angle > 180 ? 360 - 180 : angle;
}
-(BOOL)thisRegion:(MKCoordinateRegion)region containsLocation:(CLLocation *)location{
CLLocationDegrees deltaLat =fabs([self standardAngle:(region.center.latitude-location.coordinate.latitude)]);
CLLocationDegrees deltaLong =fabs([self standardAngle:(region.center.longitude-location.coordinate.longitude)]);
return region.span.latitudeDelta >= deltaLat && region.span.longitudeDelta >=deltaLong;
}
CLLocationDegrees deltalong = fabs(standardAngle(region.center.longitude - location.coordinate.longitude));
return region.span.latitudeDelta >= deltaLat && region.span.longitudeDelta >= deltalong;
}
Thanks!
Upvotes: 1
Reputation: 3373
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)
Upvotes: 7
Reputation: 1266
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];
}
Upvotes: 6
Reputation: 2410
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;
}
Upvotes: 21
Reputation: 19916
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);
}
Upvotes: 15
Reputation: 113747
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.
Upvotes: 17