Information about how to read smooth and correct compass values from Android devices is sparse. This guide is the result from our experiences with the Android compass while working on the navigation feature of our City Guide app.

The first part of the blog post describes the characteristics of the implementation and what we wanted to achieve. The second part focuses on code.

1. Implementation Characteristics

We want to show the user heading on a map. The heading angle should be dependent on the heading of the phone. We used the Mapbox Android SDK, which at the time of this writing only displays the heading based on the GPS data that unfortunately only updates the orientation while the device is moving. However, what we wanted, was realtime display of the heading of the phone, just as Google Maps does. So we had to add our own code to show the proper direction.

It turned out, that this is not quite as simple as just reading some compass values from Androids sensors, there were some hurdles to pass. Luckily, most of the problems are not that hard to solve, if you know about them. So, what are those problems?

1.1 We want the values to true north, not the magnetic north

The raw compass values (as most other tutorials use) are values pointing towards magnetic north, which is not what you usually want. You usually want the bearing to true north – also known as the geographic north pole. The offset of the magnetic north to the true north is named declination. The magnetic north is not a fixed point, but wanders around up to 40 km per year, thus declination not only depends on the location on earth but also on the current time.

Retrieving the declination is relatively easy in Android. There are mathematical models for this, such as the WMM-2010 model, which is implemented in Androids GeomagneticField helper class.

The variation between magnetic north (Nm) and “true” north (Ng) [Source: Wikipedia]

1.2 We want smoothed values

The angle to magnetic north (called azimuth in this post), is calculated based on the values of the magnetic field sensor and the accelerometer sensor. But they are pretty noisy, so depending on what you want to achieve, the UI might not look that great with a hopping compass needle. In our case of navigating on the map, this is also true.

There are multiple ways to prevent the UI from going crazy. Our solution is to calculate the mean of the last 10 values read from the sensors. This solution works for us, because the sensors are pushing out values really fast and it doesn’t really disturb the user when the heading updates are a bit delayed.

1.3 We want throttled change notifications

We don’t want to stress the UI too much, so we throttle the notifications for changes on the bearing to be not sent more than once every 50 milliseconds. Thats more than enough for our case.

2. Implementation

2.1 Required Permissions in AndroidManfest.xml

As described in 1.1. we need to know where we are on earth. So before we need to set some permissions in our AndroidManifest.xml to be able to read GPS sensors.

2.2 The BearingToNorthProvider class

This utility class keeps track of the device bearing and notifies the client for changes. It uses accelerometer and magnetic field sensors to keep track of the angle to magnetic north (azimuth). It also uses the location manager to update the current location from the GPS or WiFi (whatever is available). The azimuth and location are then combined to calculate the final bearing number.

2.3 Mean of circular quantities

Here is the implementation for the AverageAngle class, that helps us calculating the mean for the angle as described in 1.2. We use the formula found on Wikipedia. You simply pass the number of frames into the constructor that will be used for calculating the mean. Then you start putting angles into the class with the putValue method. As soon as the number of frames is reached, the oldest value will be overridden with the new one, so it’s basically a FIFO.

3 Usage example

To use the class, simply instantiate the BearingToNorthProvider, optionally set a changeListener and then start it up. Example Activity:

A working example is also available on GitHub.

0 Kommentare

Einen Kommentar abschicken

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *