I've been evaluating the location sensors in a number of Windows 8 tablet and ultrabook systems recently, and as you might expect I have come across the occasional buggy driver. Sometimes the bugs are severe enough that the location sensor doesn't function reliably or even function at all, but I have seen cases where the sensor returns partial data in the Geocoordinate class. To pick a recent example, the other day I was testing a tablet that embeds a GNSS: a Global Navigation Satellite System receiver, which is capable of using GPS, GLONASS, Galileo, or Beidou navigation systems. This tablet should have provided complete location data to Windows Store apps that include position, speed, altitude, heading, and so on, but when I ran my tests I got results like this:
45.54203 N, -122.96043 E, 109 m Alt, 1.2 m/s, <null> deg, +/-32 m, +/-10 m Alt
Notice the part where the heading is reported as "<null> deg". This is an example of a bug where the driver is returning incomplete data: we have a position and altitude, but even though we have a speed we don't have a heading.
The bad news is that, as a developer, you will have to deal with this in your apps. The tablet I was using was a consumer device purchased at retail and it shipped with a driver that has a bug, and any location-aware app running on this system will not report a valid heading. You don't have any control over who will install your app, or where, and it's very possible (if not guaranteed) that it will get run on systems with buggy drivers. The end user won't know or care why your app doesn't work properly: they'll just know that it doesn't.
The good news is, little problems like this one are pretty easy to work around. It just requires a little program logic and a bit of math.
How to calculate heading when the location provider doesn't
The first step is, of course, to determine whether or not the system you are running on has a problem. How do you know if you are not getting proper heading information? You run both of the following tests once you get a valid position fix:
- The Heading field of the Geocoordinate object should be non-NULL if the speed is non-zero (and non-NULL). In plain English, if you are moving then you should have a valid heading.
- Ensure that the reported Heading is sensible.
The first test is pretty easy, and might look something like this in C#:
if ( coordinate.Speed > 0 && coordinate.Heading == NULL ) broken_heading= true;
The second test is a bit more involved: you must first calculate the heading value yourself, and then compare it to the reported value. If the two are off by a significant margin, then the system's driver might be reporting bogus data. In theory, your calculated heading and the software driver's reported heading should be very close since they use the same method (more on that below), but in practice the embedded GPS might be employing some filtering or other post-processing of the data, or doing its calculations at a slightly different time. To play it safe, you should validate the heading information only when it is not in flux (i.e., the user appears to be moving in a striaght line).
If both tests fail, then you should calculate heading yourself and present that value to the user.
GPS devices calculate their heading from the last two position reports: they take where you were before and where you are now, and find the bearing from the former to the latter. Unlike an magnetic or electronic compass, a GPS compass requires the device to be moving, which is why GPS devices report heading-- a direction of movement-- specifically, and not a bearing. Since the GPS is just looking at the previous and current position reports to determine the heading, you can do the same thing and arrive at, in theory, the exact same value.
In my blog post "Calculating a bearing between points in location-aware apps" I provide a method for calculating a bearing between two points that we can use to get our heading, but that's really overkill in this situation. Most GPS devices update their position every 1 or 2 seconds, and a real-world object-- even a high speed one such as an airplane-- travels a very short distance relative to the circumference of the earth in such a short amount of time. That means we can use a local, flat earth approximation and get our result using fewer trig functions.
Given latitudes Φ1 and Φ2, and longitudes λ1 and λ2 (you'll need to convert them to radians):
Θ = atan2( sin(Φ1) * Δλ, ΔΦ)
This function will return the angle in radians from -π to π but what we want is an angle in degrees from 0 to 360. To accomplish this, we convert to degrees, add 360, and take the modulo 360:
Θd = ( Θ * 180 / π + 360 ) % 360
I compared the results of this formula with the output from a working location driver (for clarity, I have limited the coordinates to 3 decimal places):
As you can see, the heading values line up quite well, to within a degree in most instances. Only when the heading was changing rapidly was there a significant variation.
What this shows is that with just a few additions to your code you can detect, and compensate for, buggy location drivers that don't properly report a heading to the Location API. It's extra work, yes, but it's work that will pay off when, invariably, your app runs on an affected system. This extra level of robustness can mean the difference between an app that works properly and one that does not, and it might just give your app an edge over your competitors'.