Skip to content

Draw large polyline sets as raster tiles (PolylineTiler) - #28

Open
wf9a5m75 wants to merge 1 commit into
developfrom
feat/polyline-tiling
Open

wf9a5m75 wants to merge 1 commit into
developfrom
feat/polyline-tiling

Conversation

@wf9a5m75

@wf9a5m75 wf9a5m75 commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

なぜ

ポリゴンと同じように、ポリラインも数万本を渡せるようにするためです。ボストン市内の道路(54,645 本・234,765 頂点)をネイティブの線で描くと、iOS の Google で全 onAdded まで 114 秒・1.5 GB、Android の Google は Java ヒープが尽きて落ちます。ポリゴンのタイル化(feat/polygon-tiling)と同じ仕組みで、多数のポリラインをラスタータイルとして描きます。

設計の要点(web・Android・iOS で同じ)

  • Polylines(states): Polygons と同じ形のまとめて渡す部品。そのインスタンスが足した id だけを足し引きするので、複数の Polylines / Polyline が同じ地図で共存できます(iOS は既存の Polylines(_:) のまま)。
  • PolylineTilingOptions / PolylineTileSet / PolylineTiler / PolylineController.tiler: 名前と既定値(2,000 本以上または頂点 50,000 以上)はポリゴンと 1 対 1。レイヤー id は mc-polyline-tiles。
  • ポリゴンのタイル化と内部を共通化: タイル集合(ルート・URL の版・16 ms のまとめ・削除)、振り分け、描画(格子索引・ズームごとの簡略化・LRU キャッシュ・無効化・スクラッチのプール・PNG・キャンセル)を内部の ShapeTileSet / ShapeTiler / ShapeTileRenderer に移し、Polygon* はその上の薄い層になりました。公開 API は追加のみです。
  • 描き方: 開いた経路として、ズームの画素格子に丸め → 重複を捨て → Douglas-Peucker 0.5 px(両端は残す)。round cap / round join。線幅は画面上の幅(dp / pt / CSS px)でズームに依らない。候補は線幅の半分だけタイルの外まで引き、はみ出す分もそのまま描くので、タイルの境目で線が切れません。1 px 未満に潰れた線は線幅の点で描きます。
  • タイルにできないもの: geodesic、2 点未満、経度幅 180° 以上。その場の変更でそうなった線はネイティブ描画へ移ります(onAdded は呼ばない)。破線・キャップの指定は API に無いので対象外です。
  • onAdded: ポリゴンと同じ規則(初めて地図に載ったときだけ。タイル⇔ネイティブの移動では呼ばない)。
  • 地図ごとの指定: web はプロバイダの props / config の polylineTilingOptions、Android は compose の PolylineTiling(options)、iOS は MapViewContent.polylineTilingOptions / PolylineTiling 要素。
  • 重なり順: ポリラインのタイルはポリラインの帯の最下段(サークルの上、ネイティブのポリラインとマーカーの下)。下から raster → GroundImage → polygon(+タイル) → circle → polyline(+タイル) → marker(+タイル)。

このリポジトリで変えたこと

  • ポリラインのコントローラに PolylineTiler を付け、onTemplate でポリゴンのタイルと同じラスター層の経路に upsert / remove します。
  • ポリラインを描き直す処理は、タイルで描く線(ネイティブの線を持たない entity)を飛ばします。
  • 重なり: flatZIndex(Polyline, Int.MIN_VALUE)(サークルの上、ネイティブのポリラインの下)。

検証(Android、追従後の 158d1ee2)

  • 単体テスト(testDebugUnitTest、--rerun): core 375・compose 10・arcgis 18・googlemaps 5・here 6・longdo 6・mapbox 1・mappls 4・maptiler 9・tomtom 3・example-app 13 すべて通過。openmobilemaps は 17 件中 1 件失敗(OpenMobileMapsDriverConformanceTest、develop の 5df7b09 で入った Log.isLoggable がモックされていないため。このブランチの差分は触れていない)。
  • apiCheck: 12 モジュールすべて通過。base との差分は公開 API の追加のみ(core の削除行は非 public の PolygonTileRenderer$Model / $Simplified と、PolylineOverlay の 1 引数コンストラクタの行の位置が動いただけ)。
  • assembleDebug 通過。親ビルドの ktlintFormat は回していません。
  • 実機: 追従前の build でタブレットにてボストンの道路のページ(MapLibre / Google、タイルで全件・タップ・重なり順・切り替えのメモリ)を確認済み。追従後の APK はタブレットが adb に見えず未確認です。

