位置センサー

Android プラットフォームには、端末の位置を調べるために地磁気センサーと加速度計の 2 つのセンサーが提供されています。さらに Android プラットフォームには、端末の表面にどれほど近い所に物体があるかを調べるセンサー(近接センサー)も提供されています。地磁気センサーと近接センサーは、ハードウェア ベースです。大半のハンドセットやタブレットのメーカーは地磁気センサーを提供しています。同じように、多くの場合、ハンドセットのメーカーは、ハンドセットがユーザーの顔の近くにあるかどうかを(通話中などに)調べるための近接センサーを提供しています。端末の画面の向きを調べるには、端末の加速度計と地磁気センサーからの読み取り値を使用することができます。

注:オリエンテーション センサーは Android 2.2(API レベル 8)で廃止され、オリエンテーション センサー タイプは Android 4.4W(API レベル 20)で廃止されました。

位置センサーは、世界座標系の中での端末の物理的位置を調べるのに役立ちます。たとえば、地磁気センサーと加速度計を組み合わせることにより、磁北極を基準にした端末の位置を調べることができます。また、これらのセンサーを利用することにより、アプリケーションの座標系に関する端末画面の向きを調べることもできます。通常、位置センサーが、揺らしたり、傾けたり、押し付けたりするような端末の動きをモニターすることに使用されることはありません(詳しくは、モーション センサーを参照)。

地磁気センサーと加速度計は、各 SensorEvent についてセンサー値の多次元配列を返します。たとえば、地磁気センサーは、1 つのセンサーイベント中に、3 つの座標軸それぞれについての地磁気強度値を提供します。同じように加速度計センサーは、センサーイベント中に端末に加えられる加速度を測定します。センサーで使用される座標系について詳しくは、センサー座標系をご覧ください。近接センサーは、センサーイベントごとに 1 つの値を提供します。表 1 に、Android プラットフォームでサポートされている位置センサーをまとめます。

表 1. Android プラットフォームでサポートされている位置センサー。

センサー センサーイベント データ 説明 測定単位
TYPE_GAME_ROTATION_VECTOR SensorEvent.values[0] x 軸に関する回転ベクトル成分(x * sin(θ/2))。 単位なし
SensorEvent.values[1] y 軸に関する回転ベクトル成分(y * sin(θ/2))。
SensorEvent.values[2] z 軸に関する回転ベクトル成分(z * sin(θ/2))。
TYPE_GEOMAGNETIC_ROTATION_VECTOR SensorEvent.values[0] x 軸に関する回転ベクトル成分(x * sin(θ/2))。 単位なし
SensorEvent.values[1] y 軸に関する回転ベクトル成分(y * sin(θ/2))。
SensorEvent.values[2] z 軸に関する回転ベクトル成分(z * sin(θ/2))。
TYPE_MAGNETIC_FIELD SensorEvent.values[0] x 軸方向の地磁気強度。 μT
SensorEvent.values[1] y 軸方向の地磁気強度。
SensorEvent.values[2] z 軸方向の地磁気強度。
TYPE_MAGNETIC_FIELD_UNCALIBRATED SensorEvent.values[0] x 軸方向の地磁気強度(hard iron キャリブレーションなし)。 μT
SensorEvent.values[1] y 軸方向の地磁気強度(hard iron キャリブレーションなし)。
SensorEvent.values[2] z 軸方向の地磁気強度(hard iron キャリブレーションなし)。
SensorEvent.values[3] x 軸方向の鉄バイアス推定値。
SensorEvent.values[4] y 軸方向の鉄バイアス推定値。
SensorEvent.values[5] z 軸方向の鉄バイアス推定値。
TYPE_ORIENTATION1 SensorEvent.values[0] 方位角(z 軸を中心とした角度)。
SensorEvent.values[1] 勾配(x 軸を中心とした角度)。
SensorEvent.values[2] 回転(y 軸を中心とした角度)。
TYPE_PROXIMITY SensorEvent.values[0] 物体からの距離。2 cm

