Search

Sunday, January 9, 2011

Using orientation sensors: Simple Compass sample

In this post we will show how to get the sensor informatión of the device to do a simple compass application.

Getting the magnetic orientation with the TYPE_ORIENTATION sensor is deprecated, and now you must subscribe to TYPE_ACCELEROMETER and TYPE_MAGNETIC_FIELD to get the correct orientation. This way is a little tricky, but with this sample you will get it very easy.

This is the code for an activity that shows in a canvas a simple compass:


package com.samplecompass;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.view.View;

public class CompassActivity extends Activity implements SensorEventListener {

Float azimut; // View to draw a compass

public class CustomDrawableView extends View {
Paint paint = new Paint();
public CustomDrawableView(Context context) {
super(context);
paint.setColor(0xff00ff00);
paint.setStyle(Style.STROKE);
paint.setStrokeWidth(2);
paint.setAntiAlias(true);
};

protected void onDraw(Canvas canvas) {
int width = getWidth();
int height = getHeight();
int centerx = width/2;
int centery = height/2;
canvas.drawLine(centerx, 0, centerx, height, paint);
canvas.drawLine(0, centery, width, centery, paint);
// Rotate the canvas with the azimut
if (azimut != null)
canvas.rotate(-azimut*360/(2*3.14159f), centerx, centery);
paint.setColor(0xff0000ff);
canvas.drawLine(centerx, -1000, centerx, +1000, paint);
canvas.drawLine(-1000, centery, 1000, centery, paint);
canvas.drawText("N", centerx+5, centery-10, paint);
canvas.drawText("S", centerx-10, centery+15, paint);
paint.setColor(0xff00ff00);
}
}

CustomDrawableView mCustomDrawableView;
private SensorManager mSensorManager;
Sensor accelerometer;
Sensor magnetometer;

protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mCustomDrawableView = new CustomDrawableView(this);
setContentView(mCustomDrawableView); // Register the sensor listeners
mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
accelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
magnetometer = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
}

protected void onResume() {
super.onResume();
mSensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_UI);
mSensorManager.registerListener(this, magnetometer, SensorManager.SENSOR_DELAY_UI);
}

protected void onPause() {
super.onPause();
mSensorManager.unregisterListener(this);
}

public void onAccuracyChanged(Sensor sensor, int accuracy) { }

float[] mGravity;
float[] mGeomagnetic;
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
mGravity = event.values;
if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD)
mGeomagnetic = event.values;
if (mGravity != null && mGeomagnetic != null) {
float R[] = new float[9];
float I[] = new float[9];
boolean success = SensorManager.getRotationMatrix(R, I, mGravity, mGeomagnetic);
if (success) {
float orientation[] = new float[3];
SensorManager.getOrientation(R, orientation);
azimut = orientation[0]; // orientation contains: azimut, pitch and roll
}
}
mCustomDrawableView.invalidate();
}
}

I hope It will be useful for you.


Note: This is real code from Trackeen: your Mushrooms and Fishing GPS.

27 comments:

  1. Hi,
    I just try this code and it don't work...
    It need any special permission in the manifest?
    or any special code in the main.xml?
    Thankyou

    ReplyDelete
  2. Yes you needn both or one of the following permissions:

    android.permission.ACCESS_COARSE_LOCATION
    android.permission.ACCESS_FINE_LOCATION

    ReplyDelete
  3. thank you for this nice example.
    i have been tried many times but every time i faced failure thank you...
    keep this help up for beginners ..

    ReplyDelete
  4. Thanks to you, for commenting the post.

    And remember, everyone is a beginner at some time.

    ReplyDelete
  5. Thank you for this great post.. I wanted to know how I could modify this code to get true North azimut as opposed to Magnetic North. Again thanks for this great post...

    Orlando

    ReplyDelete
  6. Hello Orlando,

    The difference between Geographic and Magnetic Norths, varies both from place to place, and with the passage of time. Yo must get the Magnetic Declination, witch is the difference between both.

    There are tables to get the magnetic declination in base of latitude and longitude and time, but I never used them.

    ReplyDelete
  7. Nice, great post.
    I have made a similar compass app with map display and bearings as well and released the code OS.
    Check my blog @ compastic.blogpost.com
    Cheers

    ReplyDelete
  8. Sir i have created a screen from xml when I changes the orientation of mobile the buttons position are getting changed...can you tell me a better way to create screen that doesnot change the position of those buttons.....for example i created four buttons at the bottom of the screen ,when I changed the position of the screen to landscape the positon are changing .....

    ReplyDelete
  9. Hi,
    What happens if I work with 5 Sensors? do I need to create a type of "Switch case" to check which sensor triggered the event? (you did "if" for 2)
    Or maybe it is possible to create different event handlers to each sensor type, and then the performance will be better?
    Thank you,
    Diego

    ReplyDelete
  10. not use <uses-permission..... in my htc Wildfire
    in Android 2.3.3

    ReplyDelete
  11. Thank you very much this helped me a lot.

    A small hint:
    If you want to have azimut in degrees, there's a simple Math-function for it Math.toDegrees(azimut)
    This will give you something between -180° and 180°.

    ReplyDelete
  12. I made a radar simulator some time ago using TYPE_ORIENTATION sensor. I tried updating my implementation with this new way, but the results are not as smooth as before. Targets get a little shaky now. Any ideas of why this could be happening?

    ReplyDelete
  13. @Androideas I have the same problem with the readings of Azimuth.
    It is not smooth at all. Besides, the values are not accurate. I am talking about 30-40 degrees of error. Does any one know who to compensate that error?

    ReplyDelete
  14. this is such a nice example. it will working fine in 2.3 but will it work in 4.0?

    ReplyDelete
  15. I've been doing this onSensorChanged and it seems to work so far. Why did you choose to do this onAccuracyChanged?

    ReplyDelete
    Replies
    1. it's on onSensorChanged. Look again, probably an optical illusion..

      Delete
  16. My test compass app worked on a Kindle Fire using TYPE_ORIENTATION. When I tried the new code I discovered that the Kindle returns null for sensor TYPE_MAGNETIC_FIELD. Yet it was able to provide a compass bearing using the deprecated method. To work on more devices the code apparently needs to try other sensors.

    ReplyDelete
    Replies
    1. @Joe Mattioni did you find a solution? I Try it using a samsung galaxy tab 7.7" P6800 and it shows nothing...

      Delete
  17. My old test app using TYPE_ORIENTATION update orientation value about 30~40 ms
    (I register listener in SENSOR_DELAY_FASTEST mode).

    But when I use this new solution to avoid deprecation,
    my test app update orientation value about 2~3 s.
    It's so lated
    Is there any solution about increasing orientation sensor updated rate and also avoiding deprecation?

    ReplyDelete
  18. Use SENSOR_DELAY_GAME, it is faster for me than SENSOR_DELAY_FASTEST... For shake readings use low pass filter :)

    ReplyDelete
  19. Thank you so much for this, it's really helpful and exactly what I needed. Was stuck on getting orientation for the longest time before I found this.

    ReplyDelete
  20. It seems that no one had faced a similar problem with me. SensorManager.getRotationMatrix(...) method returns false nearly all the time.

    ReplyDelete
  21. With this example north just turns with the device and doesn't stay pointed north, it just kind of lags around. Anyone have this problem?

    ReplyDelete
  22. Hi,
    You've one serious bug

    mGravity = event.values;
    mGeomagnetic = event.values;

    Here you should copy data, otherwise mGravity could be equal to mGeomagnetic , because they are just references!

    ReplyDelete
    Replies
    1. it takes different values for each event...

      Delete
  23. Hi,
    I am developing a compass app for some other platform. Problem is i have only raw magnetometer reading and accelerometer readings. Can you tell me how can i use these reading to calculate magnetic heading for the app.
    Any help will be appreciated. Thank you.

    ReplyDelete
  24. Thanks for sharing! This is not towards you or your code, but the depricated way gives me a much more "stable" result, the new way makes my compass flicker 0.5 degrees back and forward. Sure I can code around that problem, but why did the android devs do that in first place?

    ReplyDelete