センサー (3) 方位 ================================================================================ ■ 方位を取得する 方位は、それ専用のセンサーがあるのではなく、加速度センサーと磁気センサーの値から 算出する。この 2 つのセンサーを使うこと以外は、いままでのサンプルと同じである。 Sensor.TYPE_ORIENTATION は、非推奨になっているので、これは使わない。 ◆ アクティビティのクラス。 □ OrientationSensor01Activity.java --- package jp.marunomaruno.android.orientationsensor; import android.app.Activity; import android.hardware.Sensor; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.os.Bundle; import android.widget.TextView; import jp.marunomaruno.android.orientationsensor.R; /** * 方位を表示する。 * @author marunomaruno * @version 1.0, 2011-12-01 */ public class OrientationSensor01Activity extends Activity { // TextView private TextView orientationTextView; // 方位センサーの表示域 private SensorManager sensorManager; private SensorEventListener sensorEventListener; // onPause()が呼ばれたときに、すべてのリスナーを解除するため @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); orientationTextView = (TextView) findViewById(R.id.sensor_value); sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); } @Override protected void onResume() { super.onResume(); sensorEventListener = new OrientationSensorListener(orientationTextView); // (1) // 加速度センサーを取得して登録する for (Sensor sensor : sensorManager .getSensorList(Sensor.TYPE_ACCELEROMETER)) { sensorManager.registerListener(sensorEventListener, sensor, SensorManager.SENSOR_DELAY_NORMAL); } // 磁気センサーを取得して登録する for (Sensor sensor : sensorManager .getSensorList(Sensor.TYPE_MAGNETIC_FIELD)) { sensorManager.registerListener(sensorEventListener, sensor, SensorManager.SENSOR_DELAY_NORMAL); } } @Override public void onPause() { super.onPause(); // すべてのリスナーを解除する sensorManager.unregisterListener(sensorEventListener); } } --- (1) 方位センサーのリスナー・オブジェクトを生成する sensorEventListener = new OrientationSensorListener(orientationTextView); // (1) そして、このオブジェクトを、加速度センサー、磁気センサーに登録しておく。 ◆ センサーのリスナーのクラス。 センサーに対するリスナーのクラス。 これは、加速度センサーと磁気センサーの 2 つのセンサーに登録される。 方位は、この 2 つのセンサーの値を基に取得する。 □ AcceleraterSensorListener.java --- package jp.marunomaruno.android.orientationsensor; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.widget.TextView; import jp.marunomaruno.android.orientationsensor.R; /** * 方位センサーのリスナー * @author maruno * @version 1.0, 2011-12-01 * @since 1.0 */ public class OrientationSensorListener implements SensorEventListener { private TextView textView; // 表示域 private float[] accelerometerValues; // 加速度センサーの値 // (1) private float[] magneticFieldValues; // 磁気センサーの値 // (2) public OrientationSensorListener(TextView textView) { this.textView = textView; } @Override public void onSensorChanged(SensorEvent event) { float[] orientationValues = new float[3]; // 方位の値 // (3) switch (event.sensor.getType()) { case Sensor.TYPE_ACCELEROMETER: // 加速度センサーが変化したとき accelerometerValues = clone(event.values); // (4) break; case Sensor.TYPE_MAGNETIC_FIELD: // 磁気センサーが変化したとき magneticFieldValues = clone(event.values); // (5) break; } if (accelerometerValues != null && magneticFieldValues != null) { float[] inRotationMatrix = new float[16]; // 入力用の回転行列 // (6) float[] outRotationMatrix = new float[16]; // 出力用の回転行列 // (7) SensorManager.getRotationMatrix(inRotationMatrix, null, accelerometerValues, magneticFieldValues); // (8) SensorManager.remapCoordinateSystem(inRotationMatrix, SensorManager.AXIS_X, SensorManager.AXIS_Z, outRotationMatrix); // (9) SensorManager.getOrientation(outRotationMatrix, orientationValues); // (10) // TextViewに表示する textView.setText(""); // ラジアンを表示 textView.append(String .format(textView.getResources().getString( R.string.sensor_value_format), textView .getResources().getString(R.string.orientation), orientationValues[0], orientationValues[1], orientationValues[2], textView.getResources() .getString(R.string.OrientationUnit))); textView.append("¥n"); // 度を表示 textView.append(String.format(textView.getResources().getString( R.string.sensor_value_format), textView.getResources() .getString(R.string.orientation), toOrientationDegrees(orientationValues[0]), toOrientationDegrees(orientationValues[1]), toOrientationDegrees(orientationValues[2]), textView .getResources() .getString(R.string.OrientationUnit2))); textView.append("¥n"); // 方位を表示 textView.append(String.format(textView.getResources().getString( R.string.orientation_value_format), toOrientationString(orientationValues[0]), toOrientationDegrees(orientationValues[0]), textView .getResources() .getString(R.string.OrientationUnit2))); textView.append("¥n"); } } /** * 配列の深いコピーを行う。 * ※Android2.2 の Arrays クラスには copyOf() メソッドがない。 * @param source ソースの配列 * @return 深いコピーを行った結果 */ private float[] clone(float[] source) { // (11) float[] target = new float[source.length]; for (int i = 0; i < target.length; i++) { target[i] = source[i]; } return target; // return Arrays.copyOf(sourse, sourse.length); // 本来はこれでOK } /** * 方位の角度にする。方位の角度は、0以上360未満。 * @param rad ラジアン。ただし、-π以上π未満。 * @return 方位の角度 */ private float toOrientationDegrees(float rad) { // (12) return (float) (rad >= 0 ? Math.toDegrees(rad) : 360 + Math .toDegrees(rad)); // (13) } /** * 方位をあらわす文字列を取得する。 * 北 0 rad * 東 π/2 rad * 南 π rad * 西 -π/2 rad * @param azimuth 方位 * @return 方位をあらわす文字列 */ private String toOrientationString(float azimuth) { // (14) double[] ORIENTATION_RANGE = { -(Math.PI * 3 / 4), // 南 -(Math.PI * 1 / 4), // 西 +(Math.PI * 1 / 4), // 北 +(Math.PI * 3 / 4), // 東 // Math.PI, // 南 }; // (15) for (int i = 0; i < ORIENTATION_RANGE.length; i++) { if (azimuth < ORIENTATION_RANGE[i]) { return textView.getResources().getStringArray( R.array.orientationNames)[i]; // (16) } } return textView.getResources().getStringArray(R.array.orientationNames)[0]; } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { assert true; // 何もしない } } --- (1)(2) 加速度センサー・磁気センサーの値を保持 それぞれ、3 次元の値なので、配列で保持する。 private float[] accelerometerValues; // 加速度センサーの値 // (1) private float[] magneticFieldValues; // 磁気センサーの値 // (2) (3) 方位の値 方位も、3 次元の値になるので、これを確保する変数を用意する。 float[] orientationValues = new float[3]; // 方位の値 // (3) (4)(5)(6)(7) 加速度センサー・磁気センサーの値から方位を取得するための準備 計算に使うので、配列の深いコピーで、値をコピーする。 accelerometerValues = clone(event.values); // (4) magneticFieldValues = clone(event.values); // (5) ※Android2.2 では、深いコピーを行う Arrays.copyOf() メソッドがないので、自分で実 装する。本来は、 accelerometerValues = Arrays.copyOf(event.values, event.values.length); // (4) magneticFieldValues = Arrays.copyOf(event.values, event.values.length); // (5) でよい。 4x4 の回転行列を用意する。実際には、要素数 16 の 1 次元配列で作る。 float[] inRotationMatrix = new float[16]; // 入力用の回転行列 // (6) float[] outRotationMatrix = new float[16]; // 出力用の回転行列 // (7) (8)(9)(10) 加速度センサー・磁気センサーの値から方位を取得する この一連の操作によって、accelerometerValues、magneticFieldValues の値は、最終的 に orientationValues として方位が取得できる。 加速度センサー・磁気センサーの値から、回転行列 inRotationMatrix を取得する。 SensorManager.getRotationMatrix(inRotationMatrix, null, accelerometerValues, magneticFieldValues); // (8) 座標系を回転行列 inRotationMatrix を使って変換し、outRotationMatrix に入れる。 今回は、画面をみるときに、たて画面で、垂直に近い形(手前に傾いている)で見ること を想定。 画面の傾き具合は、Display.getRotation() を使って取得するが、この部分はかなり複雑 になるので、今回はこのまま。 ※他のサイトでは、引数(..., SensorManager.AXIS_X, SensorManager.AXIS_Y, ...) が よいともあるが、わたしの実機では下記の引数の方がよい感じ。 SensorManager.remapCoordinateSystem(inRotationMatrix, SensorManager.AXIS_X, SensorManager.AXIS_Z, outRotationMatrix); // (9) 回転行列 outRotationMatrix からデバイスの向き orientationValues を取得する。 SensorManager.getOrientation(outRotationMatrix, orientationValues); // (10) ※回転行列などの詳細は現在調査中。 ・SensorManager クラスの static な取得系のメソッド --- static float getAltitude(float p0, float p) 大気圧と海面の気圧から高度(m)を取得 static void getAngleChange(float[] angleChange, float[] R, float[] prevR) 2 つの回転行列の間の角度を取得 static float getInclination(float[] I) getRotationMatrix() で取得した傾き行列から傾きを取得 static float[] getOrientation(float[] R, float[] values) 回転行列からデバイスの向きを取得 static void getQuaternionFromVector(float[] Q, float[] rv) 回転ベクトルから 4 元数を取得(ヘルパー) static boolean getRotationMatrix(float[] R, float[] I, float[] gravity, float[] geomagnetic) 回転行列を取得 static void getRotationMatrixFromVector(float[] R, float[] rotationVector) 回転ベクトルから回転行列を取得(ヘルパー) static boolean remapCoordinateSystem(float[] inR, int X, int Y, float[] outR) 座標系を回転行列を使って変換 --- ※ただし、使い方(訳も)についてはまだ、不明な部分が大半。 ・remapCoordinateSystem() で使う定数 --- int AXIS_MINUS_X int AXIS_MINUS_Y int AXIS_MINUS_Z int AXIS_X int AXIS_Y int AXIS_Z --- (11) 配列の深いコピーを行う private float[] clone(float[] source) { // (11) ※Android2.2 では、まだ、これを実現する Arrays.copyOf() メソッドがないので、自分 で実装する。 (12)(13) 方位の角度にする。方位の角度は、0以上360未満。 private float toOrientationDegrees(float rad) { // (12) rad は、-π ~ +πの範囲なので、これを度に変換し、値の範囲が 0 ~ 360 になるよう に調整する。 return (float) (rad >= 0 ? Math.toDegrees(rad) : 360 + Math .toDegrees(rad)); // (13) (14)(15)(16) 方位をあらわす文字列を取得する。 private String toOrientationString(float azimuth) { // (14) 真北、真東、真南、真西がそれぞれ 0, π/2, -π, -π/2 なので、北、東、南、西のそ れぞれの範囲は、以下のようになる。 北 -π/4 ~ π/4 東 π/4 ~ 3π/4 南 -π ~ -3π/4, 3π/4 ~ π 西 -π/4 ~ π/4 double[] ORIENTATION_RANGE = { -(Math.PI * 3 / 4), // 南 -(Math.PI * 1 / 4), // 西 +(Math.PI * 1 / 4), // 北 +(Math.PI * 3 / 4), // 東 // Math.PI, // 南 }; // (15) この範囲で、配列を作って、文字を割り当てる。 return textView.getResources().getStringArray( R.array.orientationNames)[i]; // (16) リソースでは、string-array 要素を使って、配列として、東西南北の文字列を持つよう にしている。 <string-array name="orientationNames"> <item>@string/south</item> <item>@string/west</item> <item>@string/north</item> <item>@string/east</item> </string-array> これは、getStringArray() メソッドで、ここで記述した順番の String[] 型の配列で値 が返ってくる。 ■ レイアウト センサー(1) のサンプルと同じ。 □ res/layout/main.xml --- <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <!-- センサー値 --> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/orientation" android:id="@+id/sensor_value" /> </LinearLayout> --- ■ 文字列の定数 プログラム中で配列を使っているので、それに対する string-array 要素を設定している ファイル arrays.xml も準備する。string.xml の中に入れてもよいが、配列として文字 列を 2 次的に利用しているので、別ファイルにした。 □ res/values/string.xml --- <?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">OrientationSensor01</string> <string name="orientation">方位: (方位角(azimuth), ピッチ(Pitch), 回転(Roll))</string> <string name="OrientationUnit">ラジアン</string> <string name="OrientationUnit2">度</string> <string name="sensor_value_format">%1$s = (%2$.1f, %3$.1f, %4$.1f) (%5$s)</string> <string name="orientation_value_format">方位: %1$s (%2$.1f %3$s)</string> <string name="east">東(45 ~ 135)</string> <string name="west">西(225 ~ 315)</string> <string name="south">南(135 ~ 225)</string> <string name="north">北(0 ~ 45, 315 ~ 360)</string> </resources> --- □ res/values/arrays.xml --- <?xml version="1.0" encoding="utf-8"?> <resources> <string-array name="orientationNames"> <item>@string/south</item> <item>@string/west</item> <item>@string/north</item> <item>@string/east</item> </string-array> </resources> --- 東西南北の文字列を配列で持っておく。 プログラム中でも配列を使うので、リソースも、string-array 要素を使って、配列とし て管理する。