1このセンサーは Android 2.2(API レベル 8)で廃止され、このタイプは Android 4.4W(API レベル 20)で廃止されました。センサー フレームワークには、端末画面の向きを調べるための代替メソッドが提供されています。それらについては、端末画面の向きの計算で説明されています。

2 一部の近接センサーの場合、提供されるのは近いか遠いかを示すバイナリ値だけです。

ゲーム回転ベクトル センサーを使用する

ゲーム回転ベクトル センサーは、地磁気場を使用しないという点を除けば、回転ベクトル センサーと同じです。したがって、Y 軸は北ではない別の基準を指す方向になります。その基準により、Z 軸の周りのジャイロスコープ ドリフトと同じ桁数のドリフトが可能になります。

ゲーム回転ベクトル センサーでは地磁気場を使用しないため、相対的な回転の精度が高くなり、磁場の変化の影響を受けることがありません。北がどの方向かに関係ないゲームではこのセンサーを使用してください。その場合、磁場に依存する通常の回転ベクトルでは、必要を満たすものとなりません。

以下のコードは、デフォルトのゲーム回転ベクトル センサーのインスタンスを取得する方法を示します:

Kotlin

private lateinit var sensorManager: SensorManager
private var sensor: Sensor? = null
...
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GAME_ROTATION_VECTOR)

Java

private SensorManager sensorManager;
private Sensor sensor;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GAME_ROTATION_VECTOR);

地磁気回転ベクトル センサーを使用する

地磁気回転ベクトル センサーは、回転ベクトル センサーによく似ていますが、ジャイロスコープの代わりに磁力計を使用します。このセンサーの精度は通常の回転ベクトル センサーよりも落ちますが、電力消費が少なくて済みます。このセンサーは、電池の消費を抑えつつ、バックグラウンドで若干の回転情報を収集する場合にのみ使用してください。このセンサーは、バッチ処理と共に使用する場合に便利です。

以下のコードは、デフォルトの地磁気回転ベクトル センサーのインスタンスを取得する方法を示します:

Kotlin

private lateinit var sensorManager: SensorManager
private var sensor: Sensor? = null
...
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR)

Java

private SensorManager sensorManager;
private Sensor sensor;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR);

端末の画面の向きを計算する

端末画面の向きを計算することにより、地球座標系(特に磁北極)を基準にした端末の位置をモニターすることができます。以下のコードは、端末画面の向きを計算する方法を示すものです:

Kotlin

private lateinit var sensorManager: SensorManager
...
// Rotation matrix based on current readings from accelerometer and magnetometer.
val rotationMatrix = FloatArray(9)
SensorManager.getRotationMatrix(rotationMatrix, null, accelerometerReading, magnetometerReading)

// Express the updated rotation matrix as three orientation angles.
val orientationAngles = FloatArray(3)
SensorManager.getOrientation(rotationMatrix, orientationAngles)

Java

private SensorManager sensorManager;
...
// Rotation matrix based on current readings from accelerometer and magnetometer.
final float[] rotationMatrix = new float[9];
SensorManager.getRotationMatrix(rotationMatrix, null,
    accelerometerReading, magnetometerReading);

// Express the updated rotation matrix as three orientation angles.
final float[] orientationAngles = new float[3];
SensorManager.getOrientation(rotationMatrix, orientationAngles);

システムにより、端末の地磁気センサーと端末の加速度計を利用して画面の向きの角度が計算されます。これらの 2 つのハードウェア センサーを使用することにより、画面の向きを示す以下の 3 つの角度のデータが提供されます:

  • 方位角(z 軸に関する回転度数)。これは、端末の現在のコンパス方位と磁北極の間の角度です。端末上部の縁が磁北極を向いているなら方位角は 0 度になります。上部の縁が南を向いているなら方位角は 180 度です。同じように、上部の縁が東を向いているなら方位角は 90 度、上部の縁が西を向いているなら方位角は 270 度です。
  • 勾配(x 軸に関する回転度数)。これは、端末画面に平行な平面と、地面に平行な平面との間の角度です。端末を地面と平行にして下部の縁を自分のほうに向けた後、端末上部の縁を地面の方に傾けると、勾配角は正になります。反対に端末上部の縁の方が地面から離れるように傾けた場合、勾配角は負になります。値の範囲は -180 度から 180 度までです。
  • 回転(y 軸に関する回転度数)。これは、端末画面に垂直な平面と、地面に垂直な平面との間の角度です。端末を地面と平行にして下部の縁を自分のほうに向けた後、端末左側の縁を地面の方に傾けると、回転角は正になります。反対に端末右側の縁を地面の方に傾けると、回転角は負になります。値の範囲は -90 度から 90 度までです。

