package com.authorwjf.whichwayisup; // Based on // https://www.techrepublic.com/article/pro-tip-create-your-own-magnetic-compass-using-androids-internal-sensors/ // with addtitions of pitch and roll read-outs. // // The blogpost above mentions math here as being complex, but it fact it's // really simple: the azimuth is the angle between the y-axis of the phone in // portrait mode and the magnetic north, counted counterclockwise (as is the // convention in math). The only confusion comes from Android image rotation API, // which interprets its angles _clockwise_, so you need to negate the angle to // get the needle to point to the azimuth. Easy. // // What is the Rotation Matrix and why was it introduced? // The older sensor API, Sensor.TYPE_ORIENTATION, was simpler, and just supplied // three angles: azimuth, pitch, and roll, assuming the y-axis of the sensor system // was always aligned with the longer side of the phone in portrait mode. Since // the sensor is fixed in the phone, its coordinate system is also fixed, and remains // the same no matter how you flip the phone. // // However, that did not play well with devices that were meant to be used in landscape // mode by default. In the end, the problem was recognized to be about there being _two_ // distinct coordinate systems involved: once for the sensors (fixed), one for the Views // (exchanging x-axis and y-axis directions on flip). The new API provides a rotation // matrix to translate between the two systems (getRotationMatrix), and a simple translation // function that applies this transform, getOrientation(..). It's not that hard. // // For details see https://android-developers.googleblog.com/2010/09/one-screen-turn-deserves-another.html import android.app.Activity; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.os.Bundle; import android.util.Log; import android.widget.ImageView; import android.widget.TextView; import android.view.animation.Animation; import android.view.animation.RotateAnimation; public class MainActivity extends Activity implements SensorEventListener { private ImageView mNeedle; private SensorManager mSensorManager; private Sensor mAccelerometer; private Sensor mMagnetometer; private float[] mLastAccelerometer = new float[3]; // (x, y, z) measurements private float[] mLastMagnetometer = new float[3]; private boolean mLastAccelerometerSet = false; private boolean mLastMagnetometerSet = false; private float[] mR = new float[9]; // Rotation matrix private float[] mOrientation = new float[3]; // (azimuth, pitch, roll) // private float mCurrentDegree = 0f; // -- used for rotation animations TextView tp; TextView tr; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE); mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); mMagnetometer = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD); mNeedle = findViewById(R.id.pointer); tp = findViewById(R.id.pitch); tr = findViewById(R.id.roll); } @Override protected void onResume() { super.onResume(); mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_GAME); mSensorManager.registerListener(this, mMagnetometer, SensorManager.SENSOR_DELAY_GAME); } @Override protected void onPause() { super.onPause(); mSensorManager.unregisterListener(this, mAccelerometer); mSensorManager.unregisterListener(this, mMagnetometer); } @Override public void onSensorChanged(SensorEvent event) { if (event.sensor == mAccelerometer) { System.arraycopy(event.values, 0, mLastAccelerometer, 0, event.values.length); mLastAccelerometerSet = true; } else if (event.sensor == mMagnetometer) { System.arraycopy(event.values, 0, mLastMagnetometer, 0, event.values.length); mLastMagnetometerSet = true; } if (mLastAccelerometerSet && mLastMagnetometerSet) { SensorManager.getRotationMatrix(mR, null, mLastAccelerometer, mLastMagnetometer); SensorManager.getOrientation(mR, mOrientation); // matrix multiplication! float azimuthInRadians = mOrientation[0]; float azimuthInDegrees = (float)(Math.toDegrees(azimuthInRadians)); Log.d("ANGLE", "Rad: " + azimuthInRadians + " Deg: " + azimuthInDegrees); double pitch = Math.toDegrees(mOrientation[1]); double roll = Math.toDegrees(mOrientation[2]); Log.d("ANGLE", "Pitch: " + pitch + "Roll: " + roll ); /* Original code animated the turn of the arrow. I found it unnecessary. RotateAnimation ra = new RotateAnimation( -mCurrentDegree, -azimuthInDegrees, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); ra.setDuration(250); ra.setFillAfter(true); mPointer.startAnimation(ra); mCurrentDegree = azimuthInDegrees; */ // Just set the rotation of the image. // setRotation is _clockwise_, whereas the azimuth angle is counterclockwise! // Negation accounts for this disparity. mNeedle.setRotation( -azimuthInDegrees ); tp.setText( "Pitch: " + Math.round(pitch) ); tr.setText( "Roll: " + Math.round(roll) ); // If you want more decimal digits, say, two: // tp.setText( "Pitch: " + Math.round(pitch*100.0)/100.0 ); // tr.setText( "Roll: " + Math.round(roll*100.0)/100.0 ); } } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { // TODO Auto-generated method stub } }