Docs / 拡張レイヤー / GeoJSON Layer

GeoJSON レイヤー

GeoJSON をそのまま地図に重ねるための拡張パッケージです。フィーチャをタイルにラスタライズして描画するため、数万件規模でも1フィーチャ1オブジェクトを作らずに済みます。Android・iOS・React で API 名・スタイル既定値・ヒットテストの挙動を揃えてあります。

ANDROID
com.mapconductor:geojson
IOS
MapConductorGeoJSON
REACT
@mapconductor/react-geojson-layer
描画方式
ラスタタイル
プラットフォーム

01 · 概要

GeoJSON をパースして軽量なフィーチャモデルに変換し、MapConductor のラスタタイルパイプラインを通して描画します。プロバイダ(Google Maps・MapLibre・MapKit・HERE など)が何であっても、同じコード・同じ見た目になります。

01

パース

FeatureCollection・単体 Feature・素のジオメトリ、さらに RFC 8142 のテキストシーケンスに対応。ストリーミングパーサもあります。
02

タイル描画

フィーチャを 512px タイルにラスタライズし、ラスタレイヤーとして地図に載せます。プロバイダのベクタ機能に依存しません。
03

ヒットテスト

描画に使ったのと同じ座標でクリック判定します。穴あきポリゴン・マルチパート・ジオメトリコレクションに対応。
Point
MultiPoint
LineString
MultiLineString
Polygon
MultiPolygon
GeometryCollection
すべてのジオメトリ型を3プラットフォームで同一にサポートします。

02 · 基本の使い方

レイヤーは地図ビューの content スコープの中に置くだけです。パースはバックグラウンドで行い、結果を features に渡します。

build.gradle.kts
dependencies {
    implementation("com.mapconductor:geojson:<version>")
}
com.mapconductor.geojson · Compose
val mapViewState = rememberMapLibreMapViewState(
    cameraPosition = MapCameraPosition(
        position = GeoPoint.fromLongLat(139.7671, 35.6812),
        zoom = 12.0,
    ),
)

val layerState = remember { GeoJSONLayerState() }
var features by remember { mutableStateOf(emptyList<GeoJSONFeature>()) }

LaunchedEffect(Unit) {
    features = withContext(Dispatchers.IO) {
        assets.open("wards.geojson").use(GeoJSONParser::parseStream)
    }
}

MapLibreMapView(state = mapViewState) {
    GeoJSONLayer(state = layerState, features = features)
}
フィーチャの内容やスタイルが変わると、レイヤーは内部でタイル URL を無効化します。地図 SDK 側のラスタキャッシュが古い絵を返し続けることはありません。

03 · スタイルの決め方

スタイルは3層で解決されます

「レイヤー全体の既定値」「フィーチャごとの上書き」「StyleProvider による動的な決定」の3層です。下の層ほど強く、指定しなかった項目は上の層から引き継がれます。まずレイヤー既定値だけで始め、必要になった分だけ下の層を足していくのが基本の進め方です。

図 · スタイルの解決順
LAYER 1
レイヤー既定値
GeoJSONLayerState
すべてのフィーチャに適用される土台。strokeColor / fillColor / strokeWidth / pointRadius。
LAYER 2
フィーチャ単位の上書き
GeoJSONFeature.strokeColor …
個々のフィーチャが持つ同名のフィールド。null(nil)なら既定値がそのまま使われます。
LAYER 3
動的な決定
GeoJSONStyleProvider
フィーチャ1件ごとに呼ばれ、最終的なスタイルを返します。properties を見た色分けはここ。
StyleProvider を指定しない場合は DefaultGeoJSONStyleProvider が使われ、「フィーチャの値があればそれ、なければレイヤー既定値」という動きになります。つまり第3層は、第1層と第2層の関係そのものを差し替える仕組みです。
3-1. スタイルプロパティ

扱うプロパティは4つだけです。加えてレイヤー側には表示制御の opacity・visible・minZoom / maxZoom があります。

