GeoJSON の細かいルール

## はじめに
ウェブ地図を使っていると身近な存在の GeoJSON ですが、仕様を見ていると細かい部分で知らないことが結構あったので、自分用にメモをします。基本的なところは省いて、普段あまり意識していなかったなというところだけつまみ食いです。

https://datatracker.ietf.org/doc/html/rfc7946

GeoJSON の基本的なルールについては、Wikipedia ですっきりまとまっています。

https://ja.wikipedia.org/wiki/GeoJSON

:::note warn
本記事では、仕様を直訳したわけではなく、かなり自分なりにかみ砕いて抜粋しているので、気になるところは実際の仕様も確認いただければ幸いです。
:::

:::note warn
なお、調べたきっかけは、「MultiPolygon に含まれる複数の Polygon どうしの重なりは許されるのか？」を知りたかったのですが、明示されていませんでした。禁止されていないということは、許されると解釈しています。
:::

## 高さの要素

[3.1.1.  Position](https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.1)

GeoJSON の位置の要素は2つ以上で構成されなければなりません（MUST）。必須なのは経度と緯度ですが、3つ目に高度や標高を含めることができます（MAY）。4つ以上の要素へ拡張すべきではなく（SHOULD NOT）、パースする時は4つ目以降は無視して良い（MAY）とされています。

## 頂点どうしをつなぐ線

[3.1.1.  Position](https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.1)

2つの頂点をつなぐ線は、その座標参照系（後述しますが、通常は OGC:CRS84）上の最短距離の直線で示す、とされています。仕様では、以下の式が示されており、(lon0, lat0) と (lon1, lat1) を結ぶ線上の点は、こちらの式を使って表せるとされています。

```
F(lon, lat) = (lon0 + (lon1 - lon0) * t, lat0 + (lat1 - lat0) * t)
※ t は、0以上1以下の実数
```

そのため、準拠楕円体上の測地線とは著しく異なる可能性がある旨が記載されています。

> Note that this line may markedly differ from the geodesic path along the curved surface of the reference ellipsoid.

つまり、ベジェ曲線や大圏航路で結ぶものではないということです。そのように表示したい場合は、補間点が必要になりそうですね。

### ソフトウェアにおける挙動

一方、ソフトウェアによっては、2点を **表示に利用している座標参照系上** の直線で結ぶ挙動が存在しているようです。

特に、GeoJSON はウェブ地図でよく利用されていますが、ウェブ地図ではWeb メルカトルを採用していることが多いです。確認したところ、主要なウェブ地図ライブラリ（Mapbox GL JS/MapLibre GL JS、Leaflet、OpenLayers 等）では、**GeoJSON の２点を結ぶ線は Web メルカトル上の最短距離で表示されてしまいます**ので、実務上、頭に入れておいた方が良いかもしれません。

いずれにせよ、明確に直線の形状を指定したい場合は、補間点（LineString や Polygon の頂点）を増やすことになりそうですね。

例となりますが、以下のデータ（補間点ありとなしの２つの LineString）を QGIS で表示してみると、OGC:CRS84 と EPSG 3857（Webメルカトル）で差があることが分かります。

:::note
上述の (lon0, lat0) と (lon1, lat1) を結ぶ線上の点を示す式に従い、(lon0, lat0) = (120, 20)、 (lon1, lat1) = (150, 50)、t = 0.5 の条件から、補間点 (135, 35) を計算しています。
:::


```
{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {
        "補間点": "なし",
      },
      "geometry": {
        "type": "LineString",
        "coordinates": [
          [120, 20], [150, 50]
        ]
      }
    },
    {
      "type": "Feature",
      "properties": {
        "補間点": "あり",
      },
      "geometry": {
        "type": "LineString",
        "coordinates": [
          [120, 20], [135, 35], [150, 50]
        ]
      }
    }
  ]
}
```

OGC:CRS84 の場合

![CRS84.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842832/9c9aba3a-c846-dce6-8f25-7075bd98f478.png)

EPSG 3857（Webメルカトル）の場合

![EPSG3857.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842832/d5eb5a64-1285-0c88-b78e-0905baf0d4c6.png)

