Wi-Fiベース屋内測位技術をAndroidアプリに組み込むには:Androidで使えるO2O技術まとめ解説(2)(4/4 ページ)
今注目の「O2O」について、現状や概要を紹介し、O2Oを利用したAndroidアプリを作る際に必要な技術要素を1つ1つ解説していきます。今回は、O2Oの技術要素の1つとして「屋内測位」の概要や、Wi-Fiを利用したものを5つ紹介し、Walkbaseというサービスを例に使い方も解説します
Walkbaseを使うAndroidアプリの実装
それではAndroidアプリにライブラリを使ってWalkbaseによる位置同定を組み込んでいきます。記事執筆現在のライブラリの最新バージョンは0.4.2です。
開発アカウントのトップページにあるライブラリのダウンロードリンクはバージョンが古いため、サイトメニューの[Downloads]リンクからダウンロードページに移動し、そこから最新の0.4.2を取得してください。
ライブラリの配置とパーミッションの追加
ダウンロードしたライブラリのjarファイルは、組み込むAndroidアプリのプロジェクトディレクトリにライブラリを配置(今回は「libs」というディレクトリを新たに作成した)してパスを通しておきます。
また、manifestファイルに以下のパーミッションを追加します。
<uses-permission android:name="android.permission.INTERNET"></uses-permission> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"></uses-permission> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission> <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS"></uses-permission> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"></uses-permission> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"></uses-permission> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"></uses-permission> <uses-permission android:name="android.permission.WAKE_LOCK"></uses-permission>
細かい説明は割愛しますが、当然ながら位置同定に必要なWi-Fi、ネットワーク、ロケーションに関するアクセスのパーミッションを一通り追加しています。
サービスとして位置問い合わせを実装
ぺこりんのクライアントでは「ぺこり」する友達が自分のいる部屋・フロアにいるかどうかを定期的に問い合わせるようにしています。
まず、自分の位置をWalkbaseを使って特定し、そこで特定された部屋・フロアのIDをぺこりんサーバに送ることで、ぺこりんサーバ側でIDをマッチングして同じ部屋・フロアにいる友だちを通知します。一連の定期的な問い合わせはサービスとして実装します。
位置の問い合わせをサービス化する場合のサンプル実装がWalkbaseから公開されているのですが、現在公開されているサンプル実装で利用されているAPIのバージョンが最新版ではないため、そのままでは利用できません。最新の0.4.x系に合わせて変更しつつ組み込んでみました。
以下、Walkbaseのサービス利用部分の実装を一部抜粋して説明します。
public class LocationDetectService extends Service implements RecommendationRequestListener{ /* WalkbaseのAPIキー */ private static final String WALKBASE_API_KEY = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; /* 位置問い合わせの間隔 */ private static final int FIVE_MINUTES = 300000; private static final String TAG = "PecorinerLocationDetect"; private String FACEBOOK_ID = ""; private String PECORIN_TOKEN = ""; private ThreadGroup svrThreads = new ThreadGroup("ServiceWorker"); /* Walkbaseの位置情報サービスを利用するためのクラス */ private Positioning mPositioning; /* 位置特定時の通知ハンドラ */ private Handler mDetectHandler; private static final int DETECT = 1; private static final int DETECT_ERR = 2; private boolean continueScanning; public void onCreate() { super.onCreate(); this.continueScanning = true; mDetectHandler = new DetectHandler(); mPositioning = new Positioning(this, WALKBASE_API_KEY); SharedPreferences preferences = getSharedPreferences("preference", Activity.MODE_PRIVATE); FACEBOOK_ID = preferences.getString("facebook_id", ""); PECORIN_TOKEN = preferences.getString("pecorin_token", ""); }
サービスを実装する際に、RecommendationRequestListenerインターフェイスをインプリメントしておきます。これは自分の位置の特定時に位置候補を問い合わせた結果を処理するためです。
以前のバージョンのAPIでは、この辺りをすべてインテントを利用して処理していましたが、現在はObserverパターンによる実装にAPIの内部実装が変更されたようです。
APIの利用には、AP情報のレコーディングにも利用したAPIキーが必要になります。Walkbaseのサービスとやりとりを行うクラスであるPositioningクラスへonCreate時にAPIキーを渡してインスタンスを生成し初期化しておきます。
位置の特定はスレッドを利用して定期的に呼び出すように実装しますので、特定時の通知にハンドラを利用します。同じくonCreate時にハンドラを生成、初期化しておきます。
public int onStartCommand(Intent intent, int flags, int startId) { super.onStartCommand(intent, flags, startId); new Thread(svrThreads, new ServiceWorker(), "PecorinerLocationDetect").start(); return START_STICKY; } class ServiceWorker implements Runnable { public void run() { continueScanning = true; try { while(continueScanning) { mPositioning.fetchRecommendations(LocationDetectService.this, new String[]{"244a80bc944707c20d3df1ed1fbd416211baae32"}); Thread.sleep(FIVE_MINUTES); } } catch(Exception e) { Log.v("LocationDetectService",e.getMessage()); } } }
自分の位置を特定するためのWalkbaseのサービス呼び出しはWorkerクラスで行います。位置を特定するに、はPositioningクラスのfetchRecommendationsを呼び出します。
fetchRecommendationsは問い合わせをしてきた端末の位置に近いと推定される位置から順に複数の候補をリスト化して返してきます。リストの最初に格納されている位置が最も確からしい位置になります。fetchRecommendationsの第1引数には、問い合わせ結果を処理するためのRecommendationRequestListenerインターフェイスをインプリメントしたクラスを、第2引数には問い合わせた結果の位置候補対象となる位置が登録されたロケーションリストのIDを文字列の配列で指定します。
RecommendationRequestListenerについては、先ほど説明した通り、今回はこのLocationDetectServiceクラス自体にインプリメントし指定しています。ロケーションリストについては、レコーディングの際に作成した、ぺこりん用のリストを1つ指定しています。
別々に作成した複数のロケーションリストから候補を特定したい場合には、こちらの配列にロケーションリストのIDを追加します。呼び出し間隔は、いったん5分で設定しています。
@Override public void didGetRecommendation(ArrayList<WalkbaseLocation> recommendations) { if(recommendations != null && recommendations.size() != 0) { WalkbaseLocation mostProbableLocation = (WalkbaseLocation)recommendations.get(0); sendResultMessage(DETECT, mostProbableLocation.locationId); } else { sendResultMessage(NO_DETECT, ""); } } @Override public void failedToGetRecommendation(int errCode, String errMessage) { sendResultMessage(DETECT_ERR, errMessage); } private void sendResultMessage(int what, String sendingMessage) { Message message = Message.obtain(mDetectHandler, what); message.obj = sendingMessage; mDetectHandler.sendMessage(message); }
fecthRecommendationsを呼び出した結果はdidGetRecommendationとfailedToGetRecommendationで処理します。サービスの呼び出しがエラーなく終了した場合は、didGetRecommendationに現在の位置候補となる位置情報を格納したWalkbaseLocationクラスのリストが渡されてきます。
先ほど説明した通り、fecthRecommendationsを呼び出した結果は最も確からしい位置から順にリストに格納されているので、先頭の0番目の位置情報を取り出しています。
取り出したWalkbaseLocationクラスから、さらにメンバ変数のロケーションIDのみを取り出してハンドラに送っています。このロケーションIDはぺこりんサーバに自分と同じ部屋にいる友だちがいるか問い合わせる際に利用します。
failedToGetRecommendationでは、サービスの呼び出しに失敗した場合の処理を書きます。今回は、ただエラーメッセージをハンドラに送るだけとしています。
Walkbaseのサービスを利用して位置を特定するまでの一連の流れは、ここまでです。
private class DetectHandler extends Handler { @Override public void handleMessage(Message msg) { switch(msg.what) { case DETECT: if(msg.obj != null) { HttpPut method = new HttpPut(getString(R.string.PecorinServerURL)+"/user/"+FACEBOOK_ID+"/location"); ArrayList<NameValuePair> params = new ArrayList<NameValuePair>(); params.add(new BasicNameValuePair("current_location_id", msg.obj.toString())); params.add(new BasicNameValuePair("facebook_id", FACEBOOK_ID)); params.add(new BasicNameValuePair("pecorin_token", PECORIN_TOKEN)); try { method.setEntity(new UrlEncodedFormEntity(params, "UTF-8")); DefaultHttpClient httpClient = new DefaultHttpClient(); HttpResponse res = httpClient.execute(method); ・ ・ ・ Log.i(TAG, "locationId:"+msg.obj.toString()); Log.i(TAG, "response:"+res.getEntity().toString()); } catch (UnsupportedEncodingException e) { e.printStackTrace(); Log.e(TAG, e.getMessage()); } catch (ClientProtocolException e) { e.printStackTrace(); Log.e(TAG, e.getMessage()); } catch (IOException e) { e.printStackTrace(); Log.e(TAG, e.getMessage()); } } break; case NO_DETECT: break; case DETECT_ERR: Toast.makeText(getApplicationContext(), "DETECT ERR:"+msg.obj.toString(), Toast.LENGTH_SHORT).show(); break; } } }
一応、位置特定した後のハンドラクラスの処理も少し載せておきます。無事位置が特定されている場合には、ぺこりんサーバに自分の位置を渡して自分の位置の更新を行うのと、同じロケーションIDの場所にいるユーザーを返してもらいます。
割愛していますが、ここでは、その他ノーティフィケーションの処理などを行っています。
Walkbaseを利用してみて
以上、Wi-Fiベースで部屋・フロアレベルの位置同定ができるWalkbaseについて、「ぺこりん」というサンプルアプリを作成した際の例を取って使い方を見てみました。実際に今回の環境で使ってみた結果を参考までに簡単にまとめておきます。
まず部屋レベルの判別についてですが、ホールと会議室については、おおむね問題なく判別できていました。
ただし、会議室入り口付近の1〜2メートルの範囲だと、ホールと会議室どちらにも判定されるような状態でした。また2F廊下についても、吹き抜けに近い部分ではホールと判定される場合もありました。境界がハッキリするようにAPの配置を工夫する必要はありそうです。
今回は細かい精度検証までしていないので、Walkbaseの主張する3〜5メートル程度の誤差内での位置判別が可能かどうかは正直未知数です。
さらに、もう少し大きな施設の中などで、もっと多くのAPや、もっと多くの場所が登録されている場合にどうかなど、実際にはもう少し試してみないと真価は分からないかなぁというのが率直な感想です。
すぐに、プロダクションレベルのサービスの中に組み込んで利用できるとは言い切れませんが、今回のような、限定的な利用、例えば一時的なイベントで屋内の場所を特定して情報を提供するといったようなものであれば、使ってみてもよいかもしれません。
次回は、NFCによる近接通信とGCMによるプッシュ通知
今回はWi-Fiベースの屋内測位をAndroidアプリで利用してみようということで、手近に試せそうなWalkbaseを例に取って説明してきました。最初にも挙げましたが、他にもWi-FiベースでAndroidアプリに組み込み可能なサービスはいくつかあるので、実現したいアイデアに合わせて、いろいろ試してみるとよいと思います。
次回は、引き続き「ぺこりん」の実装を例に取り、NFCによる近接通信とGCMによるプッシュ通知についてまとめてみます。
Copyright © ITmedia, Inc. All Rights Reserved.