センサー (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 要素を使って、配列とし
て管理する。