プロパティ
既定値
説明
strokeColor
ARGB Int / UIColor / number
#FF1E88E5
線と、ポリゴンの輪郭の色。
fillColor
ARGB Int / UIColor / number
#801E88E5
ポリゴンの塗りと、ポイントの円の色。既定は半透明です。
strokeWidth
Float / CGFloat / number
2.0
線幅(ピクセル)。タイルにラスタライズされるため、ズームしても太さは一定です。
pointRadius
Float / CGFloat / number
8.0
ポイントを描く円の半径(ピクセル)。
opacity
Float / Double / number
1.0
ラスタレイヤー全体の不透明度。個々の色のアルファとは別に効きます。
visible
Boolean
true
描画とヒットテストの両方から除外されます。フィーチャ側にも同名のフィールドがあります。
minZoom / maxZoom
Int / number
0 / 22
生成したラスタレイヤーを表示するズーム範囲。範囲外ではタイル生成も走りません。
3-2. レイヤー既定値を決める

まずここから始めます。GeoJSONLayerState に渡した値が、すべてのフィーチャの土台になります。状態は observable なので、あとから代入すれば再描画されます。

Kotlin · ARGB Int
val layerState = remember {
    GeoJSONLayerState(
        strokeColor = Color.argb(220, 30, 136, 229),
        fillColor   = Color.argb(60, 30, 136, 229),
        strokeWidth = 1.5f,
        pointRadius = 8f,
        opacity     = 1f,
        minZoom = 8, maxZoom = 22,
    )
}

// 状態は observable。あとから代入すればタイルが再生成されます
layerState.fillColor = Color.argb(90, 214, 64, 69)
色の指定形式
Android と React は ARGB の 32bit 整数(アルファが最上位バイト)、iOS は UIColor でアルファを色自身に持たせます。React には colorArgb(a,r,g,b) / colorRgb(r,g,b) / argbToCss() のヘルパーがあり、Android の Color.argb() と同じ並びです。3プラットフォームの既定色はいずれも #1E88E5(線は不透明、塗りはアルファ 128)で揃えてあります。
3-3. フィーチャごとに上書きする

フィーチャは strokeColor / fillColor / strokeWidth / pointRadius / visible を自分で持てます。null のままなら既定値、値が入っていればそちらが勝ちます。データを読み込んだ時点でスタイルが決まる(あとで変わらない)場合は、この方法がいちばん素直で速い方法です。

Kotlin · GeoJSONFeature.copy
val parsed = GeoJSONParser.parseStream(input)

// パース後にプロパティを見てスタイルを焼き込む
val styled = parsed.map { f ->
    when (f.properties["status"]) {
        "alert"  -> f.copy(fillColor = Color.argb(120, 214, 64, 69), strokeWidth = 3f)
        "closed" -> f.copy(visible = false)
        else     -> f   // null のままなのでレイヤー既定値が使われる
    }
}

GeoJSONLayer(state = layerState, features = styled)
3-4. StyleProvider で動的に決める

properties の値で色分けしたい、選択中のフィーチャだけ強調したい、しきい値をUIから変えたい ── といった「ルールで決まるスタイル」は StyleProvider に書きます。フィーチャ1件ごとに呼ばれ、レイヤー既定値を受け取って最終的なスタイルを返します。

渡されるもの
フィーチャ本体(properties を含む)と、その時点のレイヤー既定値。既定値を copy して一部だけ変えるのが定石です。
返すもの
4項目すべてが埋まった LayerStyle。触らない項目は既定値をそのまま返せば、第1層の設定が生きます。
Kotlin · GeoJSONStyleProviderInterface
// fun interface なのでラムダ1つで書けます
val densityStyle = GeoJSONStyleProviderInterface { feature, defaultStyle ->
    val pop = (feature.properties["population"] as? Number)?.toInt() ?: 0
    val fill = when {
        pop > 500_000 -> Color.argb(150, 173, 20, 87)
        pop > 200_000 -> Color.argb(120, 244, 143, 177)
        else          -> Color.argb(80, 248, 187, 208)
    }
    defaultStyle.copy(fillColor = fill)   // 触らない項目は既定値のまま
}

val layerState = remember {
    GeoJSONLayerState(styleProvider = densityStyle)
}

