CLLocation Category for Calculating Bearing w/ Haversine function - iphone

I'm trying to write a category for CLLocation to return the bearing to another CLLocation.
I believe I'm doing something wrong with the formula (calculous is not my strong suit). The returned bearing is always off.
I've been looking at this question and tried applying the changes that were accepted as a correct answer and the webpage it references:
Calculating bearing between two CLLocationCoordinate2Ds
http://www.movable-type.co.uk/scripts/latlong.html
Thanks for any pointers. I've tried incorporating the feedback from that other question and I'm still just not getting something.
Thanks
Here's my category -
----- CLLocation+Bearing.h
#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
#interface CLLocation (Bearing)
-(double) bearingToLocation:(CLLocation *) destinationLocation;
-(NSString *) compassOrdinalToLocation:(CLLocation *) nwEndPoint;
#end
---------CLLocation+Bearing.m
#import "CLLocation+Bearing.h"
double DegreesToRadians(double degrees) {return degrees * M_PI / 180;};
double RadiansToDegrees(double radians) {return radians * 180/M_PI;};
#implementation CLLocation (Bearing)
-(double) bearingToLocation:(CLLocation *) destinationLocation {
double lat1 = DegreesToRadians(self.coordinate.latitude);
double lon1 = DegreesToRadians(self.coordinate.longitude);
double lat2 = DegreesToRadians(destinationLocation.coordinate.latitude);
double lon2 = DegreesToRadians(destinationLocation.coordinate.longitude);
double dLon = lon2 - lon1;
double y = sin(dLon) * cos(lat2);
double x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon);
double radiansBearing = atan2(y, x);
return RadiansToDegrees(radiansBearing);
}

Your code seems fine to me. Nothing wrong with the calculous. You don't specify how far off your results are, but you might try tweaking your radian/degrees converters to this:
double DegreesToRadians(double degrees) {return degrees * M_PI / 180.0;};
double RadiansToDegrees(double radians) {return radians * 180.0/M_PI;};
If you are getting negative bearings, add 2*M_PI to the final result in radiansBearing (or 360 if you do it after converting to degrees). atan2 returns the result in the range -M_PI to M_PI (-180 to 180 degrees), so you might want to convert it to compass bearings, using something like the following code
if(radiansBearing < 0.0)
radiansBearing += 2*M_PI;

This is a porting in Swift of the Category at the beginning:
import Foundation
import CoreLocation
public extension CLLocation{
func DegreesToRadians(_ degrees: Double ) -> Double {
return degrees * M_PI / 180
}
func RadiansToDegrees(_ radians: Double) -> Double {
return radians * 180 / M_PI
}
func bearingToLocationRadian(_ destinationLocation:CLLocation) -> Double {
let lat1 = DegreesToRadians(self.coordinate.latitude)
let lon1 = DegreesToRadians(self.coordinate.longitude)
let lat2 = DegreesToRadians(destinationLocation.coordinate.latitude);
let lon2 = DegreesToRadians(destinationLocation.coordinate.longitude);
let dLon = lon2 - lon1
let y = sin(dLon) * cos(lat2);
let x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon);
let radiansBearing = atan2(y, x)
return radiansBearing
}
func bearingToLocationDegrees(destinationLocation:CLLocation) -> Double{
return RadiansToDegrees(bearingToLocationRadian(destinationLocation))
}
}

Here is another implementation
public func bearingBetweenTwoPoints(#lat1 : Double, #lon1 : Double, #lat2 : Double, #lon2: Double) -> Double {
func DegreesToRadians (value:Double) -> Double {
return value * M_PI / 180.0
}
func RadiansToDegrees (value:Double) -> Double {
return value * 180.0 / M_PI
}
let y = sin(lon2-lon1) * cos(lat2)
let x = (cos(lat1) * sin(lat2)) - (sin(lat1) * cos(lat2) * cos(lat2-lon1))
let degrees = RadiansToDegrees(atan2(y,x))
let ret = (degrees + 360) % 360
return ret;
}

