位置情報を正確にトラッキングする技術 in iOS — (第4回) マップの表示
位置情報の取得とは直接関係ないですが、この回では位置情報をマップに表示する方法について説明したいと思います。
位置情報は使ってもマップは表示しないアプリもあると思いますが、デバッグやアルゴリズムのパフォーマンス測定時にマップに位置情報を表示させないと位置情報の精度や取得タイミングなどが直感的にわからないのでマップは開発目的で必要になってきますのでサンプルアプリにマップを搭載してみたいと思います。
まずMain.storyboardのViewControllerのviewにMPMapViewを足します。
次にプロジェクトのCapabilitiesタブでMapsをOnにします。
MKMapViewDelegate を実装します。.
そして以下のメンバー変数を追加します。
- userAnnotationImage — ユーザーの現在位置を示すアノテーション用の画像
- userAnnotation — ユーザーの現在位置を示すアノテーションオブジェクト。MKAnnotationを継承したカスタムのUserAnnotationクラスのオブエクトです。(下の画像の赤いドットがuserAnnotationです)
- accuracyRangeCircle — MKOverlayクラスのオブジェクト. これは下のスクリーンショット中のグレーの円で、現在のGPSの精度の高さを円の大きさによって示しています。Apple MapやGoogle Mapの青い円と同じです。GPSの精度が高いほど円が小さくなります。
- polyline- MKPolyline (MKOverlayのサブクラス)のオブジェクトでユーザーの移動してきたパスを示します。(下のスクリーンショット中の青い線)
- isZooming — ズーム中かどうかのフラグ
- isBlockingAutoZoom — アプリが自動的にユーザーの現在の位置にマップをズームさせるのをブロックするためのフラグ。ユーザーがマップを操作しているときとその後10秒の間trueになる。
- zoomBlockingTimer —isBlockingAutoZoomフラグを10秒間だけtrueにしておくためのタイマー
- didInitialZoom — アプリ起動後、マップがユーザーの現在位置に一度はズームされたかを示すフラグ。falseならばマップの位置がユーザーの位置にあっていないのでzoomを行う。
viewDidLoadでこれらのメンバー変数を初期化していきます。
- mapViewのdelegateに自分を登録する。
- showsUserLocationをfalseにする。今回はユーザーの位置に自前の画像(アノテーション)を表示するため、デフォルトで表示される青いボールを表示しないようにする。
- ユーザーの位置にこのアノテーションに使うアイコン画像の初期化(赤いボールの画像)
- accuracyRangeCircle(グレーの円のオーバーレイ)をダミーの位置に半径50メートル(これも適当な値)で初期化してマップに追加する。
- LocationServiceクラスから送られてくる位置情報を取得するためにNotificationCenterに自分をオブザーバーとして登録。
上のオブザーバー登録でupdateMapという関数が位置情報取得時に毎回呼ばれるようになったのでこれを実装します。
このメソッド内では2つのことをします。
- updatePolyline()
- zoomTo(location: newLocation)
updatePolyline() はユーザーのパスを描画し、
zoomTo() はユーザーの位置が常にマップの中央に表示されるようにマップを移動します。
LocationServiceクラスの中で位置情報を取得して保存しているlocationDataArrayはCLLocationCoordinate2Dのオブジェクトのアレイを作ります。
このArrayを渡してMKPolylineオブジェクトを作り、mapViewに追加します。
zoomTo()メソッドは少しだけ複雑です。
- まだマップがユーザーの位置に一度もズームされていなければズームする。
- ズームが止められいなければ(i.e. isBlockingAutoZoomがfalseならば), マップのフォーカスをユーザーの現在位置に移動する。(ズームはしないで動かすだけ)
- accuracyRangeCircle オーバーレイ(グレーの円でGPSの精度を表示。小さいほど精度が良い)をマップに追加
- userAnnotation (赤いドットでユーザーの現在地を示す)を追加
自動ズームはユーザーがマップを触っている間は(i.e. isBlockingAutoZoomをtrueにすることによって止められます。isBlockingAutoZoomはユーザーが最後にマップに触ってから10秒間trueになるようになります。
これはユーザーがマップをスクロールしたということはある特定の場所が見たいであろう”ということを想定し、新しい位置情報が取得されてもマップを10秒間は動かさないでおくというUX的な配慮です。
MKMapViewDelegateデリゲートメソッドを実装する。
accuracyRegionCircleとpolylineを描画するレンダラーを生成する。
先ほどmapViewに追加したaccuracyRangeCircle(グレー円)とpolyline(青い線)が引数のoverlayに入ってこの関数が呼ばれるので、これを使ってRendererというクラスのオブジェクトを生成して返します。これでマップにaccuracyRangeCircleとpolylineが描画されます。
ユーザーの現在地を示すアノテーションビューを生成する
このメソッドの引数のannotationには、先ほどaddAnnotation()でマップに追加されたユーザーの現在地を示すuserAnnotationオブジェクトが渡されるので、これをラップしたMKAnnotationViewオブジェクトを作って返します。これでuserAnnotationがビューとしてマップ常に表示されます。
マップの描画エリアが変わった時のアクションを定義する。
マップの描画位置が変わるたびにこのメソッドが呼ばれます。マップの描画位置は次の2つの場合で変更されます。
- ユーザーがマップをタップ、スクロールまたはピンチなどして触った場合
- zoomTo()関数の中でマップをズームしたり中心を移動した時
zoomTo()の中でisZoomingをtrueにしているので、このフラグを見ることで、上のどちらのアクションによってこのデリゲートメソッドが呼ばれたのかわかります。falseの時はユーザーがマップに触ったということなのでisBlockingAutoZoomをtrueにするとともにTimerを作って10秒間だけこのフラグをtrueにするようにします。これで10秒間は新しい位置情報が来てもzoomTo()の中でマップのズームが行われないようにし、10秒後からはzoomTo()でマップのズームが行われるようにします。
ユーザーがマップに触った後、10秒間はその画面をキープしておくというおもてなし的な機能です。
サンプルアプリのコードはこちらです
https://github.com/mizutori/iOSLocationStarterKit
このコードには次章以降で説明する様々な機能が追加されていきます。今回までの箇所で一度00ee5cd17169cf56f53f6df369d4f83faa65e9b1 というSHA-hashでcommitしておきましたのでこちらまで戻って見ていただくとこの回までの内容を復習しやすと思います。
次回は、高精度の位置情報を取得するフィルタの作り方について説明します。
このブログの内容に関する質問や位置情報トラッキング機能開発に関する相談などはこちらにご連絡ください。
@mizutory
mizutori@goldrushcomputing.com