// あとから差し替えると全フィーチャが再評価されます
layerState.styleProvider = DefaultGeoJSONStyleProvider
StyleProvider を差し替える、または参照している状態が変わったときは、全フィーチャのスタイルが再評価されタイルが作り直されます。1件ごとに呼ばれるため、重い処理(正規表現・ネットワーク・日付パースなど)は provider の外で前計算しておいてください。
3-5. どれを使うか
方法
向いている場面
備考
LayerState
データ全体を1つの見た目で描く。
いちばん軽い。まずはここから。
Feature override
読み込み時にスタイルが確定していて、以後変わらない。
パース結果を map するだけ。provider 呼び出しのコストがない。
StyleProvider
ルールでスタイルが決まる。ルール自体が実行中に変わる。
ロジックを1か所にまとめられる。RN ではネイティブ登録が必要。

04 · タップ判定

MapConductor のクリックリスナーは1つしか無いため、レイヤーへの転送はアプリ側で行います。意図的に自動登録していません。processClick は、フィーチャに当たったときだけ true を返します。

Kotlin
val layerState = remember {
    GeoJSONLayerState(
        onClick = { feature, position -> selected = feature },
    )
}

MapLibreMapView(
    state = mapViewState,
    onMapClick = { point ->
        // 15px 相当の許容範囲で判定(ズームに追従)
        val consumed = layerState.processClick(point, 15.0, mapViewState.zoom)
        if (!consumed) selected = null
    },
) {
    GeoJSONLayer(state = layerState, features = features)
}
ピクセル許容範囲
processClick に許容ピクセルと現在のズームを渡すと、ズームに追従した判定になります。省略時は世界座標の既定許容値(約0.0002°)です。
重なったとき
最後に描画された(=いちばん上の)フィーチャが返ります。
対応ジオメトリ
ポイント・ライン・穴あきポリゴン・マルチパート・ジオメトリコレクション。

05 · データ量と読み込み

大きなデータではストリーミングパーサを使い、バックグラウンドでパースしてください。フィーチャの持ち方も2通りあります。

GeoJSONFeature
静的・大量向け
不変のデータオブジェクト。数万件でも状態オブジェクトを作らないため軽量です。大きな GeoJSON はこちら。
GeoJSONFeatureState
少数・頻繁に変わる向け
1件ずつリアクティブに更新できます。件数が多いと状態管理のコストが効いてくるため、必要な分だけに。
Kotlin · streaming
// 大きな FeatureCollection は parseStream
val features = withContext(Dispatchers.IO) {
    GeoJSONParser.parseStream(input)
}

// RFC 8142 の GeoJSON Text Sequences
val seq = withContext(Dispatchers.IO) { GeoJSONSeqParser.parse(file) }
GeoJSONSeqParser.streamParse(file) { feature -> buffer.add(feature) }
動画で見せる実装 · Android + MapLibreKotlin · Jetpack Compose
val layerState = remember { GeoJSONLayerState() }
var features by remember { mutableStateOf(emptyList<GeoJSONFeature>()) }
var loading by remember { mutableStateOf(true) }

// parseStream は GeoJSONFeature(不変)を返す。数万件でも状態オブジェクトを作らない
LaunchedEffect(Unit) {
    features = withContext(Dispatchers.IO) {
        context.assets.open("tokyo-buildings.geojson")
            .use(GeoJSONParser::parseStream)
    }
    loading = false
}

MapLibreMapView(state = mapViewState) {
    GeoJSONLayer(state = layerState, features = features)
}

if (loading) {
    LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
}
サンプル動画 · 大きな GeoJSON をタイルで読み込む
動画 未撮影スクロールに追随してタイルが順に読み込まれ、描画が埋まっていく様子。データ量が増えても操作が止まらないことは、静止画では伝わりません。

06 · 現在の制限

レイヤーへのクリックリスナー自動登録は行いません。processClick に転送してください(意図的な設計です)。
ヒットテストの線・点の既定許容値は内部定数です。呼び出しごとにピクセル許容値を渡すことで調整できます。
ラスタタイルとして描画するため、地図 SDK ネイティブのベクタフィーチャクエリは使いません。
パーサは不正な入力に対して例外を投げず、空のフィーチャ配列を返します(iOS)。

関連ページ