Taizoo's Tech note

技術系の備忘録

Android GoogleMapのマーカーを端末の向きに応じて回転させる

前回は、GoogleMapに現在位置を表示するところまでやりました。
今回は、センサーを使用して端末が向いている向きに応じてマーカーを回転させたいと思います。

方針

  • 向いている方向がわかりやすいようにマップに表示するマーカーをカスタマイズする。
  • 端末の向き(方位角)は、SensorManagerを使用して取得した地磁気と加速度の値を利用して求める。
  • センサーを扱う処理は、MySensorManagerクラスに集約し、getAzumith()で最新の方位角を取得できるようにする。
  • 前回作成したMapsActivityクラスからMySensorManagerクラスを利用する。

ソースコード

/**
 * MySensorManagerクラス
 */
public class MySensorManager implements SensorEventListener {

    private final SensorManager sensorManager;
    private float[] gravity = new float[3];
    private float[] geomagnetic = new float[3];
    private float azimuth = 0.0f;

    /**
     * コンストラクタ
     * @param context コンテキスト
     */
    public MySensorManager(Context context) {
        // SensorManagerのインスタンス取得
        sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
    }

    @Override
    public void onSensorChanged(SensorEvent event) {

        // センサーのタイプに応じて値を取得
        switch (event.sensor.getType()) {
            case Sensor.TYPE_MAGNETIC_FIELD:
                geomagnetic = event.values.clone();
                break;
            case Sensor.TYPE_ACCELEROMETER:
                gravity = event.values.clone();
                break;
        }

        // 地磁気と加速度の両方の値が揃っていない場合、方位角の算出処理をスキップ
        if (null == geomagnetic || null == gravity) {
            return;
        }

        float[] R  = new float[16];     // 回転行列Rの値を格納する
        float[] value = new float[3];   // 方位角、ピッチ、ロールの回転角を格納する配列

        // 地磁気と加速度の値から回転行列を求める
        SensorManager.getRotationMatrix(R, null, gravity, geomagnetic);

        // 回転行列に基づいて方位角と傾きを算出
        SensorManager.getOrientation(R, value);

        // 方位角をラジアンから度に変換
        azimuth = (float) (value[0] * 180 / Math.PI);
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {}

    /**
     * センサー値の取得を開始する。
     */
    public void startSensor() {
        // 地磁気センサー値の取得を開始
        sensorManager.registerListener(
                this,
                sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD),
                SensorManager.SENSOR_DELAY_NORMAL);

        // 加速度センサー値の取得を開始
        sensorManager.registerListener(
                this,
                sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
                SensorManager.SENSOR_DELAY_NORMAL);
    }

    /**
     * センサー値の取得を停止する。
     */
    public void stopSensor() {
        sensorManager.unregisterListener(this);
    }

    /**
     * 方位角を取得する。
     * @return 方位角
     */
    public float getAzimuth() {
        return azimuth;
    }
}

ソースのざっくり解説

  1. MySensorManagerクラスは、SensorEventListenerをimplements
  2. コンストラクタで、SensorManagerインスタンスを取得
  3. startSensor()で、地磁気と加速度の取得を開始
  4. stopSensor()で、地磁気と加速度の取得を停止
  5. onSensorChanged()は、センサー値が変更した際に呼び出される
    onSensorChanged()にて、取得したセンサー値から方位角を算出
  6. getAzumith()で、方位角を返却

今回のポイント

マーカーのアイコンをカスタマイズ

向きがわかりやすいようにアイコンをカスタマイズします。
イコン画像をdrawableフォルダに配置することで、オリジナル画像をマーカーとして使うことができます。 BitmapDescriptorFactory.fromResource()で指定してます。
f:id:Taizoo:20220123125444p:plain

@Override
public void onMapReady(GoogleMap googleMap) {
    mMap = googleMap;
    LatLng sydney = new LatLng(-34, 151);
    marker = mMap.addMarker(new MarkerOptions().position(sydney).title("Marker in Sydney"));
    marker.setIcon(BitmapDescriptorFactory.fromResource(R.drawable.icon));
    mMap.moveCamera(CameraUpdateFactory.newLatLng(sydney));
}

方位角の算出

方位角は、onSensorChanged()のメソッド内で計算しています。 今回は、ソースコードのコメントに私自身の解釈で説明を記載しました。
正直、地磁気と加速度の値から回転行列を求め、回転行列から方位角が算出される仕組みまでは理解していません。下記の参考サイトを参考にさせていただきました。
個人的なポイントとしては、MySensorManagerクラスにセンサー関連の処理を集約させることで、MapsActivityクラスのコード量を減らしているところが工夫した点です。

Marker.setRotation()でアイコンの向きをセット

方位角は、MySensorManager.getAzimuth()で取得し、setRotation()でセットしています。

locationCallback = new LocationCallback() {
    @Override
    public void onLocationResult(@NonNull LocationResult locationResult) {
        super.onLocationResult(locationResult);
        Location location = locationResult.getLastLocation();
        LatLng latlng = new LatLng(location.getLatitude(), location.getLongitude());
        mMap.moveCamera(CameraUpdateFactory.newLatLng(latlng));
        marker.setPosition(latlng);
        marker.setRotation(mySensorManager.getAzimuth());
    }
};

実行結果

実行すると、このように向きに応じてマーカーが回転するようになりました。 デフォルトのマーカーよりも移動している感じが格段にわかりやすくなりました。 f:id:Taizoo:20220123172108p:plain なお、マーカーのアイコンは、icon8の素材を使用させていただいています。

参考にしたサイト

端末の向きと傾きを取得する方法 - 加速度センサーと地磁気センサーの利用 - Androidプログラミングの基礎 - Android 開発入門
Androidで世界座標系の加速度と方位を取得する - Qiita

リファレンス

Sensor  |  Android Developers
SensorManager  |  Android Developers
SensorEventListener  |  Android Developers

icon8

https://icons8.com/
GPSデバイス icon by Icons8