38

Very "simple" problem: given two CLLocationCoordinate2Ds, how can I get the bearing (as radians) from the first to the second? I've done a lot of research and studying on this, both the general problem and Objective-C/Cocoa Touch/iOS specifically.

Here's my implementation:

- (float) getHeadingForDirectionFromCoordinate:(CLLocationCoordinate2D)fromLoc toCoordinate:(CLLocationCoordinate2D)toLoc
{
    float fLat = fromLoc.latitude;
    float fLng = fromLoc.longitude;
    float tLat = toLoc.latitude;
    float tLng = toLoc.longitude;

    return atan2(sin(fLng-tLng)*cos(tLat), cos(fLat)*sin(tLat)-sin(fLat)*cos(tLat)*cos(fLng-tLng));         
}

However, this method isn't returning consistant results for me. If the bearing is close to due north or due south, it seems to be fine, however, any other direction seems to return inconsistant data, for example:

From 50.405018, 8.437500

To 51.339802, 12.403340

My method returns: 5.918441 radians

Should be 1.18660576 radians

(see http://www.movable-type.co.uk/scripts/latlong.html and http://www.movable-type.co.uk/scripts/latlong-map.html?lat1=50.405018&long1=8.437500&lat2=51.339802&long2=12.403340)

I've double and triple checked the formula is correct. I've also spot checked a bunch of values like the example above, some correct, some wrong. I've played around with various modulos or bounding of the return value, also no luck.

Any ideas? Is there an issue with my code? Maybe I've misunderstood something about how math functions work?

Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
James J
  • 6,428
  • 6
  • 35
  • 45

4 Answers4

54

Here the code modified with the changes suggested by Oren Trutner and from myself:

#define degreesToRadians(x) (M_PI * x / 180.0)
#define radiansToDegrees(x) (x * 180.0 / M_PI)

- (float)getHeadingForDirectionFromCoordinate:(CLLocationCoordinate2D)fromLoc toCoordinate:(CLLocationCoordinate2D)toLoc
{
    float fLat = degreesToRadians(fromLoc.latitude);
    float fLng = degreesToRadians(fromLoc.longitude);
    float tLat = degreesToRadians(toLoc.latitude);
    float tLng = degreesToRadians(toLoc.longitude);

    float degree = radiansToDegrees(atan2(sin(tLng-fLng)*cos(tLat), cos(fLat)*sin(tLat)-sin(fLat)*cos(tLat)*cos(tLng-fLng)));

    if (degree >= 0) {
        return degree;
    } else {
        return 360+degree;
    }
}
heinst
  • 8,520
  • 7
  • 41
  • 77
megabri
  • 881
  • 8
  • 11
  • 4
    shouldn't these be doubles instead of floats..? – Sjoerd Perfors Dec 19 '14 at 10:21
  • 1
    What is the if statement at the end for? How can the angle be less than 0? Also whats the point of the else statement, why would the angle be more than 360?? – Supertecnoboff Mar 10 '17 at 10:30
  • remember to only calculate this / change the car picture rotation when the new and old positions are not the same. Otherwise, the rotation between the same positions is 0 and the car icon will keep rotating on the spot. – Lenka Pitonakova Jun 21 '17 at 16:48
  • `cos(tLat)` is called twice, could be extracted into a precomputed variable. – Desmond Hume Aug 03 '20 at 16:09
31

Your math is correct, with the following exceptions:

  1. Make sure to convert fLat, fLon, tLat, and tLon to radians before applying any sin() or cos() to them. Divide by 180.0 and multiply by PI.

  2. Enter the delta between tLng and fLng as tLng-fLng, and not the other way around. Note that this difference appears twice in the expression.

With those changes, I am getting 1.18660677830947 radians with double precision math and the values in the question.

Oren Trutner
  • 23,752
  • 8
  • 54
  • 55
  • 1
    My initial reaction was "convert latitude/longitude to radians? what manner of madness is this?" Then I actually tried it, works like a charm. – James J Sep 29 '10 at 05:20
  • 1
    Good point. The use of degrees in geographic latitude and longitude is so ingrained, that it's easy to forget that they describe angles in the sphere geometry of the Earth. The trig functions in most modern languages expect radians, forcing the conversion. – Oren Trutner Sep 30 '10 at 05:34
  • Can you explain 2? What if fLng is larger? – Nick Oct 12 '10 at 11:02
  • if fLng>tLng than tLng-fLng will be a negative number. This is okay! The arithmetic will still work fine in that case -- in fact, having the negative value is vital for the correctness of the result. – Oren Trutner Oct 12 '10 at 20:36
10

Swift 3:

extension CLLocationCoordinate2D {
    func bearing(to point: CLLocationCoordinate2D) -> Double {
        func degreesToRadians(_ degrees: Double) -> Double { return degrees * Double.pi / 180.0 }
        func radiansToDegrees(_ radians: Double) -> Double { return radians * 180.0 / Double.pi }

        let lat1 = degreesToRadians(latitude)
        let lon1 = degreesToRadians(longitude)

        let lat2 = degreesToRadians(point.latitude);
        let lon2 = degreesToRadians(point.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(radiansBearing)
    }
}
Valeriy Van
  • 1,851
  • 16
  • 19
Sahil Kapoor
  • 11,183
  • 13
  • 64
  • 87
-1

you can use my code.. it's work on my project with microcontroller that use GPS for data.

#define d2r ((22/7.0)/180.0)
#define r2d (180.0/(22/7.0))

double get_heading1(double lat1, double long1, double lat2, double long2)  
{
    double diff_lat, diff_long;
    double degree;

    diff_long =(double) (((long2*1000000)-(long1*1000000))/1000000) * d2r;
    diff_lat = (double) (((lat2*1000000)-(lat1*1000000))/1000000) * d2r;     

    degree = r2d     (atan2(sin(diff_long)*cos(d2r*lat2),cos(d2r*lat1)*sin(d2r*lat2)-sin(d2r*lat1)*cos(d2r*lat2)    *cos(diff_long)));

    if (degree >= 0) {
        return degree;
    } else {
        return 360+degree;
    }                                                                 
}