Android Compass orientation on unreliable (Low pass filter)


Im creating an application where i need to position a ImageView depending on the Orientation of the device. I use the values from a MagneticField and Accelerometer Sensors to calculate the device orientation with

SensorManager.getRotationMatrix(rotationMatrix, null, accelerometerValues, magneticFieldValues)
SensorManager.getOrientation(rotationMatrix, values);
double degrees = Math.toDegrees(values[0]);

My problem is that the positioning of the ImageView is very sensitive to changes in the orientation. Making the imageview constantly jumping around the screen. (because the degrees change)

I read that this can be because my device is close to things that can affect the magneticfield readings. But this is not the only reason it seems.

I tried downloading some applications and found that the "3D compass" and "Compass" remains extremely steady in its readings (when setting the noise filter up), i would like the same behavior in my application.

I read that i can tweak the "noise" of my readings by adding a "Low pass filter", but i have no idea how to implement this (because of my lack of Math).

Im hoping someone can help me creating a more steady reading on my device, Where a little movement to the device wont affect the current orientation. Right now i do a small

if (Math.abs(lastReadingDegrees - newReadingDegrees) > 1) { updatePosition() }

To filter abit of the noise. But its not working very well :)

1/15/2011 12:40:21 PM

Accepted Answer

Though I havn't used the compass on Android, the basic processing shown below (in JavaScript) will probably work for you.

It's based on the low pass filter on the accelerometer that's recommended by the Windows Phone team with modifications to suit a compass (the cyclic behavior every 360").

I assume the compass reading is in degrees, a float between 0-360, and the output should be similar.

You want to accomplish 2 things in the filter:

  1. If the change is small, to prevent gitter, gradually turn to that direction.
  2. If the change is big, to prevent lag, turn to that direction immediatly (and it can be canceled if you want the compass to move only in a smooth way).

For that we will have 2 constants:

  1. The easing float that defines how smooth the movement will be (1 is no smoothing and 0 is never updating, my default is 0.5). We will call it SmoothFactorCompass.
  2. The threshold in which the distance is big enough to turn immediatly (0 is jump always, 360 is never jumping, my default is 30). We will call it SmoothThresholdCompass.

We have one variable saved across the calls, a float called oldCompass and it is the result of the algorithm.

So the variable defenition is:

var SmoothFactorCompass = 0.5;
var SmoothThresholdCompass = 30.0;
var oldCompass = 0.0;

and the function recieves newCompass, and returns oldCompass as the result.

if (Math.abs(newCompass - oldCompass) < 180) {
    if (Math.abs(newCompass - oldCompass) > SmoothThresholdCompass) {
        oldCompass = newCompass;
    else {
        oldCompass = oldCompass + SmoothFactorCompass * (newCompass - oldCompass);
else {
    if (360.0 - Math.abs(newCompass - oldCompass) > SmoothThresholdCompass) {
        oldCompass = newCompass;
    else {
        if (oldCompass > newCompass) {
            oldCompass = (oldCompass + SmoothFactorCompass * ((360 + newCompass - oldCompass) % 360) + 360) % 360;
        else {
            oldCompass = (oldCompass - SmoothFactorCompass * ((360 - newCompass + oldCompass) % 360) + 360) % 360;

I see that the issue was opened 5 months ago and probably isn't relevant anymore, but I'm sure other programmers might find it useful.

Oded Elyada.

6/24/2011 1:51:10 AM

This lowpass filter works for angles in radians. Use the add function for each compass reading, then call average to get the average.

public class AngleLowpassFilter {

    private final int LENGTH = 10;

    private float sumSin, sumCos;

    private ArrayDeque<Float> queue = new ArrayDeque<Float>();

    public void add(float radians){

        sumSin += (float) Math.sin(radians);

        sumCos += (float) Math.cos(radians);


        if(queue.size() > LENGTH){

            float old = queue.poll();

            sumSin -= Math.sin(old);

            sumCos -= Math.cos(old);

    public float average(){

        int size = queue.size();

        return (float) Math.atan2(sumSin / size, sumCos / size);

Use Math.toDegrees() or Math.toRadians() to convert.

Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow