情報
要求されたトピックは次のとおりです。しかし、このトピックはこのライブラリには含まれていません。

方法: Windows Phone のコンパス センサーからデータを取得する

2012/02/09

このトピックでは、コンパス データを数値およびグラフィックで表示するコンパス アプリケーションの作成について説明します。また、このチュートリアルでは、コンパスのキャリブレーション ダイアログを実装する方法も示します。

コンパス センサー、つまり磁力計センサーは、地球の磁北に対するデバイスの回転角度を特定するために使用できます。また、アプリケーションでは、磁力センサーの生の測定値を使用してデバイスの周囲の磁力を検出することもできます。コンパス センサーはすべての Windows Phone デバイスで必須というわけではありません。アプリケーションを設計および実装する際には、この点を考慮する必要があります。アプリケーションでは常にセンサーが利用可能かどうかを確認し、センサーが存在しない場合には代わりの入力機構を提供するか、または問題なくアプリケーションが失敗するようにする必要があります。

コンパス API は、デバイスの向きに応じて、単一の軸を使用して方位を計算します。すべての軸のデバイスの向きを使用するアプリケーションを作成する場合は、Motion クラスの RotationMatrix プロパティを使用する必要があります。

デバイスのコンパス センサーは、時間が経つにつれて、特に磁場にさらされた場合には、精度が低下する可能性があります。ユーザーによる簡単なアクションでコンパスを再校正することができます。Calibrate イベントは、方位精度が +/- 20 度を上回ったことをシステムが検出したときに発生します。この例では、ユーザーがコンパスを校正するために使用できるキャリブレーション ダイアログを実装する方法を示します。

次の手順では、コンパス アプリケーションの作成方法について説明します。