注: センサーの回転の定義は、ジオセンサー エコシステムの大半の実装を反映するように変更されました。

これらの角度は、航空機で使用される座標系(ヨー、ピッチ、ロール)とは異なる座標系であることに注意してください。航空機系の場合、x 軸は飛行機の後尾から機首への前後方向です。

オリエンテーション センサーのデータは、加速度計と地磁気センサーの生のセンサーデータを処理することによって得られます。それは重い処理であるため、オリエンテーション センサーの正確さと精度は低くなります。特に、このセンサーが信頼に値するのは、回転角が 0 の場合のみです。そのため、オリエンテーション センサーは Android 2.2(API レベル 8)で廃止され、オリエンテーション センサー タイプは Android 4.4W(API レベル 20)で廃止されました。オリエンテーション センサーからの生データを使用する代わりに、getRotationMatrix() メソッドを getOrientation() メソッドと組み合わせることによって、画面の向きの値を計算することをお勧めします。次のコード サンプルはそれを示すものです。このプロセスの一部として、remapCoordinateSystem() メソッドを使用することにより、画面の向きの値を、アプリケーションの座標系に変換することができます。

Kotlin

class SensorActivity : Activity(), SensorEventListener {

    private lateinit var sensorManager: SensorManager
    private val accelerometerReading = FloatArray(3)
    private val magnetometerReading = FloatArray(3)

    private val rotationMatrix = FloatArray(9)
    private val orientationAngles = FloatArray(3)

    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main)
        sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
    }

    override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {
        // Do something here if sensor accuracy changes.
        // You must implement this callback in your code.
    }

    override fun onResume() {
        super.onResume()

        // Get updates from the accelerometer and magnetometer at a constant rate.
        // To make batch operations more efficient and reduce power consumption,
        // provide support for delaying updates to the application.
        //
        // In this example, the sensor reporting delay is small enough such that
        // the application receives an update before the system checks the sensor
        // readings again.
        sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)?.also { accelerometer ->
            sensorManager.registerListener(
                    this,
                    accelerometer,
                    SensorManager.SENSOR_DELAY_NORMAL,
                    SensorManager.SENSOR_DELAY_UI
            )
        }
        sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)?.also { magneticField ->
            sensorManager.registerListener(
                    this,
                    magneticField,
                    SensorManager.SENSOR_DELAY_NORMAL,
                    SensorManager.SENSOR_DELAY_UI
            )
        }
    }

    override fun onPause() {
        super.onPause()

        // Don't receive any more updates from either sensor.
        sensorManager.unregisterListener(this)
    }

    // Get readings from accelerometer and magnetometer. To simplify calculations,
    // consider storing these readings as unit vectors.
    override fun onSensorChanged(event: SensorEvent) {
        if (event.sensor.type == Sensor.TYPE_ACCELEROMETER) {
            System.arraycopy(event.values, 0, accelerometerReading, 0, accelerometerReading.size)
        } else if (event.sensor.type == Sensor.TYPE_MAGNETIC_FIELD) {
            System.arraycopy(event.values, 0, magnetometerReading, 0, magnetometerReading.size)
        }
    }

    // Compute the three orientation angles based on the most recent readings from
    // the device's accelerometer and magnetometer.
    fun updateOrientationAngles() {
        // Update rotation matrix, which is needed to update orientation angles.
        SensorManager.getRotationMatrix(
                rotationMatrix,
                null,
                accelerometerReading,
                magnetometerReading
        )

        // "mRotationMatrix" now has up-to-date information.

        SensorManager.getOrientation(rotationMatrix, mOrientationAngles)

        // "mOrientationAngles" now has up-to-date information.
    }
}

