23

I'm trying to develop my own augmented reality engine.

Searching on internet, I've found this useful tutorial. Reading it I see that the important thing is bearing between user location, point location and north.

The following picture is from that tutorial.

enter image description here

Following it, I wrote an Objective-C method to obtain beta:

+ (float) calculateBetaFrom:(CLLocationCoordinate2D)user to:(CLLocationCoordinate2D)destination
{
    double beta = 0;
    double a, b = 0;

    a = destination.latitude - user.latitude;
    b = destination.longitude - user.longitude;

    beta = atan2(a, b) * 180.0 / M_PI;
    if (beta < 0.0)
        beta += 360.0;
    else if (beta > 360.0)
        beta -= 360;

    return beta;
}

But, when I try it, it doesn't work very well.

So, I checked iPhone AR Toolkit, to see how it works (I've been working with this toolkit, but it is so big for me).

And, in ARGeoCoordinate.m there is another implementation of how to obtain beta:

- (float)angleFromCoordinate:(CLLocationCoordinate2D)first toCoordinate:(CLLocationCoordinate2D)second {

    float longitudinalDifference    = second.longitude - first.longitude;
    float latitudinalDifference     = second.latitude  - first.latitude;
    float possibleAzimuth           = (M_PI * .5f) - atan(latitudinalDifference / longitudinalDifference);

    if (longitudinalDifference > 0) 
        return possibleAzimuth;
    else if (longitudinalDifference < 0) 
        return possibleAzimuth + M_PI;
    else if (latitudinalDifference < 0) 
        return M_PI;

    return 0.0f;
}

It uses this formula:

float possibleAzimuth = (M_PI * .5f) - atan(latitudinalDifference / longitudinalDifference);

Why is (M_PI * .5f) in this formula? I don't understand it.

And continue searching, I've found another page talking about how to calculate distance and bearing of 2 locations. In this page there is another implementation:

/**
 * Returns the (initial) bearing from this point to the supplied point, in degrees
 *   see http://williams.best.vwh.net/avform.htm#Crs
 *
 * @param   {LatLon} point: Latitude/longitude of destination point
 * @returns {Number} Initial bearing in degrees from North
 */
LatLon.prototype.bearingTo = function(point) {
  var lat1 = this._lat.toRad(), lat2 = point._lat.toRad();
  var dLon = (point._lon-this._lon).toRad();

  var y = Math.sin(dLon) * Math.cos(lat2);
  var x = Math.cos(lat1)*Math.sin(lat2) -
          Math.sin(lat1)*Math.cos(lat2)*Math.cos(dLon);
  var brng = Math.atan2(y, x);

  return (brng.toDeg()+360) % 360;
}

Which one is the right one?

VansFannel
  • 45,055
  • 107
  • 359
  • 626

8 Answers8

25

Calculate bearing

//Source
JSONObject source = step.getJSONObject("start_location");
double lat1 = Double.parseDouble(source.getString("lat"));
double lng1 = Double.parseDouble(source.getString("lng"));

// destination
JSONObject destination = step.getJSONObject("end_location");
double lat2 = Double.parseDouble(destination.getString("lat"));
double lng2 = Double.parseDouble(destination.getString("lng"));

double dLon = (lng2-lng1);
double y = Math.sin(dLon) * Math.cos(lat2);
double x = Math.cos(lat1)*Math.sin(lat2) - Math.sin(lat1)*Math.cos(lat2)*Math.cos(dLon);
double brng = Math.toDegrees((Math.atan2(y, x)));
brng = (360 - ((brng + 360) % 360));

Convert Degrees into Radians

Radians = Degrees * PI / 180

Convert Radians into Degrees

Degrees = Radians * 180 / PI
Kirit Vaghela
  • 12,572
  • 4
  • 76
  • 80
  • You convert the value to degrees, but isn't Latitude and Longitude in degrees? Shouldn't you convert them to radians first? – ravemir May 05 '13 at 16:14
  • I dont convert the latitude and Longitude in radius. I don't think that it make any difference – Kirit Vaghela May 06 '13 at 04:52
  • Do you tried online bearing match for testing? for ex. test on by this link. http://www.sunearthtools.com/tools/distance.php – Vishal Jadav Feb 24 '17 at 07:27
  • Comparing to the tool above^, this does not give a correct result. Answer here gives the correct bearing: https://stackoverflow.com/a/42619865/2063246. – Halogen Jan 30 '23 at 06:40
18

I know this question is old, but here is an easier solution:

float bearing = loc1.bearingTo(loc2);

Jeremie D
  • 4,145
  • 1
  • 35
  • 41
7

Try this for accurate result:

private static double degreeToRadians(double latLong) {
    return (Math.PI * latLong / 180.0);
}

private static double radiansToDegree(double latLong) {
    return (latLong * 180.0 / Math.PI);
}

public static double getBearing() {

//Source
JSONObject source = step.getJSONObject("start_location");
double lat1 = Double.parseDouble(source.getString("lat"));
double lng1 = Double.parseDouble(source.getString("lng"));

// destination
JSONObject destination = step.getJSONObject("end_location");
double lat2 = Double.parseDouble(destination.getString("lat"));
double lng2 = Double.parseDouble(destination.getString("lng"));

    double fLat = degreeToRadians(lat1);
    double fLong = degreeToRadians(lng1);
    double tLat = degreeToRadians(lat2);
    double tLong = degreeToRadians(lng2);

    double dLon = (tLong - fLong);

    double degree = radiansToDegree(Math.atan2(sin(dLon) * cos(tLat),
            cos(fLat) * sin(tLat) - sin(fLat) * cos(tLat) * cos(dLon)));

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

You can test bearing result on http://www.sunearthtools.com/tools/distance.php .

Vishal Jadav
  • 924
  • 9
  • 11
  • I've implemented the math portion of this (starting with double dLon =) and when I calculate reciprocal headings, the results aren't +/- 180*, but rather +/- 180* +/- 0.25*. Am I the only one seeing this issue? Is it just how the math is (imprecise) or did I do something wrong? – ManEatingCheese Feb 14 '19 at 01:23
5

In the formula

float possibleAzimuth = (M_PI * .5f) - atan(latitudinalDifference / longitudinalDifference);

the term (M_PI * .5f) means π/2 which is 90°. That means that it is the same formula that you stated at first, because regarding to the figure above it holds

β = arctan (a/b) = 90° - arctan(b/a).

So both formulas are similar if a refers to the difference in longitude and b in the difference in latitude. The last formula calculates again the same using the first part of my equation.

Palund
  • 147
  • 7
  • You refer to `a` as difference in lat and `b` as difference in long. See my response for how `a` should actually the difference in long and `b` the difference in lat. It might be useful to edit your answer to be consistent with the diagram so we don't confuse future readers any more than necessary? – Dolbz Nov 14 '11 at 15:21
  • Thank you, Dolbz, you are right, a and b should be changed. I edited my main answer in order to have a right one. :) – Palund Nov 14 '11 at 16:02
2

a in the diagram is the longitude difference, b is the latitude difference therefore in the method you have written you've got them the wrong way round.

a = destination.latitude - user.latitude; // should be b
b = destination.longitude - user.longitude; // should be a

Try switching them and see what happens.

See Palund's response for answers to the rest of your questions.

Dolbz
  • 2,078
  • 1
  • 16
  • 25
  • Thanks, you are right: I've make a mistake with a and b. I've switched them, and I get beta = 9 degrees. But, if I use LatLon.prototype.bearingTo, with the same locations, I get beta = 7 degrees. I think the second one is more accurate, but I don't understand it isn't implemented in iPhone AR Toolkit. Thanks again. – VansFannel Nov 14 '11 at 15:53
  • This solve the issue. Just use arctan(b, a) instead of (a,b). – Juan Carlos Oropeza Jul 10 '14 at 22:24
1

/* Kirit vaghela answer has been modified.. Math.sin gives the radian value so to get degree value we need to pass Math.toRadians(value) inside Math.sin() or Math.cos() */

    double lat1 = 39.099912;
    double lat2 = 38.627089;
    double lng1 = -94.581213;
    double lng2 = -90.200203;

    double dLon = (lng2-lng1);
    double x = Math.sin(Math.toRadians(dLon)) * Math.cos(Math.toRadians(lat2));
    double y = Math.cos(Math.toRadians(lat1))*Math.sin(Math.toRadians(lat2)) - Math.sin(Math.toRadians(lat1))*Math.cos(Math.toRadians(lat2))*Math.cos(Math.toRadians(dLon));
    double bearing = Math.toDegrees((Math.atan2(x, y)));
    System.out.println("BearingAngle : "+bearing);
roshan posakya
  • 1,010
  • 10
  • 14
0

inputs are in degrees.

#define PI 3.14159265358979323846
#define RADIO_TERRESTRE 6372797.56085
#define GRADOS_RADIANES PI / 180
#define RADIANES_GRADOS 180 / PI

double calculateBearing(double lon1, double lat1, double lon2, double lat2)
{
    double longitude1 = lon1;
    double longitude2 = lon2;
    double latitude1 = lat1 * GRADOS_RADIANES;
    double latitude2 = lat2 * GRADOS_RADIANES;
    double longDiff= (longitude2-longitude1) * GRADOS_RADIANES;
    double y= sin(longDiff) * cos(latitude2);
    double x= cos(latitude1) * sin(latitude2) - sin(latitude1) * cos(latitude2) * cos(longDiff);
    
    // std::cout <<__FILE__ << "." << __FUNCTION__ << " line:" << __LINE__ << "  "
    
    return fmod(((RADIANES_GRADOS *(atan2(y, x)))+360),360);
}
M.Hefny
  • 2,677
  • 1
  • 26
  • 30
0

If you want you can take a look at the code used in mixare augmented reality engine, it's on github and there's an iPhone version as well: github.com/mixare

Daniele
  • 1,005
  • 9
  • 26
  • Thanks. I'm seeing now that you have worked with this ar browser. Could you help me to find which class containing bearing calculations? – VansFannel Nov 15 '11 at 21:34
  • 1
    Hi, I'm not the developer of the iPhone version but according to the class documentation available at: http://code.google.com/p/mixare/wiki/FileResponsibility it swhould be class AugmentedViewController. Source is: https://github.com/mixare/mixare-iphone/blob/master/Classes/gui/tabbarControllers/augmentedView/AugmentedViewController.m – Daniele Nov 16 '11 at 14:49