挙動の変わる所

  • タップの判定に線幅の半分を足しました(3 プラットフォームの core の PolylineManager.find、タイルでもネイティブでも同じ)。許容距離 = 既存の指の大きさ(tapTolerance)+ 線幅 / 2。太い線は縁から指の大きさ以内であたります。複数あたれば最も近い線。外接矩形で先に候補を絞るので、5 万本でも毎タップ全点をなめません。返す位置(線上の最近点)は今までどおりです。
  • iOS TomTom: 合成スタイルで、SDK のタイル層(ポリゴン・ポリライン・マーカーのタイル)をスタイルの最上段に置きます。TomTom のスタイルは最初の symbol レイヤーより後ろに道路があり、「最初のラベルの前」では道路のタイルが覆われていたためです。そのためポリゴンのタイルも TomTom の道路・ラベルの上に来ます。annotation(ネイティブのサークル・線・マーカー)はこれまでどおりスタイル全体の上です。
  • web Google: overlayMapTypes の中でマーカーのタイルを最上位にしました(ポリゴン < ポリライン < マーカー)。3D 地図ではタイル化せず、すべてネイティブで描きます。
  • web MapLibre 系(maplibre / mapbox / maptiler / mappls / tomtom / longdo): GeoJSON で描くネイティブのポリラインを zIndex 順に並べるようにしました(これまでは渡した順)。タイルとネイティブで重なり方がそろいます。
  • iOS MapLibre / MapTiler / Mappls: raster レンダラが SDK タイル層の重なりのキーを id から取るようにしました(OverlayStacking.rasterKey(styleLayerId:))。追加の順に関係なく、ポリラインのタイルがネイティブのポリラインの下に入ります。

SDK の制約で重なり順を守れないもの

  • web Google(2D): overlayMapTypes は図形・GroundOverlay より必ず下に描かれるので、サークルとネイティブのポリゴンが道路のタイルの上に出ます(ポリゴンのタイル < 道路のタイル < マーカーは守れています)。
  • web Cesium: imagery レイヤーは ground primitive より必ず下。アプリのラスターより上に置くところまで。
  • Android HERE / ArcGIS / TomTom: ポリラインのタイルが SDK のオーバーレイ層(HERE は map item、ArcGIS は GraphicsOverlay)より下になるので、ネイティブのポリゴン・サークルがタイルの上に来ます。ポリゴンのタイルより上、マーカー(とそのタイル)より下は守れています。
  • iOS TomTom: annotation がスタイル全体の上に描かれるので、サークルがポリラインのタイルの上に来ます。ポリゴンのタイルも道路の上(上記)。
  • iOS Longdo / HERE / ArcGIS: z16 の画像を目で見た範囲では、サークルが道路のタイルの上に見えます(画素では測っていない)。iOS OMM: ポリゴンのタイルがほとんど見えません(要調査)。

確認できていないもの

  • Android Google のネイティブ比較: 54,645 本をネイティブで描かせると、main スレッドが OutOfMemoryError で落ちます(GoogleMap.addPolyline → PolylineOptions.writeToParcel、Java ヒープが上限 512 MB に到達)。1 本ずつ Parcel に詰めて GMS に渡すためで、タイル化ありなら落ちません。このため Google のタイル化あり/なしの時間は並べられていません。
  • Android の追従後の実機確認: develop を取り込んだ後の APK は、タブレットが adb に見えず実機で確かめていません(単体テストと apiCheck は通過)。追従前の build では MapLibre / Google で確認済みです。
  • web MapKit: トークンが 401 で地図が出ず、描画と重なり順は確かめていません(タップ判定は 43/43)。
  • web ArcGIS の初回タップ: カメラが一度動くまで当たりません(地図を一度動かすと 43/43)。
  • web Mappls: 右端でタップの位置がずれます(36/43)。
  • iOS の Mac 側照合のずれ: タップ試験の Mac 側の照合(初期カメラを仮定して最寄りの道路を計算)が、Google 16/30・TomTom 18/30・ArcGIS 13/30・ArcGIS2D 10/30・MapKit 11/30 しか合いません。アプリが返した最近点はどれも実在の道路の線から 0.05 pt 以内なので、判定の誤りではなく、Mac 側が仮定するカメラ(ズームの定義・safe area・2D の傾き補正)が SDK と合っていないと見ていますが、確かめてはいません。
  • 既存の失敗(このブランチ由来ではない): Android OMM の OpenMobileMapsDriverConformanceTest 1 件(develop の Log.isLoggable がモックされていない)、iOS ArcGIS・TomTom の単体テスト各 1 件、iOS Google のテストターゲットのビルド(GoogleMaps が iOS 17 を要求)。

base について

ポリラインの差分だけをレビューできるように、base は feat/overlay-on-added(feat/polygon-tiling + onAdded + ボストンのサンプル。そのリポジトリに無い場合は develop)にしています。feat/polygon-tiling は develop にマージ済みなので、feat/overlay-on-added が develop に入ったら、この PR の base を develop に付け替えてください。
追従は rebase せず merge commit で行いました(origin/feat/polygon-tiling → feat/overlay-on-added → feat/polyline-tiling)。

マージの順番

各リポジトリの feat/overlay-on-added →(base を develop に付け替えて)android-sdk-core → android-sdk-compose → 各プロバイダ → 親。プロバイダは android-sdk-core の PolylineTiler / 重なり順を使うので core が先です。マージはレビューのうえで判断してください(この PR はまだマージしていません)。

親の PR: MapConductor/android-sdk#119

The polyline controller gets a PolylineTiler, shown through the same raster
layer path as the polygon tiles. flatZIndex(Polyline, Int.MIN_VALUE): over circles, under native polylines.
Whatever redraws from the polyline manager skips tiled entities (no native line).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant