Two months ago, I developed an islamic application in android and published it. And just like any other islamic application, Qibla direction is an essential feature. But to implement it, I had to read a lot and do some experiment. Today I am going to explain the theory behind the compass along with the example so that you can also develop your own compass in android.
First thing first, before making a compass work, we need the sensors in our device in action. To make it tick, the android framework provides a function to register for a particular sensor. For compass, we need two sensors; magnetic field and accelerometer. So, lets go.
Todo # 1 – First we need our fragment to implement the callback to be able to get the sensor data from android
public class CompassFragment extends Fragment
implements SensorEventListener {
 ...
@Override
   public void onSensorChanged(SensorEvent event) {
       // we will get sensor data in this callback
   }
 ...
}
Todo # 2 – Next we will register to android framework to get the appropriate sensor data
SensorManager mSensorManager = (SensorManager) getActivity
.getSystemService(Context.SENSOR_SERVICE);
mSensorManager.registerListener(this, mSensorManager
.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD),
SensorManager.SENSOR_DELAY_UI);
mSensorManager.registerListener(this, mSensorManager
.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
SensorManager.SENSOR_DELAY_UI);
Section # 3 – Now lets back to some theory. We have two north, one is magnetic north and another one is true north. Magnetic north is the north pointed by a compass, where true north is actual north of earth. The angle between them is called magnetic decline (expressed in angle) [1].
Azimuth is the horizontal angle measured clockwise from true north [2]. So, to calculate azimuth we need to compensate for magnetic decline also.
Luckily android has some easy way to calculate the Azimuth. Lets see how.
float [] mGravity;
float [] mGeomagnetic;
@Override
   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];
           if (SensorManager.getRotationMatrix(R, I,
mGravity, mGeomagnetic)) {
               float orientation[] = new float[3];
               SensorManager.getOrientation(R, orientation);
               float degree = (float) Math.toDegrees(orientation[0]);
               float actualAngle = getBearing(degree);
               // rotate your compass image by actualAngle
           }
        }
}
Azimuth is given by the function SensorManager.getOrientation(), it expects rotation values and convert them into azimuth, pitch and roll respectively. But in order to to get the rotation values, we will be using SensorManager.getRotationMatrix() api. It converts the magnetic sensor data and gravity data into rotation values. Check the api documentation to get more idea on it. After getting the azimuth we convert it to degrees and call our getBearing() function to calculate the angle between us and a particular point of interest.
Todo # 4 – calculate bearing between our location and location of interest
First lets define our and location of interest –
Location mMyLocation = new Location("MyLocation");
Location mKabaLocation = new Location("Kaba");
private final float kabaSharifLongitude = 39.8261f;
private final float kebaSharifLatitude = 21.4225f
You can change the mKabaLocation to whatever location you want.
As I have explained earlier azimuth is calculated from true north, so we need to compensate for the angle between true north and magnetic north. Android provides an api to calculate the magnetic decline easily, given your GPS latitude, longitude and altitude; it can calculate the magnetic decline in degree. So, using the GeomagneticField class provided by android, lets calculate the magnetic decline.
GeomagneticField mGeoMag = null;
public void onResume() {
       super.onResume();
       mGeoMag = new GeomagneticField(mMyLatitude,
mMyLongitude, mMyAltitude,
System.currentTimeMillis());
       ...
}
This is our initialization of GeomagneticField class using our latitude, longitude and altitude. Next, let us adjust our compass heading to calculate the actual angle by which we should rotate our compass image –
public float getBearing(float heading) {
       if (mGeoMag == null) return heading;
       heading -= mGeoMag.getDeclination();
       return mMyLocation.bearingTo(mKabaLocation) - heading;
 }
At first, we subtract the decline to align our azimuth value to magnetic north. Then we calculate the angle between two location and adjust our compass angle with it. That is it, we have now the required angle of rotation for the compass image.
Here is how the final output of the implementation looks like –
Leave a comment if you have any suggestion or questions.
All the images used here are used for educational purpose.
References –
- https://en.wikipedia.org/wiki/Magnetic_declination
- https://en.wikipedia.org/wiki/Azimuth