Search code examples
javaandroidandroid-sensorsbearingdigital-compass

How do I get the correct bearing (magnetic orientation) regardless of screen orientation?


I want to get the current magnetic orientation regardless of the current screen orientation (landscape or portrait).

I found this example, but it's not orientation independant, right? And this didn't help me either. I did also read http://android-developers.blogspot.de/2010/09/one-screen-turn-deserves-another.html.

This is my current approach with the deprecated way I don't want to use (short):

mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);

private SensorEventListener sensorEventListener = new SensorEventListener() {

    public void onSensorChanged(SensorEvent event) {

        /* Get measured value */
        float current_measured_bearing = (float) event.values[0];

        /* Compensate device orientation */
        switch (((WindowManager) getSystemService(WINDOW_SERVICE))
                .getDefaultDisplay().getRotation()) {
        case Surface.ROTATION_90:
            current_measured_bearing = current_measured_bearing + 90f;
            break;
        case Surface.ROTATION_180:
            current_measured_bearing = current_measured_bearing - 180f;
            break;
        case Surface.ROTATION_270:
            current_measured_bearing = current_measured_bearing - 90f;
            break;
        }

But the last part is definitely wrong! How do I use the newer method getRotationMatrix() correctly in this case? (Orientation independent) Or do I simply have to use other values of the event.values[] array based on the Rotation Matrix? Or will I need to 'remap the coordinates'? So is that the correct way of achieving this?

I'm developing for devices with 360° screen rotation and on API Level 11+.

I know that those questions are asked very often but I could simply not transfer their answers to my question.


Solution

  • OK I finally managed to get the code working!

    First, I register a Sensor.TYPE_MAGNETIC_FIELD and Sensor.TYPE_GRAVITY: (like Hoan Nguyen said!)

    /**
     * Initialize the Sensors (Gravity and magnetic field, required as a compass
     * sensor)
     */
    private void initSensors() {
    
        LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
        SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
        Sensor mSensorGravity = sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY);
        Sensor mSensorMagneticField = sensorManager
                .getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
    
        /* Initialize the gravity sensor */
        if (mSensorGravity != null) {
            Log.i(TAG, "Gravity sensor available. (TYPE_GRAVITY)");
            sensorManager.registerListener(mSensorEventListener,
                    mSensorGravity, SensorManager.SENSOR_DELAY_GAME);
        } else {
            Log.i(TAG, "Gravity sensor unavailable. (TYPE_GRAVITY)");
        }
    
        /* Initialize the magnetic field sensor */
        if (mSensorMagneticField != null) {
            Log.i(TAG, "Magnetic field sensor available. (TYPE_MAGNETIC_FIELD)");
            sensorManager.registerListener(mSensorEventListener,
                    mSensorMagneticField, SensorManager.SENSOR_DELAY_GAME);
        } else {
            Log.i(TAG,
                    "Magnetic field sensor unavailable. (TYPE_MAGNETIC_FIELD)");
        }
    }
    

    And I use that SensorEventListner for the computation:

    private SensorEventListener mSensorEventListener = new SensorEventListener() {
    
        @Override
        public void onAccuracyChanged(Sensor sensor, int accuracy) {
        }
    
        @Override
        public void onSensorChanged(SensorEvent event) {
    
            if (event.sensor.getType() == Sensor.TYPE_GRAVITY) {
    
                mGravity = event.values.clone();
    
            } else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
    
                mMagnetic = event.values.clone();
    
            }
    
            if (mGravity != null && mMagnetic != null) {
    
                /* Create rotation Matrix */
                float[] rotationMatrix = new float[9];
                if (SensorManager.getRotationMatrix(rotationMatrix, null,
                        mGravity, mMagnetic)) {
    
                    /* Compensate device orientation */
                    // http://android-developers.blogspot.de/2010/09/one-screen-turn-deserves-another.html
                    float[] remappedRotationMatrix = new float[9];
                    switch (getWindowManager().getDefaultDisplay()
                            .getRotation()) {
                    case Surface.ROTATION_0:
                        SensorManager.remapCoordinateSystem(rotationMatrix,
                                SensorManager.AXIS_X, SensorManager.AXIS_Y,
                                remappedRotationMatrix);
                        break;
                    case Surface.ROTATION_90:
                        SensorManager.remapCoordinateSystem(rotationMatrix,
                                SensorManager.AXIS_Y,
                                SensorManager.AXIS_MINUS_X,
                                remappedRotationMatrix);
                        break;
                    case Surface.ROTATION_180:
                        SensorManager.remapCoordinateSystem(rotationMatrix,
                                SensorManager.AXIS_MINUS_X,
                                SensorManager.AXIS_MINUS_Y,
                                remappedRotationMatrix);
                        break;
                    case Surface.ROTATION_270:
                        SensorManager.remapCoordinateSystem(rotationMatrix,
                                SensorManager.AXIS_MINUS_Y,
                                SensorManager.AXIS_X, remappedRotationMatrix);
                        break;
                    }
    
                    /* Calculate Orientation */
                    float results[] = new float[3];
                    SensorManager.getOrientation(remappedRotationMatrix,
                            results);
    
                    /* Get measured value */
                    float current_measured_bearing = (float) (results[0] * 180 / Math.PI);
                    if (current_measured_bearing < 0) {
                        current_measured_bearing += 360;
                    }
    
                    /* Smooth values using a 'Low Pass Filter' */
                    current_measured_bearing = current_measured_bearing
                            + SMOOTHING_FACTOR_COMPASS
                            * (current_measured_bearing - compass_last_measured_bearing);
    
                    /* Update normal output */
                    visual_compass_value.setText(String.valueOf(Math
                            .round(current_bearing))
                            + getString(R.string.degrees));
    
                    /*
                     * Update variables for next use (Required for Low Pass
                     * Filter)
                     */
                    compass_last_measured_bearing = current_measured_bearing;
    
                }
            }
        }
    };