Working Swift 3 and 4
Tried so many versions and this one finally gives correct values!
extension CLLocation {
func getRadiansFrom(degrees: Double ) -> Double {
return degrees * .pi / 180
}
func getDegreesFrom(radians: Double) -> Double {
return radians * 180 / .pi
}
func bearingRadianTo(location: CLLocation) -> Double {
let lat1 = self.getRadiansFrom(degrees: self.coordinate.latitude)
let lon1 = self.getRadiansFrom(degrees: self.coordinate.longitude)
let lat2 = self.getRadiansFrom(degrees: location.coordinate.latitude)
let lon2 = self.getRadiansFrom(degrees: location.coordinate.longitude)
let dLon = lon2 - lon1
let y = sin(dLon) * cos(lat2)
let x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon)
var radiansBearing = atan2(y, x)
if radiansBearing < 0.0 {
radiansBearing += 2 * .pi
}
return radiansBearing
}
func bearingDegreesTo(location: CLLocation) -> Double {
return self.getDegreesFrom(radians: self.bearingRadianTo(location: location))
}
}
Usage:
let degrees = location1.bearingDegreesTo(location: location2)

This is an another CLLocation extension can be used in Swift 3 and Swift 4
public extension CLLocation {
func degreesToRadians(degrees: Double) -> Double {
return degrees * .pi / 180.0
}
func radiansToDegrees(radians: Double) -> Double {
return radians * 180.0 / .pi
}
func getBearingBetweenTwoPoints(point1: CLLocation, point2: CLLocation) -> Double {
let lat1 = degreesToRadians(degrees: point1.coordinate.latitude)
let lon1 = degreesToRadians(degrees: point1.coordinate.longitude)
let lat2 = degreesToRadians(degrees: point2.coordinate.latitude)
let lon2 = degreesToRadians(degrees: point2.coordinate.longitude)
let dLon = lon2 - lon1
let y = sin(dLon) * cos(lat2)
let x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon)
let radiansBearing = atan2(y, x)
return radiansToDegrees(radians: radiansBearing)
}
}

I use the Law of Cosines in Swift. It runs faster than Haversine and its result is extremely similar. Variation of 1 metre on huge distances.
Why do I use the Law of Cosines:
Run fast (because there is no sqrt functions)
Precise enough unless you do some astronomy
Perfect for a background task
func calculateDistance(from: CLLocationCoordinate2D, to: CLLocationCoordinate2D) -> Double {
let Ο€ = M_PI
let degToRad: Double = Ο€/180
let earthRadius: Double = 6372797.560856
// Law of Cosines formula
// d = r . arc cos (sin πœ‘A sin πœ‘B + cos πœ‘A cos πœ‘B cos(πœ†B - πœ†A) )
let πœ‘A = from.latitude * degToRad
let πœ‘B = to.latitude * degToRad
let πœ†A = from.longitude * degToRad
let πœ†B = to.longitude * degToRad
let angularDistance = acos(sin(πœ‘A) * sin(πœ‘B) + cos(πœ‘A) * cos(πœ‘B) * cos(πœ†B - πœ†A) )
let distance = earthRadius * angularDistance
return distance
}

Worth mentioning that if you are using Google map GMSMapView, there's an out-of-the-box solution using the GMSGeometryHeading method:
GMSGeometryHeading(from: CLLocationCoordinate2D, to: CLLocationCoordinate2D)
Returns the initial heading (degrees clockwise of North) at from of
the shortest path to to.

