PLATEAUの3D建物データを用途別に色分けして地図表示する

## PLATEAUの3D建物をMapboxで手軽に可視化する

こちらは[toggle engineer blog](https://engineer-blog.toggle.co.jp/)で2024年12月6日(金)にポストした記事です。

トグルホールディングスのグループ会社つくるAIのmapチームのエンジニアの火原です。
今回は、国土交通省が提供する3D都市モデルデータ「PLATEAU」を使って、Web上で3D地図を作成する方法をご紹介します。


ポイントは、従来のUnityやUnreal Engineではなく、**Mapboxを使用したアプローチ**です。これにより、より手軽で低コストな実装が可能になります。

## なぜMapboxなのか？

従来のPLATEAU可視化では、以下のような課題がありました：

* Unityの場合、WebGLビルドの容量が大きい
* Unreal Engineの場合、サーバーリソースが必要
* どちらも開発コストと実行環境の要件が高い
* 私はUnityもUnreal Engineも使ったことがないので、私にとって学習コストが高い

一方、Mapboxを使用すると：

* ブラウザだけで動作（追加のプラグイン不要）
* 軽量で高速な表示が可能
* Reactで実装可能

## 実装例
今回は、PLATEAUの建物用途に応じて建物を色塗りした地図を作成します。

* 地図の範囲は東京都23区、川崎市、横浜市にしてみました。
* マウスを建物の上に置くと、建物の情報が追加されるホバー機能もつけてみました。
* mapboxの地図を使うので、mapboxのアクセストークンが必要になりますが、無料の範囲でもたっぷり使えます。

### 1. 基本的なセットアップ
mapboxの地図に、PLATEAUの建物のレイヤーを追加するシンプルなモデルはこちらです。

初期ビュー
![スクリーンショット 2024-12-01 18.19.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3949642/c5941681-1e69-3e8e-e22f-80a582aa7e1a.png)

レイヤー作成にはDeckGLを使います。  
PLATEAUのCityGMLデータをmapbox向けのMVTタイル形式に変換して読み込むことで、サクサク描画するようにしています。  
```typescript
// App.tsx
import 'mapbox-gl/dist/mapbox-gl.css'
import Map from 'react-map-gl'
import DeckGL from '@deck.gl/react/typed'

const MAPBOX_TOKEN = 'YOUR_MAPBOX_TOKEN'
# 初期視点は東京駅に設定しました
const INITIAL_VIEW_STATE = {
  longitude: 139.7670,
  latitude: 35.6810,
  zoom: 14,
  pitch: 45,
  bearing: 0
}

export default function App() {
  const [viewState, setViewState] = useState(INITIAL_VIEW_STATE)
  const plateauLayer = usePlateauBuildingLayer('plateau-buildings')

  return (
    <DeckGL
      viewState={viewState}
      onViewStateChange={({ viewState }) => setViewState(viewState)}
      layers={[plateauLayer]}
      controller={true}
      getTooltip={({object}) => object && ({
        html: formatTooltip(object.properties)
      })}
    >
      <Map
        mapboxAccessToken={MAPBOX_TOKEN}
        mapStyle="mapbox://styles/mapbox/light-v10"
      />
    </DeckGL>
  )
}
```

### 2. 建物用途による色分け

* 東京都23区は、2023年版で、建物のプロパティの'buildingDetailAttribute.0.detailedUsage'を参照して、建物用途の種類を決めて色を決めます。
* 川崎市は、2022年版で、建物のプロパティの'usage.0'を参照して、建物用途の種類を決めて色を決めます。
* 横浜市は、2023年版で、建物のプロパティには用途が記述されていないので、デフォルトで全て灰色で描画します。  

legendはこんな感じにしました。  
3D建物レイヤーを表示しないプレーンなMapbox地図の様子もご覧ください

Legend表示かつ3D建物レイヤーなしのMapbox
![スクリーンショット 2024-12-01 18.23.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3949642/7e500a1a-b797-d73d-0196-70f0d0f9d697.png)


* 建物用途の種類が多いので、グループ分けをして、グループごとに色を決めました。

```typescript
const getBuildingUsage = (properties: BuildingProperties): string | undefined => {
  // 23区と川崎市で異なる属性名に対応
  if (properties['buildingDetailAttribute.0.detailedUsage']) {
    return properties['buildingDetailAttribute.0.detailedUsage']
  }
  if (properties['usage.0']) {
    return properties['usage.0']
  }
  return undefined
}
```

* 色設定は用途ごとにグループ化して管理：

```typescript
export const PLATEAU_USEING_COLORS = {
  // 商業系は赤系統
  商業施設: [235, 85, 85, 200],
  事務所建築物: [235, 145, 145, 200],
  
  // 公共系は青系統
  医療施設: [65, 105, 225, 200],
  教育施設: [85, 120, 225, 200],
  
  // 住宅系は緑系統
  独立住宅: [95, 185, 95, 200],
  集合住宅: [115, 185, 115, 200]
  // ...
}
```

### 3. 災害リスク情報の表示
建物の保持している情報には、災害リスク情報があります。  
東京都23区、川崎市、横浜市の今回の対象エリアは、全て、災害リスク情報は保持していました。  
建物によって、災害リスクがなしのものも、複数のものもあり、災害リスク情報の件数が決まっていないことを前提に対応します。  

* 建物ごとの複数の災害リスク情報を効率的に取得：

```typescript
const getAllDisasterRisks = (properties: BuildingProperties): DisasterRisk[] => {
  const risks: DisasterRisk[] = []
  let index = 0

  while (true) {
    const description = properties[`buildingDisasterRiskAttribute.${index}.description`]
    if (!description) break

    risks.push({
      description,
      rank: properties[`buildingDisasterRiskAttribute.${index}.rank`],
      depth: properties[`buildingDisasterRiskAttribute.${index}.depth`],
      scale: properties[`buildingDisasterRiskAttribute.${index}.scale`]
    })
    index++
  }

  return risks
}
```

横浜市、川崎市、東京23区の対比と災害リスク情報
![スクリーンショット 2024-12-01 18.24.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3949642/21b0a88e-205a-3b4e-bf5f-85829b3accc0.png)


## PLATEAUデータの準備と注意点
### データ変換時のプロセス
PLATEAUデータをWeb表示用に変換する際は、以下のステップが必要です：

1. CityGML → GeoJSON変換
2. GeoJSON → MVT（Mapbox Vector Tile）変換

この変換プロセスでは、特に以下の点に注意が必要です：

- 高いメモリ要求
  - GeoJSON変換時に大量のメモリを消費
  - 32GB以上のメモリを搭載したマシンでの作業を推奨
  - MVT形式はタイルを作成するので、隣接区域はまとめて処理する必要があります
- データサイズ
  - テクスチャを除いた建物データのみでも、MVTファイルは約2.4GB

```bash
$ du -sh public/mvt 
> 2.4G    public/mvt
```


* データを以下の形式で置くだけで、サーバーを立てるような手間なく、建物が描画されます。  
`public/mvt/{z}/{x}/{y}.pbf`. 
* ここがMVTタイル形式のメリットだと感じました。

CityGMLからMVTへの変換に関するtipsは、ニーズが高いようでしたら、別の機会に書かせていただきますね。

## まとめ

PLATEAUの3D都市モデルデータをMapboxで表示することで、以下のメリットが得られました：

1. 開発期間の短縮
   - 一般的なWebフレームワーク(React)が使用可能
   - 豊富なサンプルコードやライブラリが利用可能(DeckGLのMVTレイヤーは事例豊富です)

2. 運用コストの削減
   - ReactプロジェクトのpublicフォルダにPLATEAUの建物データを置くだけで済みます

3. メンテナンス性の向上
   - Webの標準技術での実装
   - データの動的更新が容易
        - 描画エリアの拡大も、PLATEAUデータを変換して、publicに置くだけです

従来のゲームエンジンを使用した実装と比べ、より手軽にPLATEAUデータの可視化が実現できます。  
ぜひ皆さんもMapboxでの実装を検討してみてください！

なお、私は10月からTypeScript, Reactを始めたばかりで、それまではPython中心にコーディングをしてきたので、TypeScriptのルールやマナーを知らず色々と苦労しましたが、このような地図が作成できました。  
豊富なデータを提供してくださるPLATEAUプロジェクト、どんどん機能が拡充しているMapboxに、とてもとても感謝しております。  
地図の沼へ、一緒にハマりましょう！

## 参考リンク
- PLATEAU：https://www.mlit.go.jp/plateau/
- Mapbox GL JS：https://docs.mapbox.com/mapbox-gl-js/
- deck.gl：https://deck.gl/