Windows ストア

Geofencing 機能を備えた位置認識アプリの作成

Tony Champion

コード サンプルのダウンロード

モバイル デバイスの普及がさらに進んでいることで、位置認識アプリの作成が活発になっています。ユーザーの位置を認識して反応するアプリが持つ可能性は、無限と言ってよいでしょう。Windows 8 には標準で位置情報機能が付属しているため、開発者はデバイスの現在の位置を簡単に特定できます。さらに、Windows Simulator ではこの機能のテストもサポートしています。Windows 8.1 では、位置情報 API が拡張され、Geofencing の概念が取り入れられました。

ジオフェンス (Geofence) とは、GPS の任意の位置を中心に定義される領域で、この領域を Windows に登録して、デバイスがその領域に出入りするときに通知の受け取りを可能にします。たとえば、ある修理技術者のある日の出張修理予定が 10 件ほどあったとします。訪問先に後 5 分で到着する位置に来たら、予定管理アプリから、訪問先のお客様に自動的にテキスト メッセージが送信されるとしたらどうでしょう。または、アミューズメント パークにおいてキャラクターの登場を知らせたい場合に、そのキャラクターから特定の距離内にいる人にのみ通知して、見物に集まるお客様の人数を抑えることもできるでしょう。可能性は無限です。

今回は、Windows 8.1 のジオフェンスの使用方法について見て行きます。ただし、Geofencing API は Windows Phone 8.1 にも提供されているため、どちらのプラットフォームにも同じ機能を実装できます。ここでは、位置情報をベースとするアプリにジオフェンスを追加する方法と、アプリがフォアグラウンドで実行されているときのイベントの処理方法、およびアプリがバックグラウンドで実行されているときの通知の処理方法について説明します。

位置情報機能のサポートの追加

ジオフェンスは Windows SDK の位置情報 API に含まれているため、この API のサポートをアプリに追加しないと、ジオフェンスを利用できません。さいわい、Windows ストア アプリでは、位置情報を使用するための設定にほとんど手間がかかりません。実際、パッケージの appxmanifest に位置情報機能を追加することが、唯一の要件です。位置情報機能を追加するには、デザイナーでソリューションの appxmanifest を開き、[機能] タブで [場所] チェック ボックスをオンにします (図 1 参照)。

位置情報機能の有効化
図 1 位置情報機能の有効化

この機能がプロジェクトに追加されると、ユーザーが許可した場合、アプリから位置情報 API を介して Windows の位置情報サービスにアクセスできるようになります。この機能によってアプリの設定チャームに [アクセス許可] も追加され、デバイスの位置情報へのアクセスの有効と無効を切り替えられるようになります (図 2 参照)。

アクセス許可の設定
図 2 アクセス許可の設定

Windows では、個々のアプリ レベルで位置情報サービスへのアクセスを管理するだけでなく、デバイス全体で位置情報サービスを無効にすることもできます。Windows では既定で位置情報サービスが有効になっていますが、ユーザーまたはシステム管理者によってこの設定は自由に変更できます。変更する場合は、コントロール パネルで、[ハードウェアとサウンド] をクリックし、[位置情報の設定] をクリックします。

アプリからのデバイスの位置情報へのアクセスに影響するシナリオは複数あるため、このアクセスが変更された場合に、アプリが変更を認識できる必要があります。Windows SDK を使うと、アプリは Windows.Devices.Enumeration 名前空間の DeviceAccessInformation クラスで公開されるイベントを利用して、位置情報サービスへのアクセスの状態を監視できます。DeviceAccessInformation クラスのインスタンスが、CreateFromDeviceClass 静的メソッドによって作成されます。このメソッドには、情報を取得したいハードウェア デバイスを指定する DeviceClass 列挙型を設定します。位置情報サービスの場合、この値は DeviceClass.Location です。DeviceAccessInformation のインスタンスを作成したら、CurrentStatus プロパティから現在のアクセス レベルを特定し、AccessChanged イベントによりアクセスの変更通知をリッスンします。コードは次のようになります。

DeviceAccessInformation deviceAccess =
  DeviceAccessInformation.CreateFromDeviceClass(DeviceClass.Location);
DeviceAccessStatus currentStatus = deviceAccess.CurrentStatus;
// Setup geolocation based on status
// Listen for access changes
deviceAccess.AccessChanged += deviceAccess_AccessChanged;

CurrentStatus プロパティと AccessChanged イベントは、どちらも現在のアクセス レベルを示す DeviceAccessStatus 列挙型を受け取ります。図 3 に、設定できる値とその意味を示します。

図 3 AccessChanged イベント

private void deviceAccess_AccessChanged(
  DeviceAccessInformation sender, DeviceAccessChangedEventArgs args)
{
  switch (args.Status)
  {
    case DeviceAccessStatus.Allowed:
      // Access to the device is allowed
      break;
    case DeviceAccessStatus.DeniedByUser:
      // Denied by user when prompted or thru Permissions settings
      break;
    case DeviceAccessStatus.DeniedBySystem:
      // Denied at the system level from the Control Panel
      break;
    case DeviceAccessStatus.Unspecified:
      // Unknown reason for access to the device
      break;
  }
}

位置の特定

ジオフェンスを利用する位置認識アプリを作成する場合は、ユーザーの位置を特定できなければなりません。これがなくてもジオフェンスは実装できますが、後ほど説明するように、ジオフェンス イベントを検証する際に便利です。

Windows SDK は、デバイスの GPS による現在位置を特定する Geolocator クラスをいくつかの方法で公開します。Geolocator を含めたすべての位置情報クラスは、Windows.Devices.Geolocation 名前空間にあります。初めて使用するときは、Geolocator クラスがアプリの UI スレッドに含まれている必要があります。これは、Windows がユーザーに位置情報サービスの使用許可を求めるためです。ユーザーに許可を求めるメッセージは、デバイスごとに 1 回しか表示されません。また、前述のアクセス許可設定を使って、いつでもこの設定を変更できます。したがって、ユーザーが初回使用時に位置情報の利用を許可しなかった場合や、設定チャームからこの機能のアクセス許可を無効にしている場合、プログラムを使ってこのアクセスを有効に戻すことはできません。UI の通知を使ってユーザー自身で変更するように求める必要があります。

デバイスの現在位置を特定するために、Geolocator には Geoposition オブジェクトを返す GetGeopositionAsync メソッドがあります。このオブジェクトの Coordinate プロパティに、位置情報サービスから返された情報を保持する Geocoordinate のインスタンスが格納されます。Geocoordinate クラスで最も重要な 3 種類の情報は、GPS による現在位置の経度、緯度、高度と言ってよいでしょう。Windows 8.1 では、これらは Geopoint のインスタンスである Point プロパティに保持されます。Geocoordinate クラスの Latitude、Longitude、および Altitude プロパティは下位互換性のために維持されていますが、今後は Point プロパティを使用してください。

他にも Geocoordinate クラスには、ジオフェンスを処理する際に便利な、価値ある情報がいくつかあります。最も重要なのは Accuracy プロパティで、これは GPS の精度をメートル単位で定義します。Geolocator クラスは Windows の位置情報サービスを使って、現在位置を特定します。位置情報サービスが現在位置を特定する方法はいくつかあります。最も精度の高い方法は、デバイスに GPS 無線システムが組み込まれている場合、この無線システムを有効にして、信号を受信可能な状態にしておくことです。GPS 無線システムが使用できない場合、位置情報サービスはデバイスの Wi-Fi 接続を使用して、現在位置の特定を試みます。また、デバイスが Wi-Fi 接続を使用していない場合は、精度は落ちますが、IP 解決を使用した方法を試みます。当然ながら、現在位置の精度はアプリのジオフェンスの有効性を大きく左右するため、考慮する必要があります。たとえば、IP 解決を使う場合の精度は 16 km 以内です。これは、アミューズメント パークでジオフェンスを使用する場合、あまり役に立ちません。このようにデバイスの位置の精度にかなりの差がある場合は、位置の精度とジオフェンスの規模を比較して、ジオフェンスの有効性を判断することが重要です。

Timestamp プロパティは、データがどれほど古いかを特定する場合に非常に便利です。ジオフェンスを扱うときは、タイミングが特に重要です。たとえば、アミューズメント パークにジオフェンスを導入する場合、ユーザーが駐車場に入ってきたのが開園時間中の場合と深夜の場合では、それぞれ別のワークフローをアプリで使用することが考えられます。図 4 に、ユーザーのデバイスの現在位置を取得する例を示します。

図 4 位置情報の取得

Geolocator geo = new Geolocator();
try
{
  Geoposition pos = await geo.GetGeopositionAsync();
  double longitude = pos.Coordinate.Point.Position.Longitude;
  double latitude = pos.Coordinate.Point.Position.Latitude;
  double altitude = pos.Coordinate.Point.Position.Altitude;
  double accuracy = pos.Coordinate.Accuracy;
  DateTimeOffset timestamp = pos.Coordinate.Timestamp;
}
catch (Exception ex)
{
  // Handle errors like unauthorized access to location services
}

ジオフェンスの作成

現在、Geofence クラスには、ジオフェンスの位置と動作を定義できるコンストラクターが 4 つあります。Geofencing の構造体はすべて、Windows.Devices.Geolocation.Geofencing 名前空間に含まれています。公開したプロパティはすべて読み取り専用になるため、ジオフェンスを作成する前に Geofence インスタンスの動作を決めておくことが重要です。したがって、作成中に何かしらの設定を省いていて、後で変更が必要になった場合、新しいジオフェンスを作成して置き換える必要があります。

Geofence クラスを作成するために最低限必要な情報は、一意文字列 ID とジオフェンスの形状の定義です。この形状は、IGeoshape インターフェイスの実装によって表現します。Windows 8.1 でサポートされている形状は Geocircle だけで、中心の位置と半径をメートル単位で指定して定義します。Geofence と同様に、これらの値は作成時に定義する必要があり、後で変更できません。半径はメートル単位で、地球の円周の 10% ~ 25% の間で指定できます。これだけあれば、アプリで必要になるあらゆるサイズに対応できるでしょう。以下の例では、現在のデバイス位置を中心に半径 50 m のジオフェンスを作成しています。

Geolocator geo = new Geolocator();
try {
  Geoposition pos = await geo.GetGeopositionAsync();
  Geocircle shape = new Geocircle(pos.Coordinate.Point.Position, 50.0);
  Geofence geofence = new Geofence("myUniqueId", shape);
}
catch (Exception) { }

Windows は、1 つのジオフェンスで複数の異なるデバイスの反応を監視できます。既定では、ジオフェンスの定義領域へのデバイスの出入りを監視します。さらに、監視対象のジオフェンスが削除された場合に生成される通知を指定することもできます。要件として領域への出入りのどちらかの状態を監視しなければならないため、ジオフェンスの削除のみを監視することはできません。ただし、正直なところ、削除のみを監視することはアプリにとってそもそも有用性は高くないでしょう。状態の監視は、MonitoredGeofence­States 列挙型を組み合わせて、MonitoredStates プロパティに格納することで定義します。

ジオフェンスは、単一用途のエンティティとしても、複数用途のエンティティとしても使用できます。監視対象のすべての状態が発生したら、ジオフェンスの使用が認められます。したがって、出入りの両方の状態を監視するように指定した場合、デバイスは指定された領域に入り、その領域から出てはじめて、ジオフェンスの使用が認められます。監視は無効にしない限り、ジオフェンスが削除されるまで継続されます。SingleUse プロパティは Geofence を単一用途にするかどうかを指定します。以下のコードは、前のサンプルを拡張したもので、2 つ目の Geofence コンストラクターを示してます。

MonitoredGeofenceStates monitor = MonitoredGeofenceStates.Entered |
                                  MonitoredGeofenceStates.Exited |
                                  MonitoredGeofenceStates.Removed;
bool singleUse = true;
Geofence geofence = new Geofence("myUniqueId", shape, monitor, singleUse);

既定では、境界を超えて監視対象領域に入ってから 10 秒間その領域内にとどまらないと、アプリに通知されません。これにより、デバイスが境界上にあって、行きつ戻りつしている場合に、複数のイベントが発生することを防いでいます。この値は DwellTime で、0 よりも大きい任意の TimeSpan 値に設定できます。領域への出入りにも、この DwellTime が使われます。したがって、それぞれに別の値が必要な場合は、出入りにそれぞれ 1つずつ 2 つのジオフェンスを作成する必要があります。以下の例では、DwellTime を 20 秒に設定した 3 つ目のコンストラクターを使用しています。

TimeSpan dwellTime = new TimeSpan(0, 0, 20);
Geofence geofence = new Geofence("myUniqueId", shape, monitor,
  singleUse, dwellTime);

最後のコンストラクターは、ジオフェンスの StartTime と Duration を設定できるようにします。ジオフェンスは、指定された StartTime を過ぎると、アクティブになります。既定では StartTime は 0 に設定されています。または DateTime クラスで、開始タイミングが処理されます。ジオフェンスを過去の StartTime で作成し、デバイスが既に定義領域内に存在している場合、DwellTime が経過したタイミングで Entered 状態が報告されます。StartTime と併せて Duration を使うことで、StartTime を起点としてジオフェンスの有効期間を定義できます。Duration を 0 (既定値) に設定すると、監視対象として登録されている限り、ジオフェンスの有効期間は継続します。Duration の値が設定されていて、監視状態が Removed になると、アプリにジオフェンスの有効期間が終了したことが通知されます。以下は、StartTime を 2015 年 1 月 1 日に、有効期間を 365 日に設定している 4 つ目のコンストラクターです。

DateTime startTime = new DateTime(2015, 1, 1);
TimeSpan duration = new TimeSpan(365, 0, 0, 0, 0);
Geofence geofence = new Geofence(
  "myUniqueId", shape, monitor, singleUse, 
  dwellTime, startTime, duration);

フォアグラウンドでのジオフェンスの使用

ジオフェンスを作成したら、次は監視対象に登録して、アプリがジオフェンスについての通知を受け取れるようにします。これは GeofenceMonitor インスタンスを利用して処理します。アプリごとに、GeofenceMonitor.Current 静的プロパティからアクセスできる GeofenceMonitor が 1 つ用意されます。GeofenceMonitor は、登録済みのすべてのジオフェンスの一覧を Geofences プロパティに保持します。この一覧は IList<Geofence> です。この一覧へのジオフェンスの追加または削除は、Add や Remove など、おなじみの IList メソッドを使って簡単に実行できます。アプリがジオフェンスを登録すると、ディスクに保存されます。つまり、ジオフェンスは 1 度登録すれば、何度でもアプリで使用できます。重複する ID を持つジオフェンスを登録しようとすると、エラーが生成されるので、登録前に ID が存在していないことを確認します。

IEnumerable<Geofence> existing =
  GeofenceMonitor.Current.Geofences.Where(g => g.Id == geofence.Id);
if (existing.Count() == 0)
{
  GeofenceMonitor.Current.Geofences.Add(geofence);
}
else
{
  // Handle duplicate entry
}

ジオフェンスを GeofenceMonitor に追加したら、選択している監視状態を基に、ジオフェンスについての通知を受け取るようになります。このような通知は、GeofenceMonitor の GeofenceStateChanged イベントを使って処理します。

GeofenceMonitor.Current.GeofenceStateChanged += GeofenceStateChanged;

GeofenceStateChanged イベントが発生したときに、影響を受けたジオフェンスは args プロパティに送られません。これは、ほとんどの変更イベント処理の共通動作です。変更内容についての通知を取得するには、現在の GeofenceMonitor の ReadReports メソッドを呼び出します。これにより、アプリに対して発生した最近の監視通知のコレクションが、タイムスタンプの降順に格納された状態で返されます。1 つの通知は、1 つの GeofenceStateChangeReport クラスによって表されます。

GeofenceStateChangeReport には、状態が変化したジオフェンスを参照する Geofence プロパティと、状態が変化する原因となったデバイスの位置を示す Geopostion プロパティがあります。また、NewState プロパティもあります。これは、どの監視状態がトリガーされたかを示す GeofenceState 列挙型になります。さらに、RemovalReason プロパティもあり、これには GeofenceRemovalReason 列挙型が使用されます。この列挙型の値は Used または Expired になります。どのイベントの既定値も Used です。これはジオフェンスが削除されたことを意味するのではなく、単に NewState の値が Removed になった場合に、削除の理由を提供します。

ReadReports から返されるレポート数は、Windows によって制御されます。何件のレポートがどの程度の期間保存されるかはわからないため、残しておく必要がある情報については、アプリで別途コピーを保持する必要があります。図 5 は GeofenceStateChanged イベントの処理のサンプルです。

図 5 GeofenceStateChanged イベント ハンドラー

