この記事はMobility Technologies Advent Calendar 2021の7日目です。
こんにちは!タクシーアプリ「GO」の iOS アプリを開発しているパクです。
今回は「GO」アプリを支えるGoogle Mapsの活用について紹介したいと思います。
配車アプリにおいて、マップの重要性は言うまでもないですが「GO」アプリは起動してからタクシーに乗車するまでずっとマップを表示しています。
これは単純に地図情報の表示だけではなく、ユーザーからのインタラクションによって乗車地を選択させたり、状況によって指定座標にピンの引き込みをしたり、迎車中のタクシーの位置を確認できるなど、様々な機能を提供しています。
「GO」は現在 GoogleのMaps SDK for iOSを利用しています。今回、アプリでどのようにマップを利用しているか一部事例を紹介したいと思います。
ここで乗る → タクシーを呼ぶ → 配車確定 → 乗車中に遷移が変わってもMapViewは起動時に一度だけ生成しそれを使い回す
まず「GO」アプリならではの特徴ですが、起動時からタクシーに乗るまで、最初に作ったMapViewをどのような状態でも遷移後もずっと使い続けています。そのため通常の配車までの遷移でNavigationやModal が入らないことが特徴です。これはユーザーがどのような状態になっても最初の画面から離れずに深い遷移をしてないと感じさせるための工夫です。
もちろん重い地図リソースの割当てと解除の繰り返しを回避してパフォーマンスを向上させる目的もあります。
ただベースになる一つのマップを膨大な数のコンポーネントで更新しあうために副作用が起きやすいです。
解決策の一つとして外部から操作するStreamを作り、そこの状態変更だけを許可します。関連内容は以前投稿した記事を参考にしてください (https://lab.mo-t.com/blog/ios-tips-logic-bloat)
ではこれから「GO」アプリでGoogle Mapsの機能として使っている機能をいくつか紹介します。
事前確定運賃の場合、上記のようにマップ上に移動ルート表示をします。
GMSPolylineにルートのpathを設定することで実現させています。pathの型はGMSPathで緯度経度(=CLLocationCoordinate2D)の配列から作ることが可能です。
let polyline = GMSPolyline(path: path) // GMSPathを設定
polyline.strokeWidth = 3 // 太さ
polyline.spans = spanStyle // 線の表示スタイル
polyline.map = gmsMapView
渋谷の駅周辺の赤いエリア内が禁止エリアです。
タクシーを止めてはいけない特別な施設(駅、ビル、空港施設など)を禁止エリアと言います。ここのエリア内ではタクシーを呼べないように吹き出しとピンが表示されます。
そしてマップ上にGMSPolygonを使って赤い領域として描画させています。
ただし川や海など明らかに乗車がだめな場所はあえて禁止エリアとして表示してないです。
その理由は、2つあります。 1つ目は、ユーザーが乗車できない場所として十分認知できるところなためです。 2つ目は、全て禁止エリアとして表示すると、地図上が真っ赤になってしまい視認性が悪くなるためです。
2Dポリゴン情報は二次元の座標データ(CLLocationCoordinate2D型の要素になります)をGMSPathに変換して以下のようにポリゴンデータを作ります。
一つの禁止エリアを表示するためのコードは以下になります。
let polygon = GMSPolygon(path: outerPath) // outerPathはポリゴンの外環のPathになります。
polygon.fillColor = Color.alert()?.withAlphaComponent(0.25) // ポリゴンの中の色
polygon.strokeColor = Color.alert()?.withAlphaComponent(0.6) // 線の色
polygon.strokeWidth = 3 // 線の太さ
polygon.holes = holePaths // ポリゴンの中の穴になります。穴の数分のPathデータです。
タクシー車両の表示にはGMSMarkerを使っています。これはアプリを開いた時にマップ上で実際に走っている車両の表示と配車確定後に迎車に来ている車両を表示させるのに非常に重要です。
登録された車両データに合わせた画像になっているので、実際に街中で通り過ぎてるタクシーが「GO」アプリ上で出てるタクシーと一致してるのか確認するのも面白いと思います。
タクシー車両のアニメーション
動くタクシーを表現するためにはアニメーションを更新するたびに「座標」「方位角(車両の向き表現のため必要です)」「時間」の情報が必要になります。
GMSMarkerのpositionには座標が、rotationには方位角をセットして、時系列でmarkerアニメーションを更新させます。
「GO」アプリではタクシーの移動アニメーションを自然で滑らかに表示させるためにタクシーの座標と方角の更新タイミングを0.1秒単位で行ってます。
マップ上で乗車地のピンを動かすときに、通常はMarkerを操作することを第一に考えますが、「GO」では、マップの上に透明なViewを置いて、そのViewの中央にピンを固定し、マップのカメラ自体を移動させることで自由なピンの操作を実現しています。
ホテル(施設指定の場所)にピンを移動させるとその施設指定の乗車地にピンが引き込まれ建物名と付け場所が自動的に設定される
「GO」ではマップの特定座標に引き込みする機能がいろんなケースであります。ユーザーから一番使われているのは、マップ上の現在地ボタンを押した時の自分の現在地に移動する時だと思います。
気づいてる人もいると思いますが「GO」アプリを起動した時にタクシーが止めやすい道路周辺にピンが自動で位置するようしています。その他に指定のホテルなど特別な施設周辺の場合指定しているタクシー乗り場にピンが引き込まれるようになっています。
これらの引き込みの場合、引き込みする緯度経度をGMSCameraPositionに変換してGMSMapViewのカメラを更新します。
一点注意しなければならないことは、入力した際の緯度経度の値と実際にカメラの移動後に得られる緯度経度は完全に一致しない場合があることです。
例えば 緯度経度 35.0, 138.0にCameraを更新しようとすると、34.99999996412221, 138.0000001564622 のようにGoogle Mapsの都合による値に変わることがあります。
実際これらの位置情報のズレはだいたい10cm程度なのでGPSの精度として考えると問題ないと思います。
ただ緯度経度の比較で特定位置などの判定をするときはこのあたりを十分に理解して、ロジックを作る必要があります。
アプリを使う一般のユーザーは見ることができないですが、開発やQAのデバック機能として特別施設の表示でGMSCircle、近くの引き込み道路を表示させるためGMSPolylineを利用して表示する機能を提供しています。
「GO」アプリの充実なデバック機能については以下の別記事もご覧ください!
タクシーアプリ開発で開発効率を上げるためのデバッグメニュー
https://lab.mo-t.com/blog/ios-debug-menu
現在「GO」アプリはGoogle Mapsプラットフォームに依存してる実装をしていますが、このようなプラットフォーム依存はリスクがあるため、マップを自由に選択できるように抽象化したレイヤを実装しオプションとしてMap Kitでも対応させたいと個人的には思っています。
ここまで説明したMapオブジェクトはAppleのMapKitで1対1に置き換えができるかと思います。
例えば
GMSPolyline = MKPolyline
GMSPolygon = MKPolygon
GMSMarker = MKAnnotation
このように置換することが可能です。
技術的なハードルがどのぐらいあるのかまだまだ綿密に検証する必要がありますが、開発者的にはチャレンジしてみたい課題です。
いかがでしたか。マップの活用は「GO」のアプリ体験で大きな影響を与えるところで今後も開発に注力していきたいと思っています。
特にアプリ開発サイドが主導となって直接改善を測れる余地が多いところもモチベーションが上がります。
最近ますますAppleのMapも強力になっていますので今後は3Dマップの利用、例えばAR機能を使ってタクシー待ち合わせ場所までのガイドなど斬新な企画をエンジニア主導でするのも面白いと思っています。
https://developers.google.com/maps/documentation/ios-sdk/overview
興味のある方は 採用ページ も見ていただけると嬉しいです。
Twitter @mot_techtalk のフォローもよろしくお願いします!