Calculating Heading with CoreLocation

It’s fairly easy to get the heading of the user as they move in an iPhone app. More difficult however, is how to find the heading between two specific points that the user may or may not be at. In my case, this was in order to let the user pick a specific point, walk away from it, then have the device point the user back towards the point. I decided to tackle the problem using some vector mathematics.

So first we’ll have the creation of the code, then the math later to explain how it works:

1. First, we need to make sure we can work with CLLocations. This is done by adding the CLLocation framework to your project, which you can figure out how to do at a number of places. Just be sure not to copy the files into your directory, as it will ruin everything. Trust me.

2. Define a quick function in our class.h file to go from radians to degrees to make our lives easier.


#define RADIANS_TO_DEGREES(radians) ((radians) * (180.0 / M_PI))

3. First, we need a vector that will serve as our “North Vector”. This will allow us to calculate angles based off of it. All we need to do is create a point due north of one of our points by just copying the coordinates and increasing the latitude just a little.

Our method header will be this:

-(float) bearingBetweenStartLocation:(CLLocation *)startLocation andEndLocation:(CLLocation *)endLocation
{

}

And inside of it:

CLLocation *northPoint = [[[CLLocation alloc] initWithLatitude:(startLocation.coordinate.latitude)+.01 longitude:endLocation.coordinate.longitude] autorelease];

Then, we need to calculate a couple vector magnitudes. This can be done very easily using the -distanceFromLocation method in CLLocation.


float magA = [northPoint distanceFromLocation:startLocation];
float magB = [endLocation distanceFromLocation:startLocation];

Next we’ll be taking a dot product, but because one of our vectors is due north, this is pretty easy to code. I created a couple spare CLLocation objects with the same latitude as my points but on the same longitude, in order to get the y component of my vector between my points. (If you aren’t very familiar with vectors, don’t worry, it’s just the math behind what I’m doing.) The code is quite simple:


CLLocation *startLat = [[[CLLocation alloc] initWithLatitude:startLocation.coordinate.latitude longitude:0] autorelease];
CLLocation *endLat = [[[CLLocation alloc] initWithLatitude:endLocation.coordinate.latitude longitude:0] autorelease];
float aDotB = magA*[endLat distanceFromLocation:startLat];

Now if you’ll just trust me a bit on the math for a minute, all that’s left now is a very simple:


return RADIANS_TO_DEGREES(acosf(aDotB/(magA*magB))); //You can add 180 to this if you need a reversed direction.

//acosf() is a built in function to calculate the arccosine.

So overall, your function will look like so:


-(float) bearingBetweenStartLocation:(CLLocation *)startLocation andEndLocation:(CLLocation *)endLocation{

CLLocation *northPoint = [[[CLLocation alloc] initWithLatitude:(startLocation.coordinate.latitude)+.01 longitude:endLocation.coordinate.longitude] autorelease];
float magA = [northPoint distanceFromLocation:startLocation];
float magB = [endLocation distanceFromLocation:startLocation];
CLLocation *startLat = [[[CLLocation alloc] initWithLatitude:startLocation.coordinate.latitude longitude:0] autorelease];
CLLocation *endLat = [[[CLLocation alloc] initWithLatitude:endLocation.coordinate.latitude longitude:0] autorelease];
float aDotB = magA*[endLat distanceFromLocation:startLat];
return RADIANS_TO_DEGREES(acosf(aDotB/(magA*magB)));
}

And that’s all you need to get the heading! If you want to have something rotate with the user, you would simply get the user’s heading via CoreLocationManager, and just subtract that from the calculated heading here.

For those who would like to see how the math works out:

My implementation makes use of the Vector Dot Product.

http://en.wikipedia.org/wiki/Dot_product#Definition

The most important part of that article is the geometric interpretation:

theta=arccos left( frac {bold{a}cdotbold{b}} {left|bold{a}right|left|bold{b}right|}right)

where theta (the funny-looking zero), is the angle between two vectors, a and b, and ||x|| refers to the magnitude (or length) of the vector, and a (dot) b is the dot product.
AND

mathbf{a}cdot mathbf{b} = sum_{i=1}^n a_ib_i = a_1b_1 + a_2b_2 + cdots + a_nb_n

This formula is used to calculate the dot product, and while it looks complex, its fairly simple in our implementation.

So basically, the key to this is to have two vectors. Since you want to measure degrees from north, you need one vector pointing north.

The second vector is simply that between your start and ending points (which could be the user’s location and the destination).

Calculating magnitudes of vectors is quite easy using the -distanceFromLocation: method in CLLocation.

The dot product of vectors a and b is a bit more complicated, but it turns out that with one vector pointing due north, the calculation simplifies to a dot b = ||a|| * the y component of b. (which can be calculated with a couple spare CLLocations on the same longitude).

A few simple calculations from there gives us the angle between two points and a north vector.

Diagram of Vectors

Vector Diagram used for Calculations

Trackbacks/Pingbacks

  1. calculating heading with corelocation | designoMatt - January 16, 2012

    […] calculating heading with corelocation It’s fairly easy to get the heading of the user as they move in an iPhone app. More difficult however, is how to find the heading between two specific points that the user may or may not be at. In my case, this was in order to let the user pick a specific point, walk away from it, then have the device point the user back towards the point. I decided to tackle the problem using some vector mathematics. via shawnsbits.com […]

Leave a Reply