Deep Insider の Tutor コーナー
>>  Deep Insider は本サイトからスピンオフした姉妹サイトです。よろしく! 
Xamarin逆引きTips

Xamarin逆引きTips

Plugins for Xamarinを使いこなすには?(Device Motion ― 磁気センサー/コンパス編)

2016年4月8日

デバイス固有の機能に簡単にアクセスできるPlugins for Xamarinの一つ、「Device Motion Plugin」プラグインを紹介。今回は、Magnetometer(磁気)センサー、Compass(コンパス)の機能を使う方法を説明する。

田淵 義人(@ytabuchi
  • このエントリーをはてなブックマークに追加

 前回は「Device Motion Plugin」で使えるセンサー機能のうち、Accelerometer(加速度)センサー、Gyroscope(ジャイロスコープ)センサーを紹介した。今回は引き続きMagnetometer(磁気)センサーCompass(コンパス)を紹介する。

  • 本連載ではXamarin for Visual Studioの最新版を使用している。2016年3月末現在の最新版4.0.1.147では、[Blank App (Xamarin.Forms Portable)]テンプレートを使って新規プロジェクトを作成すると、PCL/Android/iOS/UWP/Windows 8.1(ストアアプリ)/Windows Phone 8.1の6つのプロジェクトが作成されるが、本連載ではAndroid/iOS/UWPでのスクリーンショットを使用している。ぜひ最新版にアップデートしてUWPアプリの作成も試してもらいたい。

Device Motion Pluginのインストール

 Xamarin.FormsのプロジェクトをVisual StudioもしくはXamarin Studioで開き、Visual Studioの場合は[ソリューション エクスプローラー]で一番上のソリューション項目を右クリックして(表示されるコンテキストメニューから)[ソリューションの NuGet パッケージの管理]をクリックする(Xamarin Studioの場合は[ソリューション]ビューの各プロジェクト項目からNuGetパッケージを追加できる)。

 これにより表示されたページの[参照]タブで「Xam.Plugin.DeviceMotion」を検索し、Device Motionを全てのプロジェクトに対して[インストール]する。

Device Motion Pluginの概要

 前回の繰り返しになるが、Device Motion Pluginの概要だ。CrossDeviceMotionクラス(DeviceMotion.Plugin名前空間)のCurrentプロパティを使用して、Accelerometer(加速度)センサーGyroscope(ジャイロスコープ)センサーMagnetometer(磁気)センサーCompass(コンパス)にアクセスできる。

 センサーの種類を表すMotionSensorType列挙体にAccelerometerGyroscopeMagnetometerCompassが含まれており、センサーの応答間隔を表すMotionSensorDelay列挙体には以下が含まれている。単位はミリ秒だ。

  • Fastest = 0,
  • Game = 20,
  • Ui = 60,
  • Default = 200

 戻り値の型であるMotionSensorValueType列挙体はSingleVectorがあり、AccelerometerGyroscopeMagnetometerVector値を、CompassSingle値を返す。

 APIの詳細はGitHubのReadme(英語)を参照してほしい。

 今回は「Magnetometer」と「Compass」を紹介する。

Magnetometer(磁気)センサー

 端末に組み込まれた磁気センサーに磁石が近づいた際に大きく反応するセンサーだ(もちろん地磁気にも反応する)。iPhoneやiPadは機種によって磁気センサーの位置が違い、筆者の手持ちの端末で確認したところ、iPhone 5sは右上に、iPhone 6 Plusは左上に、iPad Mini 2は右中央に磁気センサーの存在が確認できた。Android端末のNexus 5とSH-01Fは左上に、GL07Sは中央上にセンサーを確認できた。

 MagnetometerセンサーもAccelerometerと同様に、次のコードでX/Y/Z軸方向の磁力を取得できるが、近づける磁石の極や磁力によって取得できる値のS極/N極や大きさが変わる(そのため、この数値だけを使って処理を分岐するなどは難しいだろう。通常は、他のセンサーの値と組み合わせて、例えば後述するコンパスのような方位計算やデバイスの位置計算などを実現する場合が多い)。

C#
using DeviceMotion.Plugin;
using DeviceMotion.Plugin.Abstractions;
using System.Diagnostics;
……省略……

IDeviceMotion motion = CrossDeviceMotion.Current; // <- 1
motion.Start(MotionSensorType.Magnetometer, MotionSensorDelay.Default); // <- 2
motion.SensorValueChanged += (object sender, SensorValueChangedEventArgs e) => // <- 3
{
 //Device.BeginInvokeOnMainThread(() =>
 //{
   Debug.WriteLine(((MotionVector)e.Value).X); // <- 4
   Debug.WriteLine(((MotionVector)e.Value).Y);
   Debug.WriteLine(((MotionVector)e.Value).Z);
 //}); // <- 5
};
リスト1 磁気センサーのX/Y/Z値を取得するコード例

 Accelerometerと同じ注意点となるが、念のため再掲する。

1 CurrentプロパティでCrossDeviceMotionクラスのインスタンスを取得する。そのインスタンスをメンバー変数に代入して保持しておかないと、ガベージコレクションによりオブジェクトが自動削除されることがあるので注意してほしい。

2 Startメソッドに、引数としてMotionSensorType(センサーの種類)とMotionSensorDelay(センサー値の取得間隔)を与えて、センサー値の取得を開始する。

3 必要に応じてIsActive(MotionSensorType sensorType)メソッドでセンサーがアクティブなことを確認し、SensorValueChangedイベントのイベントハンドラーで、磁力が変化した際の処理を記述すればよいだろう。

4 SensorValueChangedイベントハンドラーの引数として渡されたSensorValueChangedEventArgsオブジェクトのValueプロパティ値は、2で指定したMotionSensorType値に基づき、適切な型にキャストしてほしい。リスト1では、X/Y/Zの値を取得するためにMotionVector型にキャストしている。

5 センサー値の取得は、バックグラウンドのスレッドで動作している。そのため、Viewの値を変更する場合は、その処理をDevice.BeginInvokeOnMainThread(() =>{ }でくくる必要があるので注意してほしい(上記のコードでは、Viewが動作するメインスレッドにアクセスする必要がないのでコメントアウトしている)。BeginInvokeOnMainThreadメソッドは、名前のとおりだが、メインスレッド上のコードを呼び出すためのものである。

注意点

 磁気センサーのAPIは現時点ではWindows Store(つまりUWPアプリ)とWindows Phone 8(Silverlight)では動作しない。

 Device Motion Pluginを使えば上記のように簡単に磁気センサーを取得できるが、これを使わずに自力で取得する方法も説明しておこう。

Xamarin.iOSでのMagnetometerの取得方法

 ざっくりとした説明はAccelerometer(加速度)センサー/Gyroscope(ジャイロスコープ)センサーの説明に記載したのでそちらを参照してほしい。

 CMMotionManagerオブジェクトのStartMagnetometerUpdatesメソッドでセンサー値の取得を開始するとよい。CMMagnetometerDataオブジェクトのMagneticField.XMagneticField.YMagneticField.Zプロパティで各値を取得できる。

Xamarin.AndroidでのMagnetometerの取得方法

 こちらも、ざっくりとした説明はAccelerometer(加速度)センサー/Gyroscope(ジャイロスコープ)センサーの説明に記載したのでそちらを参照してほしい。

 SensorManagerオブジェクトのRegisterListenerメソッドを使用してセンサー監視を登録する。その第2引数に指定するSensorオブジェクトは、Sensor.GetDefaultSensorメソッドで取得した値である。このGetDefaultSensorメソッドの引数には、SensorType列挙体の(AccelerometerGyroscopeの代わりに)MagneticFieldを指定するとよい。

Compass(コンパス)

 名前そのものでコンパスを使用する。

 コンパスのセンサー値を取得するコードは、先ほどのMagnetometerの場合と比較して、MotionSensorType列挙体のCompass値を指定する点とSensorValueChangedイベントハンドラーの引数として渡されたSensorValueChangedEventArgsオブジェクトのValueプロパティ値が(MotionVector型ではなく)MotionValue型となる点が異なる。キャストも不要でDebug.WriteLine(a.Value.Value);などのコードで確認できる*1

  • *1 なお、a.Value.ValueとValueが二重になっているのは、単にa.Valueでは自動的にMotionValueオブジェクトのToStringメソッドが呼ばれて「Value = XXX」という値を取得してしまうため。Value.Valueプロパティ値を参照することで数値を取得できる。

 なお、iOSでコンパスセンサーを使用する際は、OSが誤差を自動的に吸収してくれる。誤差があると判断された場合は以下のような画面が表示されるのはOS標準のコンパスアプリと同様だ。

コンパス調整

 コンパス値についても、Device Motion Pluginを使わずに自力で取得する方法を説明しておこう。

Xamarin.iOSでのCompassの取得方法

 iOSでコンパスを使用するにはCLLocationManagerオブジェクトのStartUpdatingHeadingメソッドを使用してセンサー値の取得を開始する。HeadingオブジェクトのMagneticHeadingプロパティで「磁北」*2double値を取得でき、TrueHeadingプロパティで「真北」*2double値を取得できる。取得を停止するにはStopUpdateHeadingメソッドを使用する。例えば次のようなコードで取得できる。

C#
using CoreLocation;

locationManager = new CLLocationManager();
locationManager.StartUpdatingHeading();
locationManager.UpdatedHeading += (object s, CLHeadingUpdatedEventArgs a) =>
{
  AngleLabel.Text = string.Format($"{locationManager.Heading.MagneticHeading:N0}°");
};
リスト2 Xamarin.iOSで「磁北」を基準にした方位角(=MagneticHeading)の値を取得するコード例

ちなみに、前述のDevice Motion Pluginのコンパスで取得される値は、「真北」を基準にした方位角(=TrueHeadingプロパティ値)となり、このコードの実行結果の値とは微妙に異なる可能性がある。

 CLHeadingクラスの詳細は「CLHeading Class - Xamarin(英語)」を参照してほしい。

  • *2 「磁北」(=方位磁石のN極が指す方向)と「真北」(=北極点を指す方位)の値は微妙にずれている(=磁気偏角)。両者の違いは「国土地理院 地磁気測量:地磁気を知る」を参考にしてほしい。
Xamarin.AndroidでのCompassの取得方法

 Androidでは、コンパスセンサーが存在しないため、加速度センサーと磁気センサーを使用して(磁北を基準にした)方位角を算出する必要がある。

 ざっくり説明すると、まずSensorManagerクラスのRegisterListener(ISensorEventListener, Sensor, SensorDelay, Int32, Handler) : Booleanメソッドで第2引数にSensorType.Accelerometer列挙体値とSensorType.MagneticField列挙体値をそれぞれ指定して加速度センサーと磁気センサーの取得を開始する。その後OnSensorChangedイベントハンドラーで、SensorManagerクラスのGetRotationMatrix(Single[], Single[], Single[], Single[]) : Booleanメソッドで回転行列を求め、RemapCoordinateSystem(Single[], Android.Hardware.Axis, Android.Hardware.Axis, Single[]) : Booleanメソッドで端末の画面の向きに合わせて変換行列を求め、GetOrientation(Single[], Single[]) : Single[]メソッドで方位角と傾きを求める*3。センサーの取得停止はUnregisterListener(ISensorEventListener)で行う。例えば次のようなコードで取得できる。

C#
switch (e.Sensor.Type)
{
  case SensorType.Accelerometer:
    accelerometerValue = new float[3];
    accelerometerValue = e.Values.ToArray();
    break;
  case SensorType.MagneticField:
    magneticFieldValue = new float[3];
    magneticFieldValue = e.Values.ToArray();
    break;
  default:
    break;
}

if (magneticFieldValue != null && accelerometerValue != null)
{
  float[] Rotate1 = new float[16];
  float[] Rotate2 = new float[16];
  float[] Inclination = new float[16];
  float[] val = new float[3];

  // 1 加速度センサーと磁気センサーの値から回転行列を求める
  SensorManager.GetRotationMatrix(Rotate1, Inclination, accelerometerValue, magneticFieldValue);

  // 2 端末の画面設定に合わせる変換行列を求める(以下は、縦表示で画面を上にした場合)
  SensorManager.RemapCoordinateSystem(Rotate1, Android.Hardware.Axis.X, Android.Hardware.Axis.Y, Rotate2);

  // 3 方位角および傾きを求める
  SensorManager.GetOrientation(Rotate2, val);

  // 4 ラジアンを角度に変換
  for (var i = 0; i < 3; i++)  
  {
    val[i] = (float)(val[i] * 180 / Math.PI);
  }

  System.Diagnostics.Debug.WriteLine("{0:F0}°", (val[0] < 0) ? val[0] + 360 : val[0]);
}
リスト3 Xamarin.Androidで「磁北」を基準にした方位角の値を取得するコード例

ちなみに、前述のDevice Motion Pluginのコンパスで取得される値も、同じく「磁北」を基準にした方位角となっているが、執筆時点のソースコードを確認する限り、非推奨のSensor.TYPE_ORIENTATIONを使用しており、結果の値に違いが出る可能性がある(ちなみにAndroidにおける方位角は、このコードのように、GetRotationMatrix()RemapCoordinateSystem()GetOrientation()メソッドを組み合わせて算出することが推奨されている)。ほとんどの場合、この磁方位角で事足りるだろうが、真方位角を取得したい場合には、その計算のために磁気偏角(declination)を求める必要がある。詳しいコード内容は割愛するが、磁気偏角はGeomagneticFieldクラス(Android.Hardware名前空間)のDeclinationプロパティを使って取得でき、磁北+磁気偏角で真北の方位角が得られる。

 SensorManager APIについては「SensorManager Class - Xamarin(英語)」を参照してほしい。

まとめ

 Device Motion Pluginを使用することで、iOS/Android/UWP/Windows Phoneなどで簡単に各種センサーの機能を使用できる。皆さんのアプリに、タップ以外のモーションによる操作を追加したい場合にぜひ活用してほしい。

※以下では、本稿の前後を合わせて5回分(第62回~第66回)のみ表示しています。
 連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。

Xamarin逆引きTips
62. Plugins for Xamarinを使いこなすには?(ファイルシステム編)

デバイス固有の機能に簡単にアクセスできるPlugins for Xamarinを複数回にわたって紹介していく。今回は、簡単にファイルの入出力が行える「PCL Storage」プラグインを説明する。

Xamarin逆引きTips
63. Plugins for Xamarinを使いこなすには?(GPS編)

デバイス固有の機能に簡単にアクセスできるPlugins for Xamarinを複数回にわたって紹介していく。今回は、GPSの機能を使える「Geolocator」プラグインを説明する。

Xamarin逆引きTips
64. Plugins for Xamarinを使いこなすには?(Device Motion ― 加速度センサー/ジャイロスコープセンサー編)

デバイス固有の機能に簡単にアクセスできるPlugins for Xamarinの一つ、「Device Motion Plugin」プラグインを紹介。今回は、Accelerometer(加速度)センサー、Gyroscope(ジャイロスコープ)センサーの機能を使う方法を説明する。

Xamarin逆引きTips
65. 【現在、表示中】≫ Plugins for Xamarinを使いこなすには?(Device Motion ― 磁気センサー/コンパス編)

デバイス固有の機能に簡単にアクセスできるPlugins for Xamarinの一つ、「Device Motion Plugin」プラグインを紹介。今回は、Magnetometer(磁気)センサー、Compass(コンパス)の機能を使う方法を説明する。

Xamarin逆引きTips
66. Xamarin Workbooksを使用するには?(REPL&リッチテキスト編)

C#のREPLアプリとして対話型でコード実行ができるだけでなく、そのコード実行をコンテンツに含めたリッチ文書が作成できるXamarin Workbooksの基本的な使い方を解説。

サイトからのお知らせ

Twitterでつぶやこう!