2011年4月9日土曜日

Androidでセンサーから方位を取得する

最近ネイティブAndroidプログラムに手を出しています。
FlashのAS3を触っていると、AndroidのJavaはあまり敷居が高くなく感じますね。whileの挙動とか、イベントリスナの登録あたりはちょっと違いますけど。
イベントリスナの登録は、AS3でいうNetStraemのonMetaDataハンドラの扱いに似ていますね。ただ、Javaの方はインターフェイスで強制実装させるあたり、厳格になっています。

さて、本題ですが、Androidでコンパスを作成する時の話です。
コンパスを作成するにあたってセンサの値を取ってくる必要があるのですが、他のセンサーと同じようにSensorManagerから読み出す事で使用する事が可能です。

public class Compass extends Activity implements SensorEventListener{

    private SensorManager sm;

    @Override
    public void onCreate(Bundle savedInstanceState) {
     
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        sm=(SensorManager)getSystemService(SENSOR_SERVICE);
        
    }

    @Override
    protected void onResume() {
        super.onResume();

        //全センサを取得
        //List<Sensor> sensors = sm.getSensorList(Sensor.TYPE_ALL);
        //加速度センサ
        //List<Sensor> sensors = sm.getSensorList(Sensor.TYPE_ACCELEROMETER);
        //磁気センサ
        //List<Sensor> sensors = sm.getSensorList(Sensor.TYPE_MAGNETIC_FIELD);
        //方位センサ
        List<Sensor> sensors = sm.getSensorList(Sensor.TYPE_ORIENTATION);

        for(Sensor sensor : sensors){
            //イベントを登録
            sm.registerListener(this, sensor, SensorManager.SENSOR_DELAY_GAME);
        }
    }

    …

}

Sensor.TYPE_ORIENTATIONが方位センサのタイプ定数になります。
ただ、このタイプは現在非推奨となっており、Android2.2からはSensorManager.getOrientation()が推奨されます。使い方としては、上記の方法で加速度と地磁気センサの値を取得し、それらの値から然るべき方位を計算する、という形になります。

//SensorEventListenerにより実装が要求されるメソッド
public void onSensorChanged(SensorEvent event) {
    
    float[] amv=new float[3];
    float[] mfv=new float[3];
    float[] orv=new float[3];

    switch(event.sensor.getType()){
        case Sensor.TYPE_ACCELEROMETER:
            amv=event.values.clone();
        break;
        case Sensor.TYPE_MAGNETIC_FIELD:
            mfv=event.values.clone();
        break;
    }

    float[] in_r=new float[16];
    float[] out_r=new float[16];
 
    if(amv!=null && mfv!=null){

        SensorManager.getRotationMatrix(in_r, null, amv, mfv);
        SensorManager.remapCoordinateSystem(in_r, SensorManager.AXIS_X, SensorManager.AXIS_Z, out_r);
        SensorManager.getOrientation(out_r, orv);

        Log.d("message", String.valueOf(orv[0]));
        Log.d("message", String.valueOf(orv[1]));
        Log.d("message", String.valueOf(orv[2]));

    }
 
}

ただ、このまま実装すると、リファレンスにもある通り特定の状態でしか正しい方位を取得できません。

Using the camera (Y axis along the camera's axis) for an augmented reality application where the rotation angles are needed:
remapCoordinateSystem(inR, AXIS_X, AXIS_Z, outR);

画面が自分から見て手前側に傾いている時は正しく方位が見えますが、向こう側に傾いている時は方位が反転してしまいます。これを調整するにはremapCoordinateSystemの第二、第三引数を調整すると良いのですが、例えば横向きにする時には

Using the device as a mechanical compass when rotation is Surface.ROTATION_90:
remapCoordinateSystem(inR, AXIS_Y, AXIS_MINUS_X, outR);

とすれば良いようです。

これを、端末の向きによらず正しい方位を取得するようにしたかったので、色々試してみた結果

remapCoordinateSystem(inR, AXIS_X, AXIS_Y, outR);

を使用すると端末の向きによらず正しい方位を取得する事が出来ました。

ただし、iPhoneでもそうですが、寝転がってコンパスを見ると正しい方位が表示されません。まぁ、そんなことをする人はあまりいないと思いますが…

0 件のコメント:

コメントを投稿