Java

public class SensorActivity extends Activity implements SensorEventListener {

    private SensorManager sensorManager;
    private final float[] accelerometerReading = new float[3];
    private final float[] magnetometerReading = new float[3];

    private final float[] rotationMatrix = new float[9];
    private final float[] orientationAngles = new float[3];

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

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
        // Do something here if sensor accuracy changes.
        // You must implement this callback in your code.
    }

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

        // Get updates from the accelerometer and magnetometer at a constant rate.
        // To make batch operations more efficient and reduce power consumption,
        // provide support for delaying updates to the application.
        //
        // In this example, the sensor reporting delay is small enough such that
        // the application receives an update before the system checks the sensor
        // readings again.
        Sensor accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        if (accelerometer != null) {
            sensorManager.registerListener(this, accelerometer,
                SensorManager.SENSOR_DELAY_NORMAL, SensorManager.SENSOR_DELAY_UI);
        }
        Sensor magneticField = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
        if (magneticField != null) {
            sensorManager.registerListener(this, magneticField,
                SensorManager.SENSOR_DELAY_NORMAL, SensorManager.SENSOR_DELAY_UI);
        }
    }

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

        // Don't receive any more updates from either sensor.
        sensorManager.unregisterListener(this);
    }

    // Get readings from accelerometer and magnetometer. To simplify calculations,
    // consider storing these readings as unit vectors.
    @Override
    public void onSensorChanged(SensorEvent event) {
        if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
          System.arraycopy(event.values, 0, accelerometerReading,
              0, accelerometerReading.length);
        } else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
            System.arraycopy(event.values, 0, magnetometerReading,
                0, magnetometerReading.length);
        }
    }

    // Compute the three orientation angles based on the most recent readings from
    // the device's accelerometer and magnetometer.
    public void updateOrientationAngles() {
        // Update rotation matrix, which is needed to update orientation angles.
        SensorManager.getRotationMatrix(rotationMatrix, null,
            accelerometerReading, mMagnetometerReading);

        // "mRotationMatrix" now has up-to-date information.

        SensorManager.getOrientation(rotationMatrix, mOrientationAngles);

        // "mOrientationAngles" now has up-to-date information.
    }
}

通常、端末画面の向きを示す生の角度データに対して、センサーの座標系からアプリケーションの座標系への変換以外のデータ処理やフィルタ処理をする必要はありません。

地磁気センサーを使用する

地磁気センサーを利用すれば、地球の磁場の変化をモニターすることができます。以下のコードは、デフォルトの地磁気ベクトル センサーのインスタンスを取得する方法を示します:

Kotlin

private lateinit var sensorManager: SensorManager
private var sensor: Sensor? = null
...
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)

Java

private SensorManager sensorManager;
private Sensor sensor;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);

このセンサーは、3 つの座標軸のそれぞれについて、生の磁場強度データ(単位μT)を提供します。通常、このセンサーを直接使用する必要はありません。むしろ、回転ベクトル センサーを使用することによって生の回転運動を調べたり、加速度計と地磁気センサーを getRotationMatrix() メソッドと共に使用して回転行列と傾斜行列を得たりすることができます。その後、それらの測定値を getOrientation() メソッドおよび getInclination() メソッドで使用することにより、方位角や地磁気傾斜データを得ることができます。

注:アプリをテストする際、8 の字に端末を動かすことにより、センサーの正確さを改善することができます。

キャリブレーションなしの磁力計を使用する

キャリブレーションなしの磁力計は、地磁気地磁気センサーによく似ていますが、磁場に hard iron キャリブレーションが適用されないという点で異なります。それでも、工場キャリブレーションと温度補正は磁場に適用されます。キャリブレーションなしの磁力計は、精度の悪い hard iron 推定値を処理する際に便利です。一般に、geomagneticsensor_event.values[0]uncalibrated_magnetometer_event.values[0] - uncalibrated_magnetometer_event.values[3] に近い値になります。つまり、