void Current_GeofenceStateChanged(GeofenceMonitor sender, object args)
{
  IReadOnlyList<GeofenceStateChangeReport> reports =
    GeofenceMonitor.Current.ReadReports();
  foreach (GeofenceStateChangeReport report in reports)
  {
    switch (report.NewState)
    {
      case GeofenceState.Entered:
        // Handle entered state
        break;
      case GeofenceState.Exited:
        // Handle exited state
        break;
      case GeofenceState.Removed:
        if (report.RemovalReason == GeofenceRemovalReason.Used)
        {
          // Removed because it was single use
        }
        else
        {
          // Removed because it was expired
        }
        break;
    }
  }
}

バックグラウンドでのジオフェンスの使用

開発者が考慮しなければならないその他の問題の 1 つが、Windows ストア アプリのライフサイクルです。アプリが画面に表示されていない場合、Windows はアプリを中断してメモリに保留し、パフォーマンスの低下とバッテリーの消耗を防ぎます。つまり、アプリは実行されていません。ほとんどの場合、これは心配の種にはなりません。ユーザーがアプリを直接操作していない場合、アプリには処理すべきタスクがないからです。

ただし、たとえアプリが実行されていなくても、特定のタスクを実行し続ける機能がアプリに必要になる場合があります。Windows では、これをバックグラウンド タスクという概念によって解決しています。バックグラウンド タスクは、事前に定義されているシステム イベントに反応して実行される小さなコードです。バックグラウンド タスクの対象として登録できるイベントは 10 種類以上あり、ジオフェンスのリッスンもその 1 つです。

バックグラウンド タスクの追加

バックグラウンド タスクは、Windows.ApplicationModel.Background 名前空間の IBackgroundTask インターフェイスを実装するシール クラスを作成することで実装します。このインターフェイスは、実装する必要がある Run メソッドを 1 つだけ定義します。IBackgroundTask を実装するクラスは、Windows ランタイム コンポーネント内に存在している必要があり、アプリのメイン プロジェクトに含まれていない場合は実行されません。一般には、同じアプリのすべてのバックグラウンド タスクは、1 つの Windows ランタイム コンポーネントにまとめます。

バックグラウンド タスクを作成するには、Windows ランタイム コンポーネント プロジェクトをソリューションに追加します。その新しいプロジェクトへの参照をアプリのメイン プロジェクトに追加して、そのタスクを登録したときに、アプリがクラスの構造を認識できるようにします。図 6 は、ジオフェンスの状態変化に反応するバックグラウンド タスクです。

図 6 バックグラウンド タスク

public sealed class MyBackgroundTask : IBackgroundTask
{
  public void Run(IBackgroundTaskInstance taskInstance)
  {
    BackgroundTaskDeferral deferral = taskInstance.GetDeferral();
    var reports = GeofenceMonitor.Current.ReadReports();
    foreach (var report in reports)
    {
      // Handle each report
    }
    deferral.Complete();
  }
}

バックグラウンド タスクの登録

バックグラウンド タスクを作成したら、そのバックグラウンド タスクを Windows に登録します。位置情報サービスと同様、アプリがバックグラウンド タスクを使用することをユーザーが承認する必要があります。ユーザーに許可を求めるメッセージは、BackgroundExecutionManager クラスの RequestAccessAsync 静的メソッドを使用して表示します。ユーザーが既に許可メッセージに応答している場合、このメソッドはアプリで実行中のバックグラウンド タスクの現在の状態を返します。

実際の登録は、BackgroundTaskBuilder のインスタンスにより処理されます。バックグラウンド タスクを有効にするには、3 種類の情報が必要です。1 つ目はアプリの一意名です。2 つ目は TaskEntryPoint プロパティで、名前空間も含めて、バックグラウンド タスク クラスの完全な名前を 1 文字列として設定します。

3 つ目の情報では、登録対象のイベントの種類を定義します。それにはトリガーを作成し、BackgroundTaskBuilder の SetTrigger メソッドを使用します。どのバックグラウンド タスクでも、設定できるトリガーは 1 つだけです。複数のトリガーに同じバックグラウンド タスクを使用する必要がある場合は、複数のバックグラウンド タスクを作成して、登録する必要があります。ジオフェンスの変化を追跡するには、LocationTrigger インスタンスを作成し、LocationTriggerType.Geofence 列挙値をコンストラクターに渡します。現在、Geofencing は、Windows に用意されている位置情報ベースのトリガーだけです。この結果として、登録したジオフェンスの状態が変化するたびに、作成した IBackgroundTask の Run メソッドが呼び出されるようになります。図 7 は、ジオフェンスのバックグラウンド タスクを登録する方法の例です。

