CLLocation Category for Calculating Bearing w/ Haversine function

Viewed 12048

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);
}
8 Answers

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