差分表示
- 最後の更新で追加された行はこのように表示します。
- 最後の更新で削除された行は
このように表示します。
Android 3.1のXOOMで90度ずれるという現象が発生したので,電子コンパスの方位角をSensor.TYPE_ORIENTATIONを使わずに求める方法を試しました
//parent=Android
*TYPE_ORIENTATIONを使わずに方位角を取得
地図ロイドですが,A500で電子コンパスが90度などずれるという話がありました.
それで手元のXOOM (Android 3.1)で確認したところ,確かに90度ずれていました.
一体何事か!ヽ(`Д´)ノ と思って調べてみたところ,結局
-方位角を求めるためのSensor.TYPE_ORIENTATIONで受け取ったeventのvalues[0]が,90度ずれている
という現象が発生していたのが原因でした.
[[AndroidでGoogleMapをヘディングアップ表示]]に概要を書いていますが,TYPE_ORIENTATIONのセンサーで方位を求める実装をしていたのです.
それにしてもOSのバージョンなのか機種依存なのかわかりませんが,今までは発生していなかった不具合です.
少なくとも,HT-03A(1.6),Xperia arc(2.3.3)では発生しません.
とは言っても仕方がないので対処法を考えるのですが,
-問題が発生する端末の場合だけ90度ずらす
という方法は,どの端末で問題が発生するのか特定できそうにないので没とし,
あれこれ試したところ,Sensor.TYPE_ORIENTATIONを使わない方法だと正しい角度が取れました.
確かに,android.hardware.SensorEventの
&link(JavaDoc,http://developer.android.com/reference/android/hardware/SensorEvent.html#values)
によると
Sensor.TYPE_ORIENTATION はレガシーである,
--(
Note: This sensor type exists for legacy reasons,
please use getRotationMatrix() in conjunction with
remapCoordinateSystem() and getOrientation() to compute these values instead.
--)
と書かれており,今となってはあまり使わない方が良いのかも知れません.
それでこのSensor.TYPE_ORIENTATIONを使わない方法ですが,ネットを検索したところ,
Sensor.TYPE_ACCELEROMETERとSensor.TYPE_MAGNETIC_FIELDを組み合わせて取得できるようです.
-&link(方位センサー ≪ むずかしいことはわかりません,http://proglogging.wordpress.com/2011/01/13/%E6%96%B9%E4%BD%8D%E3%82%BB%E3%83%B3%E3%82%B5%E3%83%BC/)
-&link(JAVA - Using getRotationMatrix and getOrientation in Android 2.1 - efreedom,http://efreedom.com/Question/1-2963705/Using-GetRotationMatrix-GetOrientation-Android-21)
今回は,この方法で行った実装について説明します.
※なお以下のコードはそのままでは動作しません.説明したい内容の''キモに絞った最小限の記述''ですので,
実際に動くアプリにするには加筆する必要がありますのでご注意ください.
***1.TYPE_ORIENTATIONを使わない方式で方位角を求める
TYPE_ACCELEROMETERとTYPE_MAGNETIC_FIELDを使う方法です.
上記のネットで検索した方法の,ほとんどそのままです.
<itle(メンバー変数)
--(1,java
SensorEventListener mSensorEventListener;
float[] accelerometerValues = new float[3];
float[] geomagneticMatrix = new float[3];
boolean sensorReady;
--)
<itle(センサー起動部)
--(1,java
List<Sensor> slm = mSensorManager.getSensorList(Sensor.TYPE_MAGNETIC_FIELD);
mSensorManager.registerListener(mSensorEventListener,
slm.get(0), SensorManager.SENSOR_DELAY_NORMAL);
List<Sensor> sla = mSensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER);
mSensorManager.registerListener(mSensorEventListener,
sla.get(0), SensorManager.SENSOR_DELAY_NORMAL);
--)
<itle(イベントリスナー定義部)
--(1,java
mSensorEventListener = new SensorEventListener() {
public void onSensorChanged(SensorEvent event) {
switch (event.sensor.getType()) {
case Sensor.TYPE_ACCELEROMETER:
accelerometerValues = event.values.clone();
break;
case Sensor.TYPE_MAGNETIC_FIELD:
geomagneticMatrix = event.values.clone();
sensorReady = true;
break;
}
if (geomagneticMatrix != null && accelerometerValues != null && sensorReady) {
sensorReady = false;
float[] R = new float[16];
float[] I = new float[16];
SensorManager.getRotationMatrix(R, I,
accelerometerValues, geomagneticMatrix);
float[] actual_orientation = new float[3];
calcActualOrientation(R, actual_orientation);
// 求まった方位角.ラジアンなので度に変換する
float orientDegree = (float)Math.toDegrees(actual_orientation[0]);
Log.d("**Test", "orientDegree=" + orientDegree);
}
}
public void onAccuracyChanged(Sensor sensor, int accuracy) {}
};
--)
<itle(子メソッド)
--(1,java
private void calcActualOrientation(float[] R, float[] out) {
SensorManager.getOrientation(R, out);
}
--)
これで,TYPE_ORIENTATIONを使わない方法で無事に方位角が求まりました.
***2.画面の回転を考慮する
しかしこれだと,端末を通常の置き方で使うときは正しい方位になるのですが,
端末を縦置きから横置きにしたりして画面表示の向きを切り替えると,90度とか角度が狂ってしまいます.
そこで,画面の回転への対応を入れます.
先ほどの一番下のcalcActualOrientation()メソッドを,以下のように書き換えてみます.
<itle(calcActualOrientationメソッドの改訂)
--(1,java
private void calcActualOrientation(float[] R, float[] out) {
Activity act = this;
// 画面の回転状態を取得する
int dr = getDispRotation(act);
if (dr == Surface.ROTATION_0) {
// 回転無し
SensorManager.getOrientation(R, out);
} else {
// 回転あり
float[] outR = new float[16];
if (dr == Surface.ROTATION_90) {
SensorManager.remapCoordinateSystem(R,
SensorManager.AXIS_Y, SensorManager.AXIS_MINUS_X, outR);
} else if (dr == Surface.ROTATION_180) {
float[] outR2 = new float[16];
SensorManager.remapCoordinateSystem(R,
SensorManager.AXIS_Y, SensorManager.AXIS_MINUS_X, outR2);
SensorManager.remapCoordinateSystem(outR2,
SensorManager.AXIS_Y, SensorManager.AXIS_MINUS_X, outR);
} else if (dr == Surface.ROTATION_270) {
SensorManager.remapCoordinateSystem(R,
SensorManager.AXIS_MINUS_Y, SensorManager.AXIS_MINUS_X, outR);
}
SensorManager.getOrientation(outR, out);
}
}
private static int getDispRotation(Activity act) {
Display d = act.getWindowManager().getDefaultDisplay();
return d.getRotation();
}
--)
getDispRotation()という新メソッドで画面の回転状態を取得し,その結果を使って
&link(SensorManager.remapCoordinateSystem,http://developer.android.com/reference/android/hardware/SensorManager.html#remapCoordinateSystem%28float[]%2C%20int%2C%20int%2C%20float[]%29)
で座標系変換をかけています.
getDispRotation()では最終的に,
&link(Display#getRotation,http://developer.android.com/reference/android/view/Display.html#getRotation%28%29)
というメソッドを使っています.
なお座標系変換といってもここで必要なのは,90度,180度,270度ずらすだけですので,
remapCoordinateSystemを使わずに単純な引き算で実装しても良いと思います.
(度に変換する前なら,もちろん度ではなくラジアン単位で引く必要があります)
***3.Android 2.1以下への対応
先ほどは触れなかったのですが,Display#getRotation()というメソッドはAPI Level 8 (2.2.x)です.
このようにLevel 8のメソッドをそのまま含んでいるプログラムをAndroid 2.1以下の端末で動かそうとすると,
エラーになってしまいます.
そこで2.1以下の端末では,Display#getRotation()ではなく,Level 7以下でも使える
&link(Display#getOrientation,http://developer.android.com/reference/android/view/Display.html#getOrientation%28%29)
を使うように対応します.(「deprecated」ですが,ここでは見なかったことにします (´∇`)
最後のgetDispRotation()メソッドを,以下のように書き換えてみます.
<itle(getDispRotationメソッドの改訂)
--(1,java
private static int getDispRotation(Activity act) {
Display d = act.getWindowManager().getDefaultDisplay();
return DispRotateGetter.getInstance().getRotate(d);
}
private static class DispRotateGetter {
private static IDispRotateGetter getInstance() {
if (Integer.parseInt(Build.VERSION.SDK) >= 8) {
// for 2.2 or higher
return new DispRotateGetterV8();
} else {
// for 2.1 or lower
return new DispRotateGetterV1();
}
}
private interface IDispRotateGetter {
public int getRotate(Display d);
}
private static class DispRotateGetterV8 implements IDispRotateGetter {
public int getRotate(Display d) {
return d.getRotation();
}
}
private static class DispRotateGetterV1 implements IDispRotateGetter {
public int getRotate(Display d) {
int r = d.getOrientation();
return (r == 0? Surface.ROTATION_0: Surface.ROTATION_90);
}
}
}
--)
何やら複雑ですが...
背景としては,その端末のLevelを超えたメソッドを呼び出しているクラスはロード時にエラーが出るため,
interfaceを挟むことでそのようなクラスをロードさせないようにしている,という事情です.
2.1以下の端末ではLevel8を含むDispRotateGetterV8クラスをロードさせないことで,
レベルオーバーのエラーを回避しています.
...ここまでやって,無事に手元の3台
-HT-03A (Android 1.6)
-Xperia arc (2.3.3)
-XOOM (3.1)
で画面の回転を考慮に入れた方位角を取れるようになりました (´∀`)
***補足
<itle(マニフェストでのバージョン定義);
--(
<uses-sdk android:minSdkVersion="3" android:targetSdkVersion="8" />
--)
targetSdkVersionを上げないとLevel 8のメソッドは記述できないのですが,Level 3の端末でも実行するという意図です