以下は MapLire GL JS で表示した様子ですが、QGIS で EPSG 3857（Webメルカトル）の場合と同じように描画されます。（赤が補間点ありです。）

![maplibre.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842832/636a6d91-7899-766e-58e6-fd9d8777244b.png)


## 穴あきポリゴン

[3.1.6.  Polygon](https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.6)

穴あきポリゴンを表現する場合、ポリゴンの coordinates へ複数のリング（閉じたLineString ）を設定します。最初のリングがポリゴンの外周であり、その他のリングは穴となります（MUST）。外周リングは反時計回り、穴となるリングは時計回りでなければなりません（MUST）。

ただし、後方互換性のため、パーサはこのルールに従っていない GeoJSON でも拒否すべきではない（SHOULD NOT）とされています。

## 経度180度線を跨ぐ

[3.1.9.  Antimeridian Cutting](https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.9)

LineString や Polygon が経度180度の線を跨ぐ場合、経度180度で分割して、MultiLineString や MultiPolygon とすべき（SHOULD）とされています。

確かに、東経175度と西経175度の２点を結ぶような場合、－180～180度の範囲内ということで本初子午線を越えるのか、最短距離となるように経度180度を越えるのか、混乱してしまいますね。

## 座標参照系

[4.  Coordinate Reference System](https://datatracker.ietf.org/doc/html/rfc7946#section-4)

GeoJSON の座標参照系は OGC:CRS84 を用いた地理座標系です。地理座標系では、経度・緯度で位置を示します。

また、もし、3つ目の位置要素（高さ）がある場合、それは WGS84 準拠楕円体からの高さ（メートル）としなければならない（SHALL）とされています。なお、もし3つ目の要素が存在しない場合、その地点の地表面か海水面とすべき（SHOULD）とされています。

相互運用性のため、座標参照系は OGC:CRS84 とされていますが、事前の取り決めがあれば、他の座標系を使用できることも記載されています。

:::note
なお、OGC:CRS84 とは別に EPSG:4326（WGS 84）というものがあります。両者の違いは、EPSG:4326 では、位置を「緯度、経度」の順で記述するのに対して、OGC:CRS84 では、「経度、緯度」の順で記述します。
Leaflet や Mapbox GL JS（派生先の MapLibre GL JS）などのウェブ地図ライブラリでも、「経度、緯度」の順で記述しています。（参考：[Mapbox の解説](https://docs.mapbox.com/help/glossary/projection/)）
なお、地理院地図（検索機能や下部のコンテキストメニュー）では、「緯度、経度」の順を採用しているようです。
:::

## メンバーの追加

[6.  Extending GeoJSON](https://datatracker.ietf.org/doc/html/rfc7946#section-6)

GeoJSON には、仕様には記載されていない JSON の要素（メンバー）を追加することができます（MAY）。仕様には、Feature に `title` というメンバを追加する例が示されています。

[7.  GeoJSON Types Are Not Extensible](https://datatracker.ietf.org/doc/html/rfc7946#section-7)

ただし、GeoJSON の 仕様で定められた type やメンバーの拡張は認められていません（MUST NOT）。

## 終わりに

自分は、ライブラリ等を使わずに JavaScript 等で直接 GeoJSON を操作することが多くありますが、厳密に記述・解釈しようとするとエラーチェックも含めて結構手間がかかりそうです。

GeoJSON でも、[ロバストネス原則](https://ja.wikipedia.org/wiki/%E3%83%AD%E3%83%90%E3%82%B9%E3%83%88%E3%83%8D%E3%82%B9%E5%8E%9F%E5%89%87)を意識するのが大事かもしれません。

> 「貴方が自分ですることに関しては厳密に、貴方が他人から受けることに関しては寛容に」(be conservative in what you do, be liberal in what you accept from others)

ただし、自分の経験では、

* MultiPolygon や 穴あきポリゴンを作成する機会にはあまり出会わなかった（解釈しなければならないことはそれなりにありましたが……）。
* 日本周辺のデータばかり扱っている。
* 精度に応じて補間点が確保されている場合が多い。
* 3次元データを GeoJSON で取り扱うことは少ない。

というような状況で、ここで書いたことを意識しなくても、問題なかったことが多いです。だからこそ、普段あまり意識していなかったのですが。