Implemented this in Swift 5. Focus is on accuracy, not speed, but it runs in real time np.
let earthRadius: Double = 6372456.7
let degToRad: Double = .pi / 180.0
let radToDeg: Double = 180.0 / .pi
func calcOffset(_ coord0: CLLocationCoordinate2D,
_ coord1: CLLocationCoordinate2D) -> (Double, Double) {
let lat0: Double = coord0.latitude * degToRad
let lat1: Double = coord1.latitude * degToRad
let lon0: Double = coord0.longitude * degToRad
let lon1: Double = coord1.longitude * degToRad
let dLat: Double = lat1 - lat0
let dLon: Double = lon1 - lon0
let y: Double = cos(lat1) * sin(dLon)
let x: Double = cos(lat0) * sin(lat1) - sin(lat0) * cos(lat1) * cos(dLon)
let t: Double = atan2(y, x)
let bearing: Double = t * radToDeg
let a: Double = pow(sin(dLat * 0.5), 2.0) + cos(lat0) * cos(lat1) * pow(sin(dLon * 0.5), 2.0)
let c: Double = 2.0 * atan2(sqrt(a), sqrt(1.0 - a));
let distance: Double = c * earthRadius
return (distance, bearing)
}
func translateCoord(_ coord: CLLocationCoordinate2D,
_ distance: Double,
_ bearing: Double) -> CLLocationCoordinate2D {
let d: Double = distance / earthRadius
let t: Double = bearing * degToRad
let lat0: Double = coord.latitude * degToRad
let lon0: Double = coord.longitude * degToRad
let lat1: Double = asin(sin(lat0) * cos(d) + cos(lat0) * sin(d) * cos(t))
let lon1: Double = lon0 + atan2(sin(t) * sin(d) * cos(lat0), cos(d) - sin(lat0) * sin(lat1))
let lat: Double = lat1 * radToDeg
let lon: Double = lon1 * radToDeg
let c: CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: lat,
longitude: lon)
return c
}
I found that Haversine nailed the distance versus CLLocation's distance method, but didn't provide a bearing ready-to-use with CL. So I'm not using it for the bearing. This gives the most accurate measurement I've encountered from all the math I've tried. The translateCoord method will also precisely plot a new point given an origin, distance in meters, and a bearing in degrees.

Related

can I calculate accurate distance between two latitude and longitude inside a house within 5 meters? [duplicate]

This question already exists:
I want to build something like that if a user enters in a room he recieve a notification in swift ios is it possible?
Closed 3 months ago.
I want to calculate distance between two coordinates within 5 meters or even within one meters is it possible
I have tried haversine formula but not getting the desired result
func calculateDistanceWithHaversin(crrLat: Double, crrLong: Double, desLat: Double = 23.1780068, desLong: Double = 75.7865060, radius: Double = 6367444.7) -> Double {
print("CrrLat \(crrLat) = CrrLong = \(crrLong)")
let haversin = { (angle: Double) -> Double in
return (1 - cos(angle))/2
}
let ahaversin = { (angle: Double) -> Double in
return 2 * asin(sqrt(angle))
}
// degree to radian
let dToR = { (angle: Double) -> Double in
return (angle / 360) * 2 * .pi
}
let lat1 = dToR(crrLat)
let lon1 = dToR(crrLong)
let lat2 = dToR(desLat)
let lon2 = dToR(desLong)
return radius * ahaversin(haversin(lat2 - lat1) + cos(lat1) * cos(lat2) * haversin(lon2 - lon1))
}
i have tried this also
func calculateDistance(crrLat: Double, crrLong: Double) {
let destinationLocation = CLLocation(latitude: 23.1780068, longitude: 75.7865060)
let currentLocation = CLLocation(latitude: crrLat, longitude: crrLong)
distance = currentLocation.distance(from: destinationLocation)
print(String(format: "The distance to my buddy is %.02f m", distance))
}
You can calculate distance with this Builtin Function provided by CoreLocation . The provided distance will be in meters
import CoreLocation
let locationOne = CLLocation(latitude: 37.899, longitude: 74.8989)
let locationTwo = CLLocation(latitude: 38.0900, longitude: 78.98898)
let distance = locationOne.distance(from: locationTwo)

Fetch posts from firebase by location with swift