図 7 バックグラウンド タスクの登録

private async void RegisterBackgroundTask(object sender, RoutedEventArgs e)
{
  BackgroundAccessStatus accessStatus =
    await BackgroundExecutionManager.RequestAccessAsync();
  if(accessStatus ==
    BackgroundAccessStatus.AllowedMayUseActiveRealTimeConnectivity ||
   accessStatus ==
     BackgroundAccessStatus.AllowedWithAlwaysOnRealTimeConnectivity)
  {
    BackgroundTaskBuilder taskBuilder = new BackgroundTaskBuilder();
    taskBuilder.Name = "MyGeoBackground";
    taskBuilder.TaskEntryPoint = 
      "WindowsRuntimeComponent1.MyBackgroundTask";
    LocationTrigger trigger = new LocationTrigger(LocationTriggerType.Geofence);
    taskBuilder.SetTrigger(trigger);
    taskBuilder.Register();
  }
}

アプリでバックグラウンド タスクを実行可能にする最後の作業として、パッケージの appxmanifest にバックグラウンド タスクの宣言を追加します。デザイナーで appxmanifest を開き、[宣言] タブを選択します。[使用可能な宣言] ドロップダウン リストから [バックグラウンド タスク] を選択し、[追加] をクリックします。詳細ウィンドウで、[プロパティ] の [場所] チェック ボックスをオンにし、[エントリ ポイント] ボックスには BackgroundTaskBuilder の TaskEntryPoint のクラス名を入力します。これが完了すると、[アプリケーション] タブに赤い "X" が表示されます。バックグラウンド タスクの中には、ロック画面にアプリを追加しないと実行できないものがあります。位置情報バックグラウンド タスクもそのようなタスクの 1 つです。まず、[アプリケーション] ウィンドウで [ロック画面通知] を [バッジ] または [バッジとタイル テキスト] に設定します。次に、ユーザーによってアプリがロック画面に追加されるようにします。前述の BackgroundExecutionMananger.RequestAccessAsync メソッドを使うと、ユーザーが承認した場合、ロック画面にアプリを追加できます。ただし、Windows ではロック画面へのアプリの追加を求めるメッセージは 1 度しか表示されないため、後日ユーザーがロック画面からアプリを削除した場合に、ユーザーに通知するコードを作成する必要があります。

これで、バックグラウンドでジオフェンスの状態が変化した場合に、アプリが反応するようになります。アプリが実行中の場合でも、このバックグラウンドの状態変化にアプリが反応することに注意してください。したがって、フォアグランドでジオフェンスを監視して、UI の更新を確認する場合、アプリのワークフローに配慮が必要です。

Windows 8.1 のバックグラウンド タスクの詳細については、「バックグラウンド タスクによるアプリのサポート (XAML)」(https://msdn.microsoft.com/ja-jp/library/windows/apps/xaml/hh977056.aspx) を参照してください。

まとめ

ジオフェンスにより、事前に定義された GPS 座標からの距離の変化にアプリが反応できるようにすることで、位置認識アプリで新しい動的な処理を実行できるようになります。ジオフェンスを使うことで、世界中のどこにでも、アプリが反応できるホット スポットを簡単に用意できます。アプリは、実行中にジオフェンスに反応できるだけでなく、バックグラウンド タスクを使用することで、アプリが中断されている場合や、実行もされていない場合でも、反応できます。GPS 対応の小型デバイスの普及がますます進んでいる現在、位置認識機能をアプリに追加することで優れた UX を実現できます。個人的には、次にどのようなものが開発されるか、非常に楽しみにしています。


Tony Championは、Champion DS の代表取締役であり、Microsoft MVP を取得しており、講演、ブログ、および著書で積極的にコミュニティに貢献しています。彼のブログは、tonychampion.net (英語) から、電子メールは tony@tonychampion.net (英語のみ) から利用できます。

この記事のレビューに協力してくれたマイクロソフト技術スタッフの Robert Green に心より感謝いたします。