Conversation
Step 8。android-for-template と同じ構造。TemplateMap(地図 SDK の代役)を 置き換えるだけで、ホルダー・コントローラ・レンダラの形はそのまま使える。 README.md が authoring guide を兼ねる(実装点 A〜H の一覧、コアが持っていて 書かなくてよいものの一覧、実際に作り込んだ不具合 6 件、実機で確かめること)。 適合テスト 6 本が緑。雛形を書く過程でコア側の viewportSizePx の ディスパッチ不具合が見つかった(雛形が腐らない仕組みが最初から効いた)。
3 プラットフォームを並べて見比べるページなのに、カメラもマーカーも揃って いなかった。同じ定数へ統一する。 ## 揃えた値 - カメラ: エッフェル塔 (48.858140690309604, 2.2945027576710344) / ズーム 17 / bearing 270 - マーカー: 中心 1 個 + 同心円 5 本(各 8 個、45 度おき)= 41 個、間隔 60m - 色: 中心 #111827、リングは内側から #E74C3C #E67E22 #F1C40F #2ECC71 #3498DB - タップでバウンド - 各マーカーの真下に半径 2.5m の円(塗り #FF00FF / 枠 黒 1px / clickable = false) ## それぞれの変更点 - android: すでにこの形。iOS / react と揃えた旨をコメントへ明記しただけ - ios: マーカーと円が無かったので追加(カメラは元から一致) - react: ホノルル / ズーム 11 / 3km 間隔だったのをエッフェル塔の値へ変更し、円を追加 ## bearing 270 は意図的 北向きでないときにしか出ない不具合がある。実際 android-for-openmobilemaps で「アンカーのオフセットが画面空間ではなく 地図空間で適用される」不具合が、bearing 270 のこのページでだけ表面化した (StoreMap は bearing 0 なので完璧に見えていた)。 ## 円は常設の検証装置 円は地面に貼り付き、マーカーは画面固定サイズで描かれる。傾けたり回したりして ピンの先端が円から外れたら、おかしいのはマーカー側だと一目で分かる。 ## 確認 - android: assembleDebug / ktlintCheck 緑。実機(Lenovo TB520FU)の MapLibre で 41 個すべてのピンの先端が円の真上 - ios: xcodebuild BUILD SUCCEEDED - react: tsc に Tilt ページ由来のエラー無し、vite build 成功
android / react と同じ内容を iOS へ。 ## GroundImage: SW / NE を対角とする矩形を描く NE → NW → SW → SE → NE の 5 点で閉じたポリライン。SW / NE の 2 点だけでは **画像がどこまで載っているのかが見えない**ので、ドラッグで範囲を変えたときの 手応えを出す。 ドラッグ中は `PolylineState.points` を差し替えるだけで、インスタンスは作り直さない (作り直すと id が変わってちらつく)。南北・東西が反転しても、常に 2 点を 対角とする矩形になる。 ## プロバイダ選択をページ間で引き継ぐ 各ページが `@State private var provider = MapProvider.initial()` で持つため、 ページを移ると毎回既定へ戻っていた。react-sdk はプロバイダの地図インスタンスを シングルトンで持つので既に引き継がれていて、**android と iOS だけが揃っていなかった**。 `SelectedProviderStore`(プロセス内のみ)に覚え、`MapProvider.initial()` が 環境変数/起動引数 → 直近の選択 → `fallback` の順で決める。 Camera Sync の右ペインだけは `useRemembered: false` にした。引き継ぐと左右が 同じプロバイダになり、見比べるためのページとして成り立たなくなる。 ## 確認 - `xcodebuild` BUILD SUCCEEDED - シミュレータ(iPhone 17 Pro)の GroundImage ページで白い矩形を目視 - 引き継ぎは UI テストで固定(`SelectedProviderCarryOverUITests`)。 Google Map → MapLibre に変えてから Circle ページへ移り、MapLibre のままであることを見る。 `MAPCONDUCTOR_SAMPLE_PROVIDER` は敢えて渡さない(渡すと store より優先され、 引き継ぎの検証にならない)
react-sdk の `VisibleRegionPage.tsx` を基準にし、android / iOS をそこへ寄せた。 3 プラットフォームを並べて見比べるページなのに、カメラも項目も更新契機も 違っていて比較にならなかった。 ## 揃えた内容 - カメラ: ホノルル (21.3069, -157.8583) / ズーム 10(従来は東京) - 更新契機: `onCameraMove`。従来の `onCameraMoveEnd` では**動かし終わるまで 数値が変わらない**。react は動かしている最中も更新される - 項目と並び: Center / Zoom / Bearing / Tilt / Bounds / Near Left / Near Right / Far Left / Far Right - 座標の書式: `toUrlValue(5)`。3 プラットフォームで同じ文字列になるので、 数値をそのまま突き合わせられる - 取得できないときの表示: `Unavailable` ## 落としたもの react に無いので外した。 - 地図上の角マーカー 6 個(Center / SW / NE / NL / NR / FL / FR)。 このページで見たいのは**数値**であって、マーカーがあると 「マーカーの位置が正しいか」という別の話が混ざる - Size(km)、Altitude、折りたたみ、コピー/共有ボタン android の `VisibleRegionInfo` は Size の算出に使っていたもので、唯一の 利用者になった `ZoomCalibrationPage` へ移し、実際に使う幅・高さだけに絞った (較正ページはプロバイダ間で「同じズームでの見えている広さ」を比べるので、 そこは残す必要がある)。 ## 確認 - android: 実機(Lenovo TB520FU)の MapLibre / Open Mobile Maps で、 ドラッグ中に全項目が追従することを目視。ktlintCheck / testDebugUnitTest / apiCheck 緑 - iOS: `xcodebuild` BUILD SUCCEEDED。シミュレータで目視し、 「動かすと Unavailable が実数値へ変わる」ことを UI テストで固定 (`VisibleRegionPanelUITests`)。`onCameraMoveEnd` に戻すと落ちる
国土数値情報の鉄道データは属性が `N02_001` のような番号なので、吹き出しに
生のキーが並んでも何の値か分からなかった(値の方も "11" や "2" のコードなので、
見出しが読めないと手掛かりがゼロになる)。
N02_001 → 鉄道区分(railway category)
N02_002 → 事業者区分(business category)
N02_003 → 路線名(railway name)
N02_004 → 運営会社(railway company)
android / iOS / react(web)/ react-native の 4 つとも**同じ文言**にした。
3 プラットフォームを並べて見比べるサンプルなので、見出しが違うと
同じ地物を選んでいるのかどうかが判断できない。
web は既に日本語の見出しを持っていたが `N02_002` だけ「事業者種別」で、
android / iOS は生キーのままだった。
見出しが長くなったぶん、属性の列と値の列を半々にした。iOS の表の幅は
320pt のままにしてある(広げると iPhone の横幅で吹き出しが画面外へ出る)。
表に無いキーは今まで通り生のキー名を出すので、データ側に属性が増えても
表から消えることはない。
## 確認
- android: 実機(Lenovo TB520FU)で総武線をタップ → 4 行とも新しい見出しで表示
- iOS: シミュレータで半蔵門線をタップ → 同上。ktlint 相当(ビルド)緑
- react(web): Playwright で新宿線をタップ → 同上
- react-native: 同じ差し替えのみ。実機では未確認
なお XCUITest では吹き出しの中身を検証できない。InfoBubble は地図の
ビュー階層の中に描かれるため `staticTexts` に出てこない(画面には出ている)。
UI テストは足さず、目視で確認した。
吹き出しの見出しを「路線名(railway name)」のように併記していたが、日本語と英語が 1 行に混ざるうえ、値の方は「総武線」のままで英語にならなかった。 アプリの言語(web は言語セレクタ、それ以外は端末の言語)が日本語なら日本語、 それ以外なら英語で出すように変えた。 ## 対訳をアプリではなくデータへ `N02-22_GML.zip` の各フィーチャーに `N02_003_en`(路線名)と `N02_004_en` (運営会社)を足した。549 路線・177 事業者ぶん。 アプリ側に対訳表を置くと、**同じ表を 4 つのサンプルへ配ることになり、 しかも必ずどれかが古くなる**。データが持っていれば、アプリがするのは 「日本語なら `N02_003`、英語なら `N02_003_en`」だけで済む。 英語名は、事業者が公式の英語名を出しているものはそれを使い(JR East / Tokyo Metro / Kintetsu Railway)、それ以外はヘボン式で長音記号なし (Sobu Line / Tokaido Shinkansen)。JR や私鉄の英語案内がその表記のため。 経緯は zip 内の README.md に書いた。 `N02_001`(鉄道区分)と `N02_002`(事業者区分)は元データがコード値なので、 見出しだけ訳して値はそのまま。 ## 表示 - 見出し: 鉄道区分 / 事業者区分 / 路線名 / 運営会社 ⇔ Railway category / Business category / Railway name / Railway company - ヘッダ行: プロパティ・値 ⇔ Property・Value - `_en` で終わる属性は行として出さない(同じ項目が 2 行に増えるため) - 表に無いキーは今まで通り生のキー名。データ側に属性が増えても消えない zip は元より小さくなっている(2,428KB → 2,383KB)。属性が 2 つ増えたが、 geojson を字下げなしで書き出したぶんが上回った。 ## 確認 日本語・英語の両方で、4 プラットフォームとも切り替わることを目視。 - android: 実機。端末が ja-JP なので `cmd locale set-app-locales` で英語も確認 (総武線/東日本旅客鉄道 ⇔ Sobu Line/JR East) - iOS: シミュレータを `-AppleLanguages` で切り替え (11号線半蔵門線/東京地下鉄 ⇔ Line 11 Hanzomon Line/Tokyo Metro) - react(web): `/maplibre/geojson-layer` と `/ja` を Playwright で (12号線大江戸線/東京都 ⇔ Line 12 Oedo Line/Tokyo Metropolitan Government) - react-native: 端末の言語は `NativeModules` から取る(`react-native-localize` を 足さずに済ませるため)。型検査のみ。実機では未確認 なお iOS の吹き出しに accessibility を付けるのは見送った。吹き出しは地図ビューの 中に足したビューで、地図ビュー自身が accessibility のコンテナとして振る舞い 並べる要素を自前で決めているため、中身が読み上げにも UI テストにも出てこない。 `accessibilityElements` へ足しても効かなかった(MapLibre は container のメソッドを 上書きしている)。器を地図ビューの外へ出せば直るが、9 プロバイダ共通の コア変更になるので、この変更には含めない。
プロバイダを選び直しても、次のページへ移ると指定のプロバイダへ戻っていた。
## 原因
`--es provider <key>`(android)/ `MAPCONDUCTOR_SAMPLE_PROVIDER`(iOS)は
「**開始時**のプロバイダ」のつもりの指定だったが、各ページが表示のたびに
これを読み、しかも直近の選択より優先していた。つまり指定が起動時ではなく
**常時の上書き**になっていて、ユーザーの選択が毎回打ち消されていた。
指定つきで起動したプロセスでのみ起きる。ふつうに起動した場合は指定が無いので
引き継ぎは効く。「一部では機能するのに全部では機能しない」ように見えるのはこのため。
## 直し方
指定は起動時に一度だけ読み、`SelectedProviderStore` へ「覚えている選択」として
置く。以降はユーザーが選んだときと同じ扱いになり、選び直せば上書きされる。
ページ側は store だけを見る。
- android: `MainActivity.onCreate` で store へ入れる。`providerExtra` は廃止し、
`DemoMapPageScaffold` からも参照を外した
- iOS: `MapProvider.initial()` から環境変数の参照を外し、読み取りは
`fromLaunch()` として切り出してアプリ起動時に一度だけ呼ぶ。
Camera Sync の右ペインは自分用のキー(`..._RIGHT`)を直接読む形にしたので、
`useRemembered` 引数は不要になり削除した
- android の `SelectedProviderStore.indexIn` は大文字小文字を無視するようにした
(`--es provider MapLibre` のような指定も引き当てられるように)
## 確認
- android: 実機で「polyline-click を `--es provider maplibre` つきで起動 →
Open Mobile Maps を選択 → Geodesic Polygons へ移動」が
Open Mobile Maps のままになることを目視(直す前は MapLibre に戻っていた)
- iOS: `SelectedProviderCarryOverUITests` に
`testLaunchProviderDoesNotOutrankTheUsersChoice` を追加。直す前のコードに
戻すと `("Provider, MapKit") is not equal to ("Provider, MapLibre")` で落ちる
ことを確認済み
## 既存の UI テストが空回りしていた件
同ファイルの `testProviderSelectionCarriesOverToTheNextPage` は、サイドバーの
移動先を `staticTexts["Circle"]` で引いていた。サイドバーは節見出しもただの
文字なので**見出しの方**に当たり、叩いても移動しない。移動しないまま前のページの
ピッカーを見て通っていた。行だけが Button なので `buttons["Circle"]` に変え、
さらに移動先ページの見出しが出るまで待ってから、ピッカーのラベルを
存在確認ではなく**一致で**確かめるようにした。
android-for-openmobilemaps に対応する iOS ドライバーを作り始める。
ドライバー層の共通化が本当に揃っているかは、**同じ SDK を 2 つ目の
プラットフォームで書いてみないと分からない**ため。
このコミットは土台まで。実装点 A〜H はこれから(進捗表は README)。
## maps-core が SwiftPM で解決できない件
maps-core 4.0.0 の Package.swift は、`external/djinni` に中身があると djinni を
相対パスの依存へ切り替える。SwiftPM は git 依存を再帰 clone するので中身が入り、
「依存パッケージがローカルパス依存を持つ」形になって解決できない:
error: exhausted attempts to resolve the dependencies graph, with the
following dependencies unresolved:
* 'djinni' at .../checkouts/maps-core/external/djinni
`../maps-core` へ自分で clone し **djinni だけ空のまま**にすると、maps-core は
djinni を URL から取るようになり解決が通る。Package.swift はローカルがあれば
そちらを、無ければ公開リポジトリを見るので、上流が直れば自動的に戻る。
手順は README に書いた。Google Maps の ios-maps-sdk も同じくローカル clone 運用。
Metal ツールチェーン(`xcodebuild -downloadComponent MetalToolchain`)も要る。
入っていないと maps-core のシェーダで落ちる。これも README に書いた。
## ズーム換算
この SDK のズームは**縮尺の分母**であって 2 の指数ではないので、他プロバイダの
ようなオフセットの足し算では変換できず、`WebMercatorZoomAltitudeConverter` を
使えない(対数を挟む)。android と同じ導出・同じ定数
(`156543.033928 x 160 / 0.0254 = 986_097_222`)にしてある。
ここがプラットフォームでずれると、同じ統一ズームでも縮尺が変わり、
並べて見比べるサンプルが成立しなくなる。
android の適合テストのズーム部分と同じ主張をテストへ移した(5 件、緑)。
往復・単調性・定数一致・0 以下の縮尺でのクランプ。
バージョンは android の mapscore と同じ 4.0.0。
…るため)
前のコミットでは「djinni を空にすれば URL から取れるので解決が通る」と書いたが、
それだと**djinni の版が変わる**ことを見落としていた。
maps-core の submodule が指す版 1.4.0
URL フォールバックの制約 .upToNextMinor(from: "1.0.9") 1.0.10
生成された bridging は submodule の版に対して作られているので、上流が意図する
1.4.0 で使う。`--recurse-submodules` で clone すれば path 依存として解決も通る
(検証済み。テスト 5 件緑)。手順もこちらの方が単純。
あわせて `.package(url:)` が使えないことを確認した記録を残す。SwiftPM は git 依存を
再帰 clone するため submodule の中身が必ず入り、maps-core の manifest が djinni を
相対パス依存へ切り替えてしまう。**キャッシュを消した完全な初期状態でも同じ**なので、
こちら側の設定では回避できない(上流の作りの問題)。
Metal ツールチェーンについても、Xcode 26 から本体に含まれなくなった旨と
GUI / CI での入れ方を書き足した。
android-for-openmobilemaps から、SDK の型に触らずに書ける部分と、投影まわりを移す。 残りは B(カメラ)/ D(レンダラ 6 種)/ E / F / G。 ## 移した中身 - 値の変換と Actual 型(`OpenMobileMapsTypes`) EPSG:4326 は **x が経度・y が緯度**。MapConductor は latitude / longitude の順なので 取り違えても例外にならず「座標が入れ替わるだけ」になる。変換をこの 1 箇所に閉じ込める。 穴付きポリゴンは `MCPolygonCoord.holes` にそのまま載る(コアのブリッジ細工は要らない)。 - 地図デザイン(実装点 C) この SDK は「素の地図」を持たない。地図デザイン = どのタイルを一番下に敷くか。 - capability(実装点 H) コントローラの外に置く。コントローラの組み立てには Metal と C++ に触る呼び出しが 要るので、素のユニットテストからは作れないため。 - tilt 擬似表現 `MCMapCameraInterface` にピッチが無く、`asMapCamera3d()` は平面地図では nil。 ios-for-arcgis の 2D と同じ方式・同じ定数で、カメラ位置の付け替えとして表現する。 - 傾き用の入れ物(`OpenMobileMapsMapSurface`)と ホルダー(実装点 A) 遠近感はビューを X 軸まわりに回して作る。android の `rotationX` + `cameraDistance` は iOS では `CATransform3D` の m34 + 回転にあたる。 **投影は必ずこの入れ物で畳む**。SDK の投影は内側の `MCMapView` の座標で返るので、 畳まないと傾けたときにオーバーレイが全部ずれる(android では tilt 45 度で InfoBubble が画面外へ飛ぶ形で出た)。タッチ経路用に内側座標を受け取る口も分けてある (android で二重変換の実害が出た箇所)。 ## 確認 テスト 8 件緑(ズーム 5・tilt 3)。いずれも android の適合テストと同じ主張。 実機での見た目は D(レンダラ)が終わってから。この SDK はベースのタイルも レイヤとして自分で載せる作りなので、それまでは何も表示されない。
## レイヤの重ね順(`OpenMobileMapsLayers`) この SDK は「オーバーレイ 1 つ = ネイティブオブジェクト 1 つ」ではなく、種別ごとに 1 枚のレイヤへ要素のリストを流し込む形(MapLibre の GeoJSON ソースに近い)。 レンダラが個別にレイヤを作ると重ね順が生成順に依存して不定になるので、 1 箇所でまとめて作り索引を割り当てる。android と同じ索引。 罠を 2 つとも引き継いだ: - `insertLayer(at:at:)` は**挿入ではなく上書き**。索引が衝突すると、居たレイヤが 黙って外れる(android では「円は塗れるのにポリゴンだけ塗れない」で出た)。 そのため索引を 0 / 1.. / 1000.. に分けて衝突させない。 - `asLayerInterface()` は**呼ぶたびに別のオブジェクトを返す**。載せたときの値を 持っておかないと外せない。`lazy var` で一度だけ取る。 クリックは SDK に取らせない(全レイヤ `setLayerClickable(false)`)。当たり判定は コアが地理座標で行う。ここを消すと `clickable = false` の透過もカスケードの順序も 効かなくなる。 ## レンダラ 3 種 - ポリライン: geodesic を密度化して子午線で分割するので、1 本が複数の線になり得る - ポリゴン: 穴は `MCPolygonCoord.holes` にそのまま載る(ブリッジ細工は不要)。 ただし**巻き方向を揃える**こと。テッセレータは外周 CCW / 穴 CW 前提で、逆向きだと 塗りが丸ごと消える(例外も警告も出ない)。輪郭は線レイヤで別に描く。 - 円: SDK に円が無いのでコア共通の `circleToRing` でリングに直して描く `setPolygons` を使う理由も android と同じ。4.0 の PolygonLayer は原点を持つように なっていて、`setPolygons` を通っていないレイヤに add すると何も描かれない。 ## SDK 名の違い djinni の Swift 名は android の Kotlin 名とほぼ 1:1 だが、enum だけ生の ObjC 名(`.SCREEN_PIXEL` / `.ROUND`)で出る。`MCPolygonInfo` の引数ラベルも `highlightColor` ではなく `highlight`。
残るレンダラはマーカーのみ。 ## タイル設定(`WebMercatorTileLayerConfig`) SDK 同梱の web メルカトル設定は使わない。同梱の設定はレベルの縮尺が SDK 独自の基準 (レベル 0 で 1:500'000'000)で刻まれていて、MapConductor の統一ズームの基準 (1:986'097'222)と約 2 倍ずれる。そのまま使うと**統一ズーム Z のときに 1 段低い レベルのタイルが選ばれ、常にぼやける**。表示はされるので気づきにくい。 `tileSize` は画像のピクセル数ではなく**ポイント**なので、レベルの選択に端末密度は 関係しない。android で一度ここに density を掛けていて、マーカータイルが 1 段深い レベルで選ばれ PostOffice のアイコンが巨大かつぼやける、という形で出た。 引数に密度が無いことをテストで固定してある。 ## レンダラ 2 種 - グラウンドイメージ: テクスチャ付きポリゴンレイヤを 1 枚 1 画像として使う - ラスターレイヤ: マーカーのタイル描画もこの経路を通るので、ここが動かないと PostOffice のような大量マーカーのページが白紙になる。透過前提なので `numDrawPreviousLayers = 0` / `maskTile = true`(android で粗い親タイルが 残り続ける不具合を直したときと同じ設定) ## 確認 テスト 11 件緑(ズーム 5・tilt 3・タイル 3)。いずれも android の適合テストと同じ主張。 なお 512pt タイルのテストで最初に落とした。「512pt は 1 段浅いレベルでよい」を 逆向きに書いていたもので、android の同じテストと突き合わせて直した。 実装ではなくテスト側の誤り。
`OpenMobileMapsDesign` の 3 つがどれも `attributionRules` を渡しておらず、空のままだった。 コアの出典表示は「宣言された規則が 1 つも無ければ何も描かない」ので、**OSM のタイルを 出典表示なしで表示していた**。タイルの利用条件を満たさない状態だったので直す。 - OpenStreetMap / OpenStreetMap Japan: `© OpenStreetMap contributors` - OpenTopoMap: 地図データと地図スタイルの両方(先方が両方の表示を求めている) ## なぜこの SDK でだけ抜けたのか 他のプロバイダは**地図 SDK 自身が地図ビューの中へロゴを描く**(MapLibre の 「MapLibre ⓘ」など)ので、こちらが何も宣言しなくても表示される。Open Mobile Maps は 地図の中身をすべてアプリが載せる作りで、出典表示も自前では出さない。つまり このドライバーだけが宣言を必要としていて、それに気づいていなかった。 同じことが起きないよう、両プラットフォームの定義にその理由をコメントで残した。 ## 擬似 tilt の 200% 拡大とは無関係 「傾きのためにビューを 200% にしているせいで消えているのでは」という指摘から調べたが、 そちらは原因ではなかった。出典表示は地図ビューの**外側**(android は Compose、 iOS は SwiftUI の兄弟要素)に描かれるので、内側のビューを拡大・回転しても影響を受けない。 実機で確認: tilt 0 でも tilt 89(描画は 60 でクランプ)でも右下に表示される。
submodule 側のコミット: android-sdk-compose 55b6749 / ios-sdk-core 8b3c61f / js-sdk-react bdabdd4 android 実機で変更前後を切り出して比較し、帯の下の隙間が無くなったことを目視。 ktlintCheck / apiCheck 緑。iOS はコアとサンプルアプリのビルド成功。 react は型検査に影響なし。
## SDK のアニメーションを使わない `moveToCenterPositionZoom(..., animated: true)` は**尺を指定できず、実測で常に約 300ms** で着地する。アプリが 1000ms と言っても 300ms で終わるので、他プロバイダと並べると 明らかに先に着いてしまう。フレームを刻んで `animated: false` の移動を繰り返し、 こちらで尺を守る。このコミットはその補間の中身(コントローラは次)。 ## 中身 - 中心は**メルカトルのメートル空間**で線形に混ぜる。緯度を直接混ぜると、 緯度 0 → 60 の中点が 30 になってしまう(正しくは約 35.2) - 経度と方位は**近い方向へ**回る。素直に線形補間すると、東京 → ホノルルが 太平洋ではなくユーラシア大陸経由になる - イージングは android 標準の `AccelerateDecelerateInterpolator` と同じ余弦カーブ。 Google Maps のカメラもこの系統なので、並べたときの見え方が揃う Swift の `%` は負の値で負を返すので、Kotlin の `((x % m) + m) % m` に相当する 補助を足してある(経度の正規化で効く)。 ## 確認 テスト 16 件緑(ズーム 5・tilt 3・タイル 3・カメラ補間 5)。 いずれも android の適合テストと同じ主張。
## 症状 Open Mobile Maps だけ地図のタップが**一度も届いていなかった**。地図クリック・ オーバーレイのクリックカスケード・マーカーのタップ・マーカーのドラッグが まとめて無反応で、UI テストで 64 回叩いて 0 回という状態だった。 ## 原因 `MCMapView` が自分に付けている `TouchForwardingGestureRecognizer` の `shouldBeRequiredToFail(by:)` が、画面左端 44pt より内側で始まったタッチについて **常に true を返す**(maps-core/ios/maps/MCMapView.swift)。「こちらの認識器が 認識するには SDK 側が先に失敗しなければならない」という依存が張られるが、 SDK 側は touchesBegan で .began に入り touchesEnded で .ended に至るだけで **決して失敗しない**。したがって外側に足した UITapGestureRecognizer / UILongPressGestureRecognizer は永久に待たされる。 `shouldRecognizeSimultaneouslyWith` を true にしても解けない。失敗依存は 「どちらか一方が true を返せば成立し、false を返しても解除は保証されない」ため。 ## 直し方 android-for-openmobilemaps と同じく **SDK 自身のタッチ経路(MCTouchInterface)** を使う。新しい OpenMobileMapsGestures.swift が android の OpenMobileMapsGestures.kt に対応する(実装点 E / F)。 - タップ … onClickConfirmed → コアの dispatchOverlayTap(カスケードはコア) - 長押し … onLongPress → マーカーのドラッグ開始、無ければ地図の長押し - ドラッグ … onMove は差分しか渡してこないので、長押しの位置から足し込んで 絶対位置を組み直す(SDK 側の touchPosition と同じ足し込みなのでずれない) - パン抑止(実装点 F)… ドラッグ中は onMove を消費する。リスナーを索引 0 で 差し込んでカメラより手前に置いてあるので、消費すればカメラまで届かない 内側 MCMapView の isUserInteractionEnabled は**落とさない**。落とすと進行中の タッチへ touchesCancelled が飛び、掴んだ直後にドラッグが消える。android が setTouchEnabled(false) を使えるのは MotionEvent を横取りし続けられるからで、 iOS には対応する経路が無い。 ## 確認(iPhone シミュレータ) - GeoJSON Layer: 6 タップで路線に命中(従来は 64 タップ 0 反応) - marker-basic: ピンのタップでスニペットが出る(カスケード 1 段目) - polygon-basic: ドラッグ中 32 回 onDrag、指が下りている間だけ頂点が追従し **地図は動かない**。離したあとは地図がパンできる(isScrollEnabled の復元) ## 残っている差 pinch の終わりに onCameraMoveEnd を配っていない。android も onMoveComplete しか 実装していないので現状は揃っているが、両プラットフォームとも onTwoFingerMoveComplete を足すべき箇所。
## 症状 GeoJSON Layer ページで、同じデータ・同じカメラなのにプロバイダごとに ポリラインの太さが**最大 9 倍**違っていた。 iPhone 17 Pro シミュレータ、地図ズーム 13、赤紫の路線の太さ(物理ピクセル): | provider | 修正前 | 修正後 | |---|---|---| | MapLibre / Mapbox / MapTiler / HERE | 17-18 | 17-18 | | MapKit | 15 | 15 | | **TomTom** | **35** | **17** | | **ArcGIS 2D** | **9** | **18** | | Google Maps | 5 | 9 | | Longdo | 10 | 10 | | ArcGIS 3D | 4 | 4 | ## 原因 `RasterLayerSource.tileSize` は「タイル 1 枚が覆うレイアウト単位(ポイント)」で、 既定は 512。地図ズーム 13 では世界が 256×2^13 ポイントなので、正しくは **z=12** の タイルを 512 ポイントに敷く。MapLibre はそのとおりに動く。 そこから外れていたものを、要求されたタイル z を実測して突き止めた。 - **ArcGIS**: `WebTiledLayer(urlTemplate:subDomains:)` が `defaultTileInfo`(256)を 使うので、512 の画像が 256 の枠に入り中身が半分になる。z=13 を要求していた。 宣言された `tileSize` から TileInfo を組んで渡す。解像度は「level の地理的な広さ」を 固定するため `tileSize` に反比例させること。 - **TomTom**: 同じスタイル JSON なのに宣言の**倍**で敷く(z=11 を要求)。半分を 宣言するとちょうど揃うことを実測で確認した。 - **Google Maps**: `GMSTileLayer.tileSize` はポイントではなく**ピクセル**。 画面倍率を掛ける。ただし 1536 でも 3072 でも結果が変わらず、SDK 側に上限が あるとみられる。5px → 9px までは寄るが MapLibre には届かない。 Longdo は `Layer` の options に `tileSize` を足しても変化しないことを実測した (256 固定)。ArcGIS 3D は TileInfo を直しても SceneView が 2 段深い level を 選ぶままで、外から効かせる手が見つかっていない。どちらもコメントに残した。 ## ついでに直したもの `GeoJSONLayerMapPage` がプロバイダの初期値を `.mapLibre` に決め打ちしていた。 他のページと同じく選択中のプロバイダを引き継ぐ(android は共通の選択をそのまま使う)。 ## 影響範囲 ArcGIS のラスターは GeoJSON レイヤだけでなくヒートマップとタイル方式マーカーも 同じだけ縮んでいた。どれも一緒に直る。
## 症状 GeoJSON Layer が OMM だけ永久に描かれない。クリックは効く(当たり判定は CPU 側で 座標を直接見る)うえ、下の OSM 基図が鉄道も描くため「描かれている」ように見える。 ## 原因 コアの `RasterLayerController.add` は、レンダラが nil を返すと**エンティティを 登録しない**。その後 `visible` が true へ反転しても `update(state:)` は登録済みの エンティティを前提に差分を取るので、黙って何もしない。 GeoJSON レイヤはまさに「`visible: false` で add → 読み込み完了後に true」という 形で生まれる。このレンダラだけ「不可視 = レイヤ無し」と混同して nil を返して いたため、反転が届かなかった。マーカータイル(PostOffice)は upsert 経由で 最初から visible なので影響がなく、「経路は同じなのに GeoJSON だけ出ない」 という食い違いになっていた。 ## 直し方 他プロバイダと同じく「レイヤは作る・可視性は別のスイッチで切る」。 この SDK では `MCLayerInterface.hide()` / `show()` がそれにあたる。 ## 確認(iPhone シミュレータ) - GeoJSON Layer: 路線が描かれ、線の太さ 17px(MapLibre 18px と同等) - PostOffice: マーカータイル描画に回帰なし android-for-openmobilemaps にも同じ修正を入れた(あちらは Compose の 再コンポジションで add が呼び直されると自己修復するため発覚しにくかった)。
- project.yml: ios-for-mappls パッケージ + 依存 + postBuildScript の コピー対象へ conf/olf を追加 (Resources/mappls.i.conf / .i.olf、 アカウント固有なので gitignore。無ければ Mappls だけ認証エラー) - SampleMapView: enum case .mappls / allCases / parse / body 分岐 / initializeAllSDKs で MapplsInitSDK.ensureInitialized() - 全ページに mapplsState を追加 (openMobileMaps と同型の機械的追加) - ios-for-mappls 本体は同ディレクトリの独立リポジトリ (submodule 登録は リモート作成後に .gitmodules へ追加すること)
MarkerClusterGroup はプロバイダ別のジェネリック分岐 (MarkerClusterGroup<XxxActualMarker>) で、機械的な mapplsState 追加パスでは拾えず分岐が漏れていた。分岐が無いと MarkerClusterGroup が一切マウントされず、クラスタが 1 つも描画されない (シミュレータ/実機で確認、追加後は数字バッジ付きクラスタが出る)
- FlyTo: android / react と同じ都市イラスト (city_*.png、react の public/city-icons と同一画像) を ImageIcon で表示。読めない環境は 従来のラベルへフォールバック - UI Settings: トグルパネルを android / react (.control-panel) と同じ 左下配置に (ZStack topTrailing 既定 → bottomLeading)
…xtensions - strategyIconLayer を追加(通常マーカーとは applyIcons の全量流し込みが 衝突するためレイヤを分離、重ね順は通常マーカーの上) - StrategyMarkerManager を controller に持たせ、factory で MarkerRenderingSupportKey へ登録(未登録だとクラスタページが白紙) - カメラ変更と初期カメラを strategy へ転送。sendInitialCameraUpdate は レイアウト前なら 0.1s 間隔でリトライ(1 回きりだと XCUITest 起動で 初期カメラが配られず、クラスタ計算が永久に始まらない) - handleTap に strategy マーカーの当たり判定フォールバックを追加
- クラスタークリックで重心へ zoom+2(上限 18、600ms)する zoomToCluster を 追加(android MarkerClusterMapPageViewModel / react useClusterClick と同一仕様) - spiderfy は既定で無効にし、MAPCONDUCTOR_SAMPLE_SPIDERFY=1 のときだけ有効 (SpiderfyUITests が拡張機能の検証用に opt-in する) - クラスタページに .openMobileMaps 分岐を追加(mappls 分岐漏れと同型の欠落) - submodule bump: ios-for-tomtom(クラスタタップのフォールバック)、 ios-for-arcgis(2D のマーカータイリング配線) - 検証: ClusterZoomUITests(ローカル)で maplibre / tomtom / openmobilemaps の タップ→クリック配送とズームインを確認、longdo / arcgis / arcgis2d は表示確認
…iders styled-info-bubble を作り直し: マーカー 1 個 + 常時表示バブルに、 8 色スウォッチ 4 行(塗り / 枠線 / 文字 / マーカー)とスライダー 2 本 (枠線幅・マーカースケール 0.5〜2.0、0.25 刻み)。android / react と同一仕様。 ios-sdk-core bump (InfoBubble borderWidth)。検証は StyledBubbleUITests(ローカル)。
Sample app deployment target 17.0 -> 18.0 (required by ArcGIS 300.x); regenerated project via xcodegen.
Verified on iPhone 17 Pro simulator and iPad Pro 11 device: CameraSync (Google source vs ArcGIS 2D / Mapbox / MapLibre) matches exactly after drag.
- ios-geojson-layer を雛形にしたタイル描画パイプライン + KML パーサ/ローダ。 XMLParser (SAX) + 明示スタックで再帰なし(20,000 段ネストをテストで実証)、 ZIP 署名判別による KMZ 読み込み(KMZArchive: stored/deflate, zlib)、 NetworkLink のキュー+訪問済みセット追跡(循環検出・maxDocuments・失敗スキップ) - サンプルアプリに kml-layer ページ(sample.kml 解析、タップで属性テーブル)を追加 - MapConductorKMLTests 15 本 + api-surface に MapConductorKML を登録
android-kml と同じ修正: 内部(穴を除く)判定を常に先に行い、外れた場合のみ 輪郭近傍にフォールバック。KMLHitTesterTests 5 本を追加(内部/穴/輪郭近傍/遠方/既定許容差)
React Native の reactnative-for-template から同じ地図を使えるようにする。 TemplateMapCoordinator と同じ配線を SwiftUI 非依存の形で持つ。 maplibre / here / googlemaps の *MapHost と同じ位置づけで、@_spi(MapConductorDriver) なのでアプリ向けの凍結 API には載らない。 雛形の地図は描画面を持たないので makeMapView は空の UIView を返す。 本物のドライバーはここで SDK の MapView を返す。
ios-for-longdo: makeMapView の公開と LongdoMapFramework 依存。 ios-for-template: MapConductorForTemplate.podspec を新規追加。 RN のサンプルアプリに雛形を載せておかないと腐るため(android-for-template を publishToMavenLocal しているのと同じ理由)。単体配布しないので LICENSE ファイルではなくテキストを直接置いた。
ClickCascadeUITests が実際に配送先を検証できるようにする。
InfoBubble に出る "Inside"/"Outside" は last-write-wins なので、**二重配送を
表現できない**(ポリゴンと地図の両方に配送されても後勝ちで片方しか残らない)。
種別ごとに数え、"map=N polygon=N" を cascadeReadout として公開する。
InfoBubble の描画が効いているかとは独立に配送だけを見られる。
これまで ClickCascadeUITests の唯一の検証は app.state == .runningForeground
だった。ジェスチャーレコグナイザが1つも登録されていないビルドでも緑になり、
MapTiler のホスト切り出し回帰を取り逃がしていた。
実機(iPad Pro 11-inch)11プロバイダ:
maplibre maptiler mapkit googlemaps mapbox here tomtom arcgis arcgis2d mappls
→ all passed
longdo → failed(ポリゴン内タップで onPolygonClicked も onMapClick も来ない)
8c29b69^ でも同じく失敗するので既存不具合。別途調査する。
- Split ios-kml and ios-for-template out of this repository into their own
module repositories and register them in .gitmodules
- Split ios-for-openmobilemaps out as well: it was already registered in
.gitmodules but its contents were committed directly into this repository
- Register the ios-icons-{jp,us,weather} packs, which had their own
repositories but were not tracked here
- Bump every submodule to its release commit
- Point README docs links at the consolidated mapconductor.com site
- Register ios-for-template in scripts/api-surface.sh
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Every module now lives in its own repository and is registered in
.gitmodules.ios-kmlandios-for-templateout of this repository into their own module repositoriesios-for-openmobilemapsout as well: it was already registered in.gitmodulesbut its contents were committed directly into this repository, so the entry pointed at an empty repositoryios-icons-{jp,us,weather}packs, which already had repositories but were not tracked hereios-for-templateinscripts/api-surface.shso the scaffold is covered by the frozen-API gate.gitmodulesgoes from 16 to 21 entries.