I'm trying to fetch posts and print them to the console when the user comes into some sort of radius of them. How can I do this in swift.
Here's a small struct in Swift I wrote that let's you compute the distance in km between two coordinates using the Haversine formula
.
Example
let london = Coordinate(long: -0.118092, lat: 51.509865)
let paris = Coordinate(long: 2.3522219, lat: 48.856614)
print(london.distance(to: paris)) // ~343.43km
print(london == paris) // false
print(london) // 51.509865,-0.118092
print(paris) // 48.856614,2.3522219
Code
import Foundation
public struct Coordinate: CustomStringConvertible, Equatable {
// Earth radius in km
private static let radiusEarth = 6371.0
public typealias Distance = Double
public let lon: Double
public let lat: Double
public init(long: Double, lat: Double) {
self.lon = long
self.lat = lat
}
public static func == (lhs: Coordinate, rhs: Coordinate) -> Bool {
return lhs.lon == rhs.lon && lhs.lat == rhs.lat
}
public var description: String {
return "\(lat),\(lon)"
}
// Haversine formula
public func distance(to other: Coordinate) -> Distance {
let dLat = deg2rad(other.lat - lat)
let dLon = deg2rad(other.lon - lon)
let a =
sin(dLat / 2) * sin(dLat / 2) +
cos(deg2rad(lat)) * cos(deg2rad(other.lat)) *
sin(dLon / 2) * sin(dLon / 2)
let c = 2 * atan2(sqrt(a), sqrt(1 - a))
return Coordinate.radiusEarth * c
}
private func deg2rad(_ deg: Double) -> Double {
return deg * (.pi / 180.0)
}
}

Calculate bearing between two locations

I want to calculate bearing between two location in Swift. I tried some code but it do not work. I searched about this problem but I didn't find any result about this.
func calculat(userlocation:CLLocation){
let latuserlocation: () = DegreesToRadians(userlocation.coordinate.latitude)
let lonuserlocatioc: () = DegreesToRadians(userlocation.coordinate.longitude)
latitude = NSString (string: places[activePlace]["lat"]!).doubleValue
longitude = NSString (string: places[activePlace]["lon"]!).doubleValue
let targetedPointLatitude: () = DegreesToRadians(latitude)
let targetedPointlongitude: () = DegreesToRadians(longitude)
let dlon = lonuserlocatioc - targetedPointlongitude
let y = sin(dLon) * cos(targetedPointLatitude);
let x = cos(latuserlocation) * sin(targetedPointLatitude) - sin(latuserlocation) * cos(targetedPointLatitude) * cos(dLon);
let radiansBearing = atan2(y, x);
return RadiansToDegrees(radiansBearing)
The error on let dlon = lonuserlocatioc - targetedPointlongitude is:
(cannot invoke '-' with an argument list of type '((), ())')
Compared to CLLocation Category for Calculating Bearing w/ Haversine function, your dlon is different. That answer has
let dlon = targetedPointlongitude - lonuserlocatioc
I don't know if that's your problem but it looks odd.
Swift function like this;
func radians(n: Double) -> Double{
return n * (M_PI / 180);
}
func degrees(n: Double) -> Double{
return n * (180 / M_PI);
}
func logC(val:Double,forBase base:Double) -> Double {
return log(val)/log(base);
}
func getBearing(startLat: Double,startLon:Double,endLat: Double,endLon: Double) -> Double{
var s_LAT: Double , s_LON: Double, e_LAT: Double, e_LON: Double, d_LONG: Double, d_PHI: Double;
s_LAT = startLat;
s_LON = startLon;
e_LAT = endLat;
e_LON = endLon;
d_LONG = e_LON - s_LON;
d_PHI = logC(tan(e_LAT/2.0+M_PI/4.0)/tan(s_LAT/2.0+M_PI/4.0),forBase :M_E);
if (abs(d_LONG)>M_PI){
if(d_LONG>0.0){
d_LONG = -(2.0 * M_PI - d_LONG);
}else{
d_LONG = (2.0 * M_PI - d_LONG);
}
}
return degrees(atan2(d_LONG, d_PHI)+360.0)%360.0;
}

Calculation that calculates the new latitude and longitude based on a starting point, bearing and distance?

Does anyone have a calculation that calculates the new
latitude and longitude based on a starting point, bearing and
distance?
I would greatly appreciate any help people might have.
I've used the code from Calculate new coordinate x meters and y degree away from one coordinate:
- (CLLocationCoordinate2D)coordinateFromCoord:(CLLocationCoordinate2D)fromCoord
atDistanceKm:(double)distanceKm
atBearingDegrees:(double)bearingDegrees
{
double distanceRadians = distanceKm / 6371.0;
//6,371 = Earth's radius in km
double bearingRadians = [self radiansFromDegrees:bearingDegrees];
double fromLatRadians = [self radiansFromDegrees:fromCoord.latitude];
double fromLonRadians = [self radiansFromDegrees:fromCoord.longitude];
double toLatRadians = asin(sin(fromLatRadians) * cos(distanceRadians)
+ cos(fromLatRadians) * sin(distanceRadians) * cos(bearingRadians) );
double toLonRadians = fromLonRadians + atan2(sin(bearingRadians)
* sin(distanceRadians) * cos(fromLatRadians), cos(distanceRadians)
- sin(fromLatRadians) * sin(toLatRadians));
// adjust toLonRadians to be in the range -180 to +180...
toLonRadians = fmod((toLonRadians + 3*M_PI), (2*M_PI)) - M_PI;
CLLocationCoordinate2D result;
result.latitude = [self degreesFromRadians:toLatRadians];
result.longitude = [self degreesFromRadians:toLonRadians];
return result;
}
- (double)radiansFromDegrees:(double)degrees
{
return degrees * (M_PI/180.0);
}
- (double)degreesFromRadians:(double)radians
{
return radians * (180.0/M_PI);
}
Or in Swift:
extension CLLocationCoordinate2D {
func adjusted(distance: Double, degrees: Double) -> CLLocationCoordinate2D {
let distanceRadians = distance / 6_371 // 6,371 == Earth's radius in km
let bearingRadians = degrees.radians
let fromLatRadians = latitude.radians
let fromLonRadians = longitude.radians
let toLatRadians = asin(sin(fromLatRadians) * cos(distanceRadians) + cos(fromLatRadians) * sin(distanceRadians) * cos(bearingRadians))
var toLonRadians = fromLonRadians + atan2(sin(bearingRadians)
* sin(distanceRadians) * cos(fromLatRadians), cos(distanceRadians)
- sin(fromLatRadians) * sin(toLatRadians))
// adjust toLonRadians to be in the range -180 to +180...
toLonRadians = fmod((toLonRadians + 3 * .pi), (2 * .pi)) - .pi
return CLLocationCoordinate2D(latitude: toLatRadians.degrees, longitude: toLonRadians.degrees)
}
}
extension CLLocationDegrees {
var radians: Double { self * .pi / 180 }
}
extension Double {
var degrees: CLLocationDegrees { self * 180 / .pi }
}
You will find all the calculations you could possibly want (including explanations etc) at http://www.movable-type.co.uk/scripts/latlong.html
The code you need is (in JavaScript) under the heading "Destination point given distance and bearing from start point". Excerpting:
var lat2 = Math.asin( Math.sin(lat1)*Math.cos(d/R) +
Math.cos(lat1)*Math.sin(d/R)*Math.cos(brng) );
var lon2 = lon1 + Math.atan2(Math.sin(brng)*Math.sin(d/R)*Math.cos(lat1),
Math.cos(d/R)-Math.sin(lat1)*Math.sin(lat2));
Where R = radius of the earth, d = distance (in same units), and lat/long are in radians (since that's what the sin function expects). You go from degrees to radians with
radians = pi * degrees / 180;
You should be able to take it from here. Do look at the link I gave for more info.

How to check if MKCoordinateRegion contains CLLocationCoordinate2D without using MKMapView?

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?
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;
}
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.
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);
}
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)
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];
}
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)
}
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!
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
}
}
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
)
}
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)
}
}