calibrated_x ~= uncalibrated_x - bias_estimate_x

注:キャリブレーションなしのセンサーはさらに多くの生の結果を提供しますが、若干のバイアスがかかっている可能性があります。しかし、その測定値には、キャリブレーションによって適用された修正により飛躍した値がほとんど含まれていません。アプリケーションによっては、滑らかで信頼性が高いということから、これらのキャリブレーションなしの結果を使用する方が良いかもしれません。たとえば、アプリケーションが独自のセンサー融合を試みる場合、キャリブレーションを導入すると実際には結果が歪んでしまいます。

磁場に加えて、キャリブレーションなしの磁力計は軸ごとの hard iron バイアス推定値も提供します。以下のコードは、デフォルトのキャリブレーションなしの磁力計のインスタンスを取得する方法を示します:

Kotlin

private lateinit var sensorManager: SensorManager
private var sensor: Sensor? = null
...
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED)

Java

private SensorManager sensorManager;
private Sensor sensor;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED);

近接センサーを使用する

近接センサーを使用すると、物体が端末からどれだけ離れているかを調べることができます。以下のコードは、デフォルトの近接センサーのインスタンスを取得する方法を示します:

Kotlin

private lateinit var sensorManager: SensorManager
private var sensor: Sensor? = null
...
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)

Java

private SensorManager sensorManager;
private Sensor sensor;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);

通常、近接センサーはハンドセット端末の表面から人の頭がどれだけ離れているかを調べるために使用されます(たとえば、ユーザーが電話で通話する場合)。ほとんどの近接センサーは距離の絶対値(cm 単位)を返しますが、近いか遠いかだけを示す値を返すものもあります。以下のコードは、近接センサーの使用方法を示しています:

Kotlin

class SensorActivity : Activity(), SensorEventListener {

    private lateinit var sensorManager: SensorManager
    private var mProximity: Sensor? = null

    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main)

        // Get an instance of the sensor service, and use that to get an instance of
        // a particular sensor.
        sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
        mProximity = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)
    }

    override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {
        // Do something here if sensor accuracy changes.
    }

    override fun onSensorChanged(event: SensorEvent) {
        val distance = event.values[0]
        // Do something with this sensor data.
    }

    override fun onResume() {
        // Register a listener for the sensor.
        super.onResume()

        mProximity?.also { proximity ->
            sensorManager.registerListener(this, proximity, SensorManager.SENSOR_DELAY_NORMAL)
        }
    }

    override fun onPause() {
        // Be sure to unregister the sensor when the activity pauses.
        super.onPause()
        sensorManager.unregisterListener(this)
    }
}

Java

public class SensorActivity extends Activity implements SensorEventListener {
    private SensorManager sensorManager;
    private Sensor proximity;

    @Override
    public final void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // Get an instance of the sensor service, and use that to get an instance of
        // a particular sensor.
        sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
        proximity = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
    }

    @Override
    public final void onAccuracyChanged(Sensor sensor, int accuracy) {
        // Do something here if sensor accuracy changes.
    }

    @Override
    public final void onSensorChanged(SensorEvent event) {
        float distance = event.values[0];
        // Do something with this sensor data.
    }

    @Override
    protected void onResume() {
        // Register a listener for the sensor.
        super.onResume();
        sensorManager.registerListener(this, proximity, SensorManager.SENSOR_DELAY_NORMAL);
      }

    @Override
    protected void onPause() {
        // Be sure to unregister the sensor when the activity pauses.
        super.onPause();
        sensorManager.unregisterListener(this);
    }
}

注:一部の近接センサーは、「近い」か「遠い」かを示すバイナリ値を返します。その場合、通常、センサーは、遠い状態では範囲内の最大値、近い状態では小さい値を報告します。多くの場合、遠い値は 5 cm より大きい値ですが、それはセンサーごとに異なります。センサーの最大範囲は、getMaximumRange() メソッドを使用することにより調べることができます。

関連ドキュメント