Nike+を超える高精度位置情報フィルターの作り方 — 位置情報を正確にトラッキングする技術 in iOS(第5回)
ここまで4回のブログで作ってきたサンプルをシミュレーターで実行しダミーの位置情報をフィードすると綺麗に位置情報が取得できマップ上にパスが描かれているのが見えると思います。
ではこのアプリをiPhoneにインストールして街に出て歩いてみましょう。高いビルに囲まれた路地裏や、木が茂っている公園の中、または厚い雲に覆われた天気の下を歩いていると自分の位置とずれたところにパスが描かれることがあるのがわかります。
実はこのように普通にCoreLocationを使っただけでは綺麗な位置情報の軌跡をとることはできません。
Uberのように一時的に車が少し道路よりずれた場所に表示されてもいいアプリのケースもありますがもしNike+のようにユーザーの行動の軌跡を美しくマップ上に描画し、なおかつ走った距離も正確でなければいけないアプリは高い精度の位置情報だけを取り続ける必要があります。
この回では高い精度の位置情報だけを取り続けるためのフィルターの作り方について説明します。
まずdidUpdateLocationで今までは、locationDataArrayに取得した位置情報をそのまま登録していた箇所をfilterAndAddLocation()という関数で置き換えます。
このfilterAndAddLocation()というメソッドの中で位置情報をフィルタし、フィルタを通過したものだけを登録するようにします。
下がfilterAndAddLocation()のコードです。
ここでは3つのフィルタリングをやっているので順番に説明します。
位置情報を取得した時刻でフィルタする
CoreLocationオブジェクトにはtimestampというメンバーがあり、ここにGPSチップが位置情報を取得した時刻が保存されています。まずこれをチェックします。
前々回説明しましたがGSPの取得は非同期処理です。以下の条件によって位置情報の取得できるタイミングが変わります。
- GPS衛星の位置 — 位置情報を取得するには複数のGPSとの通信が必要なので宇宙を飛んでいる複数の衛星と自分の位置はその都度変わる
- 衛星とiPhoneとの間の障害物(ビル、樹木、雲など)の状況
- 携帯電話基地局の位置 — iPhoneに搭載されているGPSチップはA-GPS(Assisted GPS)と呼ばれるもので基地局との通信によって位置情報の精度を補正するチップなので、基地局との通信が必要
- 携帯電話基地局とiPhoneとの間の障害物の状況
上の条件が悪いと一定時間位置情報が取れません。マンションなどの集合住宅の中に入った時にGoogleマップがしばらく更新されないような時です。
この場合、CoreLocationはキャッシュしてある位置情報をdidUpdateLocationに渡してきます。キャッシュしてある位置情報とは上の諸条件が良くて位置情報が取得できていた時に取得した最後の位置情報です。
キャッシュした古い情報を取得してユーザーに見せてしまうと、何分も前にいた位置に自分の位置が表示されることになり位置トラッキングの信頼性を極端に落としてしまいます。
このキャッシュしてある位置情報を使うのを避けるためにまずtimestampを見ます。
timestampから現在までの経過時間をtimeIntervalSinceNowで測って、それが10秒以上であればその位置情報を捨てます。
10秒は走っている時でも40メートルくらいなので、40メートル以上前の位置にユーザーを表示することを避けることができます。
horizontalAccuracyでフィルタする
次にhorizontalAccuracyがマイナスになっていないかを見ます。
horizontalAccuracyは、水平方向の位置の精度をメートル単位で表す値です。
上にあげたGPS取得の諸条件の状態から位置情報の信頼性がメートル単位で計算されこの値が1つ1つのCLLocationオブジェクトのhorizontalAccuracyに入っています。
ただ上のGPS取得の諸条件のどれかが致命的に悪い場合この値がマイナスの値になることがわかっています。
Appleのドキュメントにも
“A negative value indicates that the location’s latitude and longitude are invalid”
と書かれています。
そんな致命的な状況ならCLLocationオブジェクトをアプリに渡さないでくれと思いますが、、入ってくるものはしょうがないのでhorizontalAccuracyの値が0未満だったらこの位置情報を捨てます。
次にhorizontalAccuracyの値が0以上であった場合、この値を使ってフィルタをかけます。
このサンプルでは、100メートルよりもhorizontalAccurayが大きかたら位置情報を捨てるようにしています。
これは”水平方向の誤差が100メートル以下の位置情報だけを使う”という意味です。
この値はアプリごとの目的によって変わってくると思います。Uberであれば車が車線から多少ずれていてもこちらに向かっているのがわかるので100メートルぐらいでいいと思いますし、Pokemonのようなアプリだと100メートル以上の精度は欲しいかもしれませんが、位置情報が取れなくてキャラクターがずっと同じところに止まっているよりは、とにかく動いていてくれた方がいいから200メートルなどに設定した方がいいという判断になるかもしれません。もちろんさらにこの値を狭めて精度の高い位置情報だけを扱うようにすることもできます。
フィルターの威力
下はある薄曇りの日の朝に木がそこそこ茂った公園を走って取得した位置情報のパスになります。
左がフィルターをOffにして走った場合、右がその後フィルターをOnにして同じ経路を走った場合のログになります。
左は画像の下の方で2箇所、制度の低い位置情報を取得してパスが歪んでいるのがわかります。右はそのような箇所はなくフィルターを導入することで移動経路のパス正確さが上がったことがわかります。
Nike+を超えるパフォーマンス
直後にNike+でも走って比べて見ました。青い円のマーカーでポイントした部分で私たちのトラッキングの方が高い精度が出ていることがわかります。
別の日に渋谷でも測定して見ました。こちらは全く同じ精度のトラッキング結果となりました。
このようにここまで説明した内容でNike+以上のパフォーマンスを発揮するトラッキングエンジンを作ることができました。
さらにすごい位置情報トラッキングアルゴリズム
実はこれ以上のパフォーマンスを発揮するトラッキングアルゴリズムを作ることが可能です。
ここまで説明したトラッキングエンジンでは大雨の日に、森の中を、iPhoneをカバンの中に入れた状態で走る、などの非常にGPS取得環境が悪い状態で使われるとパフォーマスがガクッと落ちます。
このような状況でも位置情報の精度を担保しながら位置情報を取り続けるというのが次のチャレンジになります。
このようなNike+やMapMyRunをはるかに超える精密かつタフな位置情報トラッキングアルゴリズムを必要とされる方は私にご連絡ください。iOS, Androidの両プラットフォームで実現させることが可能です。
お仕事の依頼はこちらにお願いします!
mizutori@goldrushcomputing.com
サンプルアプリのコードはこちらです
https://github.com/mizutori/iOSLocationStarterKit
ec86a446ad6549be808fcc440f6eefa249374d10 というcommitが今回までのものをcommitしたものになります。