🍄

アプリの地図上へたくさんのピンを描画する技術

2023/12/15に公開

※この記事は Luup Developers Advent Calendar 2023 の15日目の記事です。

はじめに

こんにちは、Luup iOSチームの茂呂(@slightair)です。

今年は7月の道交法改正に向けて年初から慌ただしく動いてきました。
法改正への対応についてはiOSDC 2023で発表する機会をいただけたので、気になる方は発表報告記事[1]や資料をご覧ください。

法改正を乗り越えつつも、アプリの裏側では日々様々な改善を続けています。
この記事ではその取り組みのひとつとして、LUUPアプリの根幹でもあるマップ画面でのポートピンの描画の工夫について紹介したいと思います。

LUUPアプリのポートピン

LUUPアプリを起動してすぐに表示されるのは、このマップ画面です。

機体ありフィルター表示 返却可フィルター表示
マップ画面1 マップ画面2

この画面では、電動キックボードや電動アシスト自転車のライド開始と返却ができるポートをピンで表現しています。
車両を利用する際に、この画面で開始ポートや返却ポートを探して手続きに進んでもらいます。
ポートに目的の車両があるか、または返却できるスペースが残っているかをピンの色で表現しており、割引の条件を満たすポートがあればピンの右上にクーポンマークも表示しています。

サービスの成長に伴い生まれてきた課題

サービスを開始してしばらくの間はポートの数がそこまで多くなかったため、 全国にあるポートを全件取得しマップ画面上に素直に表示したとしても、パフォーマンス上の問題にはつながりにくい状況でした。
しかしサービスの成長に伴い提供しているポート数が増加してくると[2]、当然表示すべきポートピンの数も増加しマップ画面が重たいと感じるようになってしまいました。

マップ画面はLUUPアプリにとってとても重要な画面であり、アプリ起動直後最初に表示される画面でもあるため、すぐに表示されてサクサク動くのが望ましいでしょう。
ユーザーに気持ちよくアプリを使っていただくために、マップ上へのポートピンの描画を効率よく行う必要がありました。

ポートピンを表示するためには

ポートの状態は、ユーザーの車両の乗り降りやLUUPオペレーションチームによる車両配置の修正などにより、刻々と変化しています。
そのため、アプリは定期的にポートの情報を取得し、画面に反映させる必要があります。

ポートピンの表示には、以下のステップが必要となります。

  1. ポート情報の取得
    • 位置情報
    • 停車中の車両の台数
    • 停車可能な車両の種類、許容量
    • 割引情報など
  2. 表示内容の決定
    • ピンの種類・画像の決定
    • アプリのフィルター設定による絞り込み
  3. マップ上にピンを配置
    • マップSDKのAPI呼び出し

ポートピンをマップ上に表示する際の工夫

ポートピンの表示をステップに分解してみると、ポート情報の取得と画面に表示する部分がそれぞれで改善できそうですね。

ポート情報の取得は実際に計測してみると、全件取得に平均で数秒かかっていました。
取得するタイミングをどう設定するかによりそうですが、重い処理と言えるでしょう。
少なくとも、アプリ起動直後は最新のポート情報を取得する必要があるので、表示されるまでに確実に数秒待つことになってしまいます。

マップ画面では現在地周辺の地図を表示しているので、この表示される範囲のみ情報を取得したりピンを配置・描画すると効率がよいはずです。

そのため、現在のLUUPアプリでは以下の工夫をしています。

  • 全件ではなく表示している、表示しようとする範囲のポート情報のみを取得する
  • 表示している範囲にだけポートピンを配置する

ポート情報取得の効率化

マップの表示領域が変化したら、その領域に存在するポート群のみ情報を取得するようにします。

LUUPアプリではGoogleMapsSDKを利用して地図を表示しており、SDKから現在表示している領域を取得できます。
またポートの情報はFirestoreのドキュメントとして記録しているので、これを指定した領域内に含まれるポートのみ抽出できるようにします。

この抽出のクエリにはGeohashという緯度経度のペアをBase32文字列にエンコードしたものを用いています。
詳しくはFirestoreのドキュメントにあるので、ジオクエリの項目を参照してください。

https://firebase.google.com/docs/firestore/solutions/geoqueries?hl=ja

ポートのドキュメントに緯度経度の他にもGeohashで表現した座標情報を記録しておきます。
このフィールドに対するクエリを発行することで指定の領域のポート群のみを抽出できるようになります。

こうして、マップの表示領域が変わるたび必要な分の最新のポート情報を取得し、画面へ反映できるようになりました。
定期的にポート全件を取得していたときよりも現在表示したい部分のマップをすばやく表示できるだけでなく、都度取得した最新のポート情報を画面に反映できています。

この改善を入れたタイミングでポート全件取得に数秒かかっていたのに対し、必要な分だけを取得するこの方法ではポート群の取得は長くても1秒未満に完了しています。
導入当時よりもポート数が大きく増えているので、この改善を入れていなかったら起動時の表示にもっと時間がかかっていたはずです。

ポートピンのマーカー配置の効率化

地図上にポートピンを表示する場合は、 座標を元にポートピンの画像をマーカーとして表示するようMapSDKのAPIを呼び出します。
マップの表示領域外にもマーカーを配置できますが、ほとんどの場合画面に表示されることがないので無駄になってしまいます。

そのため、表示領域が変化するたびに[3]アプリが保持しているピンの座標と表示領域を比較して、表示領域に入ったか・外に出たかを判定し、マップへ追加したり削除しています。
これによりマップの表示領域外にはマーカーが存在せず、画面へ表示されるマーカーのみ存在するようにしています。

おわりに

刻々と変化するポートの情報をわかりやすくユーザーに示しつつ、体験を損なわせないように行っている、マップ画面でのポートピンの描画の工夫について説明しました。
サービスの成長に合わせて、マップ画面のさらなる改善が必要となったり、他の部分でも改善が必要になることがあると考えています。
引き続き計測などを行い、状況の把握とその改善に努めたいと思います。

今回の事例のポートなど、車両の移り変わりのような今現実に起きていることをリアルタイムに反映させるモバイルアプリケーションの開発は乗り越えるべき課題も多く、やりがいがあります。

もし興味をお持ちいただけたら、弊社の採用ページもご覧になってください。

https://recruit.luup.sc

脚注
  1. 「法改正を乗り越えるiOSアプリのリリース戦略」というタイトルでiOSDC Japan 2023に登壇してきました ↩︎

  2. 2023年12月現在、ポート数は5000を超えています。 ↩︎

  3. 正確にはスクロールが停止して一定時間経った際に呼ばれるデリゲートメソッドから更新処理を呼び出しています。 ↩︎

Luup Developers Blog

Discussion