コンパス アプリケーションを作成するには

  1. Visual Studio で、新しい Windows Phone アプリケーション プロジェクトを作成します。このテンプレートは、Silverlight for Windows Phone カテゴリにあります。

  2. このアプリケーションでは、センサー API と XNA Framework を含むアセンブリへの参照が必要です。これは、一部のコンパス データが、XNA Framework の Vector3 オブジェクトの形式で渡されるからです。[プロジェクト] メニューの [参照の追加] をクリックし、[Microsoft.Devices.Sensors] と [Microsoft.Xna.Framework] を選択し、[OK] をクリックします。

  3. MainPage.xaml ファイルで、"ContentPanel" というの名前の Grid 要素に次の XAML コードを配置します。この XAML コードは、コンパス データを表示する UI を作成します。コンパス データを数字で表示するには、TextBlock 要素が使用されます。方位と生のコンパス データをグラフィック表示するには、Line 要素が使用されます。

    <StackPanel Orientation="Vertical">
      <StackPanel Orientation="Horizontal">
        <TextBlock>status: </TextBlock>
        <TextBlock Name="statusTextBlock"></TextBlock>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <TextBlock>time between updates:</TextBlock>
        <TextBlock Name="timeBetweenUpdatesTextBlock"></TextBlock>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <TextBlock>magnetic heading: </TextBlock>
        <TextBlock Name="magneticTextBlock"></TextBlock>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <TextBlock>true heading: </TextBlock>
        <TextBlock Name="trueTextBlock"></TextBlock>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <TextBlock>heading accuracy: </TextBlock>
        <TextBlock Name="accuracyTextBlock"></TextBlock>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <TextBlock>compass orientation mode:</TextBlock>
        <TextBlock Name="orientationTextBlock"></TextBlock>
      </StackPanel>
      <Grid Height="200" Name="headingGrid">
        <TextBlock Foreground="Yellow" FontSize="16">magnetic heading</TextBlock>
        <TextBlock Foreground="Orange" FontSize="16" Margin="0,18">true heading</TextBlock>
        <Line x:Name="magneticLine" X1="240" Y1="100" X2="240" Y2="0" Stroke="Yellow" StrokeThickness="4"></Line>
        <Line x:Name="trueLine" X1="240" Y1="100" X2="240" Y2="0" Stroke="Orange" StrokeThickness="4"></Line>
      </Grid>
      <TextBlock Text="raw magnetometer data:"></TextBlock>
      <Grid>
        <TextBlock Height="30" HorizontalAlignment="Left" Name="xTextBlock" Text="X: 1.0" VerticalAlignment="Top" Foreground="Red" FontWeight="Bold"/>
        <TextBlock Height="30" HorizontalAlignment="Center" Name="yTextBlock" Text="Y: 1.0" VerticalAlignment="Top" Foreground="Green" FontWeight="Bold"/>
        <TextBlock Height="30" HorizontalAlignment="Right"  Name="zTextBlock" Text="Z: 1.0" VerticalAlignment="Top"  Foreground="Blue" FontWeight="Bold"/>
      </Grid>
      <Grid Height="140">
        <Line x:Name="xLine" X1="240" Y1="40" X2="240" Y2="40" Stroke="Red" StrokeThickness="14"></Line>
        <Line x:Name="yLine" X1="240" Y1="70" X2="240" Y2="70" Stroke="Green" StrokeThickness="14"></Line>
        <Line x:Name="zLine" X1="240" Y1="100" X2="240" Y2="100" Stroke="Blue" StrokeThickness="14"></Line>
      </Grid>
    </StackPanel>
    
    
    

    これは UI の表示方法を示しています。

    コンパス アプリケーション用の UI
  4. 次に、コンパス キャリブレーション ダイアログの UI を定義する XAML を追加します。この UI はユーザーに画像を表示し、コンパスを校正するためにデバイスを動かす方法を示します。外側の StackPanelVisibility.Collapsed に設定され、ユーザーに対して非表示になっています。この UI を表示するコードは、後でこのチュートリアルの中で示します。

    <!--Calibration UI-->
    <StackPanel Name="calibrationStackPanel" Background="Black" Opacity="1" Visibility="Collapsed">
      <Image Source="/Images/calibrate_compass.png" Opacity=".95" HorizontalAlignment="Center"/>
      <TextBlock TextWrapping="Wrap" TextAlignment="Center">The compass on your device needs to be calibrated.
      Hold the device in front of you and sweep it through a figure 8 pattern as shown
      until the calibration is complete.</TextBlock>
      <StackPanel Orientation="Horizontal" Margin="0,10" HorizontalAlignment="Center">
        <TextBlock>heading accuracy:</TextBlock>
        <TextBlock Name="calibrationTextBlock">0.0°</TextBlock>  
      </StackPanel>
      <Button Name="calibrationButton" Content="Done" Click="calibrationButton_Click"></Button>
    </StackPanel>
    <!--End Calibration UI-->
    
    
    

    キャリブレーション UI は次のように表示されます。

    コンパス校正画面の UI
  5. 前に定義したコンパス キャリブレーション ダイアログでは、画像を使用して、コンパスを校正するためにユーザーがデバイスをスイープするパターンを示しています。ソリューションに画像を追加するには、画像をプロジェクト ディレクトリの "Images" という名前のサブフォルダーにコピーします。ソリューション エクスプローラーでアプリケーション プロジェクトを右クリックし、[追加]、[既存の項目] の順にクリックします。画像を選択し、[追加] をクリックします。画像を追加した後、ソリューション エクスプローラーで画像アイコンを右クリックし、[プロパティ] をクリックします。[ビルド アクション] プロパティが [コンテンツ] に設定されていることを確認します。このサンプルで使用する画像は、「Windows Phone のコード サンプル」からダウンロードできる生センサー データのサンプルに含まれています。

  6. MainPage.xaml に追加する最後の UI コードは、コンパスからのデータの取得を開始および停止する 1 つのボタンを含むアプリケーション バーの定義です。次のコードを、プロジェクト テンプレートに含まれているコメントアウトされたアプリケーション バーのコードに貼り付けます。

    <phone:PhoneApplicationPage.ApplicationBar>
      <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
        <shell:ApplicationBarIconButton IconUri="/Images/onoff.png" Text="on/off" Click="ApplicationBarIconButton_Click"/>
      </shell:ApplicationBar>
    </phone:PhoneApplicationPage.ApplicationBar>
    
    
    
  7. MainPage.xaml.cs 分離コード ページを開き、センサーと XNA Framework 名前空間の using ディレクティブを、ページの先頭にある他の using ディレクティブに追加します。この例では、タイマーを使用して UI を更新するので、System.Windows.Threading 名前空間も含めます。

    using Microsoft.Devices.Sensors;
    using Microsoft.Xna.Framework;
    using System.Windows.Threading;
    
    
  8. MainPage クラス定義の先頭に、いくつかのメンバー変数を宣言します。

    public partial class MainPage : PhoneApplicationPage
    {
      Compass compass;
      DispatcherTimer timer;
    
      double magneticHeading;
      double trueHeading;
      double headingAccuracy;
      Vector3 rawMagnetometerReading;
      bool isDataValid;
    
      bool calibrating = false;
    
    

    最初の変数は、Compass 型のオブジェクトで、コンパス センサーからデータを取得するために使用されます。次に、UI を定期的に更新するために使われる DispatcherTimer を宣言します。その後に、コンパス データを格納する変数のセットがあります。これらはコンパス API を使用して設定され、DispatcherTimerTick イベントに表示されます。最後に、ブール変数 calibrating を使用して、キャリブレーション ダイアログが現在表示されているかどうかを追跡します。

  9. ページのコンストラクターで、アプリケーションが実行されているデバイスがコンパス センサーをサポートしているかどうかを確認します。すべてのデバイスがすべてのセンサーをサポートしているわけではないため、センサーを使用する前に必ず確認する必要があります。コンパスがサポートされていない場合は、ユーザーに対してメッセージが表示され、アプリケーション バーは非表示になります。コンパスがサポートされている場合は、DispatcherTimer が初期化され、イベント ハンドラーが割り当てられますが、この時点ではタイマーは起動しません。既存のページのコンストラクターを次のコードに置き換えます。

    // Constructor
    public MainPage()
    {
      InitializeComponent();
    
      if (!Compass.IsSupported)
      {
        // The device on which the application is running does not support
        // the compass sensor. Alert the user and hide the
        // application bar.
        statusTextBlock.Text = "device does not support compass";
        ApplicationBar.IsVisible = false;
      }
      else
      {
        // Initialize the timer and add Tick event handler, but don't start it yet.
        timer = new DispatcherTimer();
        timer.Interval = TimeSpan.FromMilliseconds(30);
        timer.Tick += new EventHandler(timer_Tick);
      }
    }
    
    
  10. アプリケーション バー ボタンのクリック イベントのハンドラーを追加します。前述の XAML コードの追加方法によっては、Visual Studio でこのハンドラーが既に追加されている可能性があります。その場合は、ハンドラー内のコードを削除します。ハンドラーが自動的に追加されていない場合は、MainPage クラス定義に、次の空の関数をコピーして貼り付けます。

    private void ApplicationBarIconButton_Click(object sender, EventArgs e)
    {
              
    }
    
    
  11. アプリケーション バー ボタンのクリック ハンドラーでは、最初に Compass オブジェクトが null ではなく、データを受信していることを確認します。この場合、ユーザーがボタンをクリックしてコンパスを停止しているときは、CompassDispatcherTimer の両方について Stop()()()() を呼び出します。空のボタンのクリック ハンドラー内に次のコードを貼り付けます。

      if (compass != null && compass.IsDataValid)
      {
        // Stop data acquisition from the compass.
        compass.Stop();
        timer.Stop();
        statusTextBlock.Text = "compass stopped.";
      }
    
    
    
  12. 次に、このコードは、ユーザーがコンパスを起動中であるケースを処理します。Compass オブジェクトが null の場合は、新しいインスタンスを作成します。目的の更新の間隔の時間を設定します。デバイスによってセンサーがサポートする更新間隔が異なります。この例では、ユーザーにセンサーの実際の間隔を表示するため、プロパティは設定後に照会されます。次に、コンパスのデータが更新されたときに発生する CurrentValueChanged イベントのイベント ハンドラーと、コンパスの校正が必要なときに発生する Calibrate イベントのイベント ハンドラーが追加されます。ボタン クリック ハンドラー内の、前のコード セクションの後に次のコードを貼りつけます。

      else
      {
        if (compass == null)
        {
          // Instantiate the compass.
          compass = new Compass();
    
          // Specify the desired time between updates. The sensor accepts
          // intervals in multiples of 20 ms.
          compass.TimeBetweenUpdates = TimeSpan.FromMilliseconds(20);
    
          // The sensor may not support the requested time between updates.
          // The TimeBetweenUpdates property reflects the actual rate.
          timeBetweenUpdatesTextBlock.Text = compass.TimeBetweenUpdates.TotalMilliseconds + " ms";
    
    
          compass.CurrentValueChanged += new EventHandler<SensorReadingEventArgs<CompassReading>>(compass_CurrentValueChanged);
          compass.Calibrate += new EventHandler<CalibrationEventArgs>(compass_Calibrate);
        }
    
    
    
  13. 次に、Start()()()() メソッドを使用してコンパスを起動します。Start の呼び出しは失敗する可能性があるため、この呼出は try ブロックに入れる必要があります。catch ブロックで、コンパスを起動できなかったことをユーザーに対して警告できます。このコードは、DispatcherTimer も起動します。前のコード セクションの後の [スタート] ボタンのクリック ハンドラーに次のコードを貼り付けます。

        try
        {
          statusTextBlock.Text = "starting compass.";
          compass.Start();
          timer.Start();
    
          // Start accelerometer for detecting compass axis
          accelerometer = new Accelerometer();
          accelerometer.CurrentValueChanged += new EventHandler<SensorReadingEventArgs<AccelerometerReading>>(accelerometer_CurrentValueChanged);
          accelerometer.Start();
        }
        catch (InvalidOperationException)
        {
          statusTextBlock.Text = "unable to start compass.";
        }
    
      }
    
    
  14. 次に、CurrentValueChanged イベント ハンドラーを実装します。このメソッドは、新しいコンパス データを持つシステムによって、TimeBetweenUpdates で指定した頻度で呼び出されます。ハンドラーは、コンパス データを含む CompassReading オブジェクトを受信します。このハンドラーは、UI へのアクセスのないバックグラウンド スレッドで呼び出されます。したがって、このメソッドから UI を変更する場合は、Dispatcher.BeginInvoke を使用して UI スレッドでコードを呼び出す必要があります。この例では、ディスパッチャー タイマーを使用して UI を更新するので、このメソッドはクラス メンバー変数の値を、CompassReading オブジェクトの値に設定するだけです。このアプリケーションでは、精度の符号ではなく、絶対値のみが考慮されるので、Abs 関数を使用して絶対値を取得しています。

    void compass_CurrentValueChanged(object sender, SensorReadingEventArgs<CompassReading> e)
    {
      // Note that this event handler is called from a background thread
      // and therefore does not have access to the UI thread. To update 
      // the UI from this handler, use Dispatcher.BeginInvoke() as shown.
      // Dispatcher.BeginInvoke(() => { statusTextBlock.Text = "in CurrentValueChanged"; });
    
    
      isDataValid = compass.IsDataValid;
    
      trueHeading = e.SensorReading.TrueHeading;
      magneticHeading = e.SensorReading.MagneticHeading;
      headingAccuracy = Math.Abs(e.SensorReading.HeadingAccuracy);
      rawMagnetometerReading = e.SensorReading.MagnetometerReading;
                
    }
    
    
  15. 現在のコンパスの設定で UI を更新するには、DispatcherTimerTick イベント ハンドラーを実装します。このメソッドは、コンパスが現在校正中であるかどうかに応じて、動作が異なります。校正中ではない場合は、データが受信中であることを示すために、ステータス TextBlock が更新されます。次に、TextBlock オブジェクトが更新され、磁方位 (磁北を基準とした方位)、真方位 (地軸の北を基準とした方位)、およびコンパスの読み取り値の誤差を示す方位精度が表示されます。次に、コンパスの読み取り値をグラフィカルに表すために Line オブジェクトが更新されます。Microsoft.Xna.Framework ライブラリの MathHelper クラスを使用して、読み取り値は三角関数で使用できるように度からラジアンに変換されます。次に、生の磁力計の読み取り値が、数値とグラフィックの両方で表示されます。次のコードを MainPage.xaml.cs に貼り付けます。このメソッドの残りの部分は、次の手順で示します。

    void timer_Tick(object sender, EventArgs e)
    {
      if (!calibrating)
      {
        if (isDataValid)
        {
          statusTextBlock.Text = "receiving data from compass.";
        }
    
        // Update the textblocks with numeric heading values
        magneticTextBlock.Text = magneticHeading.ToString("0.0");
        trueTextBlock.Text = trueHeading.ToString("0.0");
        accuracyTextBlock.Text = headingAccuracy.ToString("0.0");
    
        // Update the line objects to graphically display the headings
        double centerX = headingGrid.ActualWidth / 2.0;
        double centerY = headingGrid.ActualHeight / 2.0;
        magneticLine.X2 = centerX - centerY * Math.Sin(MathHelper.ToRadians((float)magneticHeading));
        magneticLine.Y2 = centerY - centerY * Math.Cos(MathHelper.ToRadians((float)magneticHeading));
        trueLine.X2 = centerX - centerY * Math.Sin(MathHelper.ToRadians((float)trueHeading));
        trueLine.Y2 = centerY - centerY * Math.Cos(MathHelper.ToRadians((float)trueHeading));
    
        // Update the textblocks with numeric raw magnetometer readings
        xTextBlock.Text = rawMagnetometerReading.X.ToString("0.00");
        yTextBlock.Text = rawMagnetometerReading.Y.ToString("0.00");
        zTextBlock.Text = rawMagnetometerReading.Z.ToString("0.00");
    
        // Update the line objects to graphically display raw data
        xLine.X2 = xLine.X1 + rawMagnetometerReading.X * 4;
        yLine.X2 = yLine.X1 + rawMagnetometerReading.Y * 4;
        zLine.X2 = zLine.X1 + rawMagnetometerReading.Z * 4;
      }
    
    
  16. Tick イベント ハンドラーの 2 番目の部分は、コンパス キャリブレーション用の UI を更新します。この UI は、既定ではユーザーに対して表示されないことに注意してください。この UI を表示するコードは、次の手順で示します。このコードは単純に HeadingAccuracy の値を評価し、この値が 10 度以下である場合、コンパスは校正されていると見なされ、テキストの色は緑に設定され、校正が完了したことがユーザーに通知されます。それ以外の場合は、テキストは赤で表示され、方位精度の数値が表示されます。前の手順のコードの後に次のコードを貼り付けて、Tick イベント ハンドラーを完成させます。

      else
      {
        if (headingAccuracy <= 10)
        {
          calibrationTextBlock.Foreground = new SolidColorBrush(Colors.Green);
          calibrationTextBlock.Text = "Complete!";
        }
        else
        {
          calibrationTextBlock.Foreground = new SolidColorBrush(Colors.Red);
          calibrationTextBlock.Text = headingAccuracy.ToString("0.0");
        }
      }
    }
    
    
  17. 次に、Calibrate イベント ハンドラーを実装します。このイベントは、コンパスの方位精度が +/- 20 度を上回ったことをシステムが検出したときに発生します。このイベント ハンドラーでは、単にキャリブレーション UI を表示し、calibrating メンバー変数を true に設定します。このイベント ハンドラーは UI スレッドで呼び出されないので、UI を更新するコードは Dispatcher.Invoke を使用して呼び出されます。

    void compass_Calibrate(object sender, CalibrationEventArgs e)
    {
      Dispatcher.BeginInvoke(() => { calibrationStackPanel.Visibility = Visibility.Visible; });
      calibrating = true;
    }
    
    
    
  18. 最後に、キャリブレーション UI のボタンの Click イベント ハンドラーを実装します。ユーザーは、キャリブレーションが完了したときに、これをタップします。このメソッドは、単にキャリブレーション UI を非表示にし、calibrating メンバー変数を false に設定します。

    private void calibrationButton_Click(object sender, RoutedEventArgs e)
    {
      calibrationStackPanel.Visibility = Visibility.Collapsed;
      calibrating = false;
    }
    
    
    

コンパス API は、電話の向きに応じて、異なる軸を使用して方位を計算します。次のコードは、サンプル アプリケーションを変更して、実行時にコンパスで使用されている向きを特定します。

コンパスの方向モードを特定するには

  1. 最初に、クラスの先頭に他のメンバー変数と共に、Accelerometer 型のメンバー変数を追加します。

    Accelerometer accelerometer;
    
    
    
  2. 次に、アプリケーション バー ボタンのクリック ハンドラーで、Compass および DispatcherTimerStop メソッドの呼び出しの直後に、加速度センサーの Stop()()()() メソッドを呼び出します。

    
    …
    compass.Stop();
    timer.Stop();
    // Add the following line
    accelerometer.Stop();
    
    
    
  3. 次に、同じアプリケーション バー ボタンのクリック ハンドラーで、Accelerometer オブジェクトを初期化し、CurrentValueChanged のイベント ハンドラーをアタッチして、Start()()()() を呼び出します。このコードを、Compass および DispatcherTimerStart の呼び出しの直後に追加します。

    …
    statusTextBlock.Text = "starting compass.";
    compass.Start();
    timer.Start();
    
    // add the following lines
    accelerometer = new Accelerometer();
    accelerometer.CurrentValueChanged += new EventHandler<SensorReadingEventArgs<AccelerometerReading>>(accelerometer_CurrentValueChanged);
    accelerometer.Start();
    
    
    
  4. 最後に、Accelerometer オブジェクトの CurrentValueChanged イベントのイベント ハンドラーを実装します。このメソッドは、加速度の読み取りデータを表す Vector3 オブジェクトを取得し、三角関数を使用してデバイスの向きを特定します。最後に、現在のコンパス モードで UI が更新されます。このイベント ハンドラーは UI スレッドで呼び出されないので、Dispatcher.BeginInvoke を使用して UI が更新されます。

    void accelerometer_CurrentValueChanged(object sender, SensorReadingEventArgs<AccelerometerReading> e)
    {
      Vector3 v = e.SensorReading.Acceleration;
    
      bool isCompassUsingNegativeZAxis = false;
    
      if (Math.Abs(v.Z) < Math.Cos(Math.PI / 4) &&
                    (v.Y < Math.Sin(7 * Math.PI / 4)))
      {
        isCompassUsingNegativeZAxis = true;
      }
    
      Dispatcher.BeginInvoke(() => { orientationTextBlock.Text = (isCompassUsingNegativeZAxis) ? "portrait mode" : "flat mode"; });
    }
    
    
    

表示:
© 2014 Microsoft