MapConductor は、地図SDKを 1 枚の共通 API で扱うためのオープンソースの SDK です。Android(Jetpack Compose)・iOS(SwiftUI)・React(TypeScript)向けに提供していて、いまは 15 のプロバイダに対応しています。ライセンスは Apache-2.0 です。
最初の記事なので、これが何をするものなのか、そしてどんなときに役に立つのかを、あらためて書いておきます。
地図SDKは、どれもよくできている
Google Maps、Mapbox、HERE、ArcGIS、MapLibre、MapTiler、TomTom、Longdo、Mappls ── 地図SDKはどれも長い時間をかけて磨かれていて、それぞれに得意なところがあります。道路や住所の詳しさ、ナビゲーション、GIS 資産との接続、デザインの自由度、対応地域。「どれがいちばん良いか」という問いに一般解はなく、作るアプリと届ける地域によって答えが変わります。
にもかかわらず、実装の側から見ると、その選択は最初の数行でほぼ確定します。地図を出すコードは特定の SDK の型と概念に深く結びつくので、あとから比べたくなったときには、比べる手段のほうが残っていません。
これは各社の設計が悪いという話ではありません。むしろ各社が自分の得意分野に合わせて丁寧に設計してきた結果として、概念も命名も揃っていない、というだけのことです。揃っていないものを揃えるのは、SDK を提供する側ではなく、その上で書く側の仕事になります。MapConductor はその 1 枚を引き受けます。
何をするものか
アプリは MapConductor の統一 API にだけ依存します。プロバイダごとの差はドライバーが吸収するので、プロバイダを変えるときに書き換えるのは、依存モジュール・地図ビューの型・状態オブジェクトの型の 3 つだけです。マーカーや図形、カメラ操作、イベントの書き方は変わりません。
// オーバーレイの宣言は分岐の外に置く。プロバイダを差し替えても書き直さない
val overlays: @Composable MapViewScope.() -> Unit = {
Marker(markerState)
Polyline(routeState)
}
if (useMapLibre) {
MapLibreMapView(state = maplibre, content = overlays)
} else {
GoogleMapView(state = googlemaps, content = overlays)
}同じ画面に 2 つ並べて、実行時に切り替えることもできます。切り替える側へ直前のカメラを引き継げば、見た目のジャンプにもなりません。
揃うのは、書き方だけではない
「共通の API」と聞いたときにいちばん心配になるのは、書き方は揃っても結果が揃わないことだと思います。ズーム 14 の見え方が環境によって違う、2 点間の距離が Android と iOS で一致しない ── これは実際に起きます。
MapConductor は、そこを SDK 側で持っています。
| 自前で持っているもの | そのおかげで揃うこと |
|---|---|
| 地理計算 | 距離・方位・面積・補間を Kotlin / Swift / TypeScript で同じ式で実装。SDK のユーティリティに依存しないので、表示される数値がプラットフォームでずれません |
| カメラ | ズームとカメラ高度を相互変換し、実測にもとづいて校正しています。zoom 14 はどのプロバイダでも同じ広さです |
| マーカー描画 | 少数ならネイティブマーカー、2,000 個を超えると内部でラスタータイルへ自動で切り替え。数万件でもカメラ操作は滑らかなままで、アプリ側のコードは 1 行も変わりません |
| 図形 | 穴あきポリゴンや大圏コースなど、ネイティブに機能がないプロバイダではラスタータイルとして描いて、同じ見た目にします |
| 出典表記 | ズームや表示範囲に応じて出典を切り替えるルールを、地図デザインの一部として持ちます |
各 SDK のメソッドを呼び分けるだけのラッパーだと、できることが「いちばん機能の少ない SDK」に揃ってしまいます。足りないところは自分で描いて埋める、という方針をとっているのはそのためです。
何の役に立つのか
覚える API が 1 つで済む
地図SDKはベンダーごとに概念も命名も違うので、プロバイダを変えるたび、プラットフォームを移るたびに覚え直しが起きます。MapConductor で覚えるのは 1 つの API だけで、Android・iOS・React のどこでも同じ名前・同じ意味です。Android で得た知見はそのまま iOS で通用しますし、チームに人が入るときに読む対象がプロバイダの数だけ増えることもありません。
地図SDKの名前が出てこないコードが書ける
GeoPoint や MarkerState は、どの地図SDKにも属さない型です。経路を組み立てる、範囲に入るかを判定する、何を表示するかを決める ── こうしたロジックを SDK の型から切り離して書けます。地図まわりの共通部品を、プロバイダごとに作り分けずにライブラリとして切り出すこともできます。
仕様の議論が 1 回で済む
Android・iOS・Web でそれぞれ別の地図SDKを使っていると、マーカーの挙動もカメラの数値の意味も揃わず、同じ機能の仕様を 3 回相談することになります。共通のモデルがあると、その 3 回が 1 回になります。
「選べる」という状態をつくりたい
ここまでは地図を組み込む側の話です。MapConductor がやりたいのは、それだけではありません。
いまは、地図SDKの選択が実装の最初のコミットになっています。そうすると選定は「試して決める」ではなく「決めてから作る」になり、比較は机上で終わります。これは選ばれる側にとっても、あまり良い状態ではありません。良い SDK が、品質ではなく「すでに別の SDK で書いてしまったから」という理由で候補から外れてしまうからです。
共通 API があると、この順番が変わります。実際に動かして、同じアプリの同じ画面で見比べて、そのうえで決められる。決め手になるのは、価格・対応地域・データの新しさ・描画の品質 ── つまり各社が本来競っているところです。
だから MapConductor は、各社の強みを覆い隠さない作りにしています。共通化しているのは、どのプロバイダにもある基本的な機能だけです。状態オブジェクトの getMapViewHolder() を呼べばネイティブの地図ビューと地図インスタンスがそのまま取り出せるので、そのプロバイダにしかない機能は各社の API で直接書けます。全部を覆い隠すラッパーであれば差別化要因が消えますが、そうはしていません。「共通部分は MapConductor で、勝負どころは各社の API で」という併用が普通にできます。
ブラウザが HTML5 で実装を揃えたあと、各ブラウザは互換性を保つこと自体ではなく、速度や機能そのもので競うようになりました。地図SDKも同じで、共通のインタフェースが定まれば、その上でどれだけ速く・正確に・広い地域を描けるかが問われます。共通化は個々の SDK の進化を止めるものではなく、進化の向きを揃えるものだと考えています。
もうひとつ、これは今後の方向の話です。地図データやジオコーディングは、それを提供するベンダーの地図SDKとセットで使うのが前提でした。表示とデータを切り離せれば、自前の表示手段を持たない提供者 ── ジオコーディングだけ、特定分野のデータだけを持つところ ── にも、使ってもらえる場面が増えます。現時点でこの用途に向けた機能はまだありません。方向であって、機能一覧ではない、と書いておきます。
なお、データやサービスの利用規約に従うのは、それを組み込む開発者の責務です。技術的に組み合わせられることと、その組み合わせが許諾されていることは、別の話です。
向いている場合、向いていない場合
向いているのは、こういうときです。
- 複数プラットフォームで同じ地図体験を出したい
- 将来プロバイダを変える可能性を残しておきたい
- マーカー・図形・カメラといった標準的な地図表現が中心
- 数千〜数万のマーカーを扱う
- 距離や面積の表示に一貫性が求められる
逆に、こういうときは向いていません。
- 特定プロバイダ固有の高度な表現が主役(独自シェーダー、ベンダー専用レイヤー)
- ターンバイターンのナビゲーションなど、SDK の専用機能そのものが目的
- 1 プラットフォーム・1 プロバイダで完結し、今後も変える予定がない
ただし、全部か無かではありません。ネイティブの地図インスタンスには常に手が届くので、特殊な部分だけプロバイダ固有のコードで書く、という構成ができます。
はじめかた
MapLibre なら API キーなしで動くので、最初の地図が出るまでは 5 分ほどです。
- はじめる — MapLibre で最初の地図を出すまで
- なぜ MapConductor か — この記事のもとになっている考え方
- アーキテクチャ — 統一 API・Core・ドライバーの分かれ方
- 対応プロバイダとセットアップ — プラットフォーム別の導入手順
開発の進捗やリリースは Discord で公開しています。設計への意見も、不具合の報告も歓迎です。コードは GitHub にあります。
