OSMから建物データをエクスポートし、ベクトルタイルを使用してMapLibre GL JSで3次元表示

## 0．はじめに
OSM（オープンストリートマップ）に含まれる建物データには高さデータも入っている場合があることを知り、それをMapLibre GL JSを用いて3次元表示してみたいと考え作業をしました。思考錯誤しながら作業しましたので、詰まった点など参考になれば嬉しいです。

### 作業環境
Windows 10 Home
QGIS  3.22.1
Docker  4.15.0 (93002)
Powershell  5.1.19041.4291

## １．OSMから建物データをエクスポート
まずは建物データをエクスポートしていきます。エクスポートする場所は建物データが高さ属性を持っている東京都東村山駅周辺としました。以下の図のように①エクスポート、②エクスポートを押すと、map.osmとしてデータがダウンロードされます。ダウンロード範囲は画面に表示されている範囲となります。私の場合、データサイズは2,321KBでした。

![02_OSM東村山駅周辺エクスポート.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3684675/9141d4f8-b9df-d8de-5ade-891bd5a94b82.jpeg)


## ２．QGISでSHPファイルに変換
ダウンロードしたデータをQGISにドラッグ＆ドロップして表示します。![QGIS東村山駅周辺.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3684675/4b05ce93-769a-4969-410a-6b1c1d803e5f.jpeg)

ここで建物の属性情報を見てみると、高さデータがother_tags属性の中にさらにネスト構造として含まれていることが分かります。
![QGIS属性.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3684675/11857c74-8ad2-6a69-05e9-e48be1591ba8.jpeg)

このままだと高さデータが扱いにくいので、QGISの「HStoreフィールドを展開」ツールを使用して、高さデータをother_tags属性の外に出しましょう。①プロセシング > ツールボックス> ベクターテーブル> ②HStoreフィールドを展開 を使用すれば、other_tagsの中の属性を外に出せます。③入力レイヤは建物データ、④HStoreフィールドは「other_tags」、⑤出力レイヤはshpファイル形式としました。保存したファイル名は「building.shp」としています。

![HStore_edit.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3684675/bbaec4f7-c71f-f498-70e7-30441948aed7.jpeg)

新しく作成したshpファイルの属性を見てみると、きちんと高さデータが通常の属性として追加されています。他の属性で文字化けしていますが、ここでは気にせず進めていきます。
![QGIS属性抽出後edit.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3684675/6ed04789-e8c3-ca9a-66b2-26ed08a022ad.jpeg)

## ３．SHPファイルからGeoJSON形式に変換(GDAL)
ここからは、以下の記事を参考にして、shpファイルからGeoJSONファイルに変換します。

https://qiita.com/T-ubu/items/76a60bfd4a27d11521e2

### Docker （unvt/nanban）の開始
shpファイルを保存したディレクトリで以下のコマンドを実行します。

```
docker pull unvt/nanban
docker run -it --rm -v ${PWD}:/data unvt/nanban
```

Dockerが立ち上がったら、/dataディレクトリに移動します。ここには、先ほど作成したshpファイルが保存されています。ここで、/dataディレクトリへの移動も含めて以下のコマンドを実行します。

```
cd /data
ogr2ogr -f GeoJSON building.geojson building.shp
```

コマンド実行後、ディレクトリの中身を見てみるとbuilding.geojsonが作成されているはずです。

## ４．GeoJSONからベクトルタイルに変換（Tippecanoe）
次にgeojsonファイルからベクトルタイルに変換します。以下のようにコマンドを実行するとVTPracticeというフォルダにベクトルタイルがpbf形式で作成されるはずです。オプションの説明等は先ほどのリンクから参照してください。

```
tippecanoe -e VTpractice --no-tile-compression --no-feature-limit --no-tile-size-limit --drop-rate=1 -Z10 -z17 -L building:building.geojson
```
しかし、今回は以下のようなエラーが発生しました。
![ベクトルタイル作成時の文字化けエラー.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3684675/b9f58873-4039-6267-90de-e51113da1b27.jpeg)

なぜエラーが出たのか、よく分からないですね。。おそらく、geojson形式がきちんと整形されていないからだろうと思い、[geojson.io](https://geojson.io/)というサイトにいき、そこでgeojsonをインポートしてそれをそのままエクスポートしてみました。エクスポートする時には、SaveからGeoJSONボタンを押すと自動的にgeojsonファイルがダウンロードされます。
![geojsonio_edit.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3684675/a41074e5-edaa-00ae-f626-58ebdd2de466.jpeg)

ダウンロードしたgeojsonファイルをTippecanoeを実行するディレクトリに移動させて、以前のgeojsonファイルの容量と比べると、1,714KBから1,495KBに減っています。不必要なデータが消去されたのかもしれません。エクスポートされたgeojsonファイル名を「buildingio.geojson」として、もう一度Tippecanoeを実行します。今度は成功しました。
![ベクトルタイル作成時成功.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3684675/a84352e2-9d51-b6b4-f09e-657202b93ea6.jpeg)

ベクトルタイルがVTpracticeディレクトリに作成されていることを確認したら、exitでDockerを終了しましょう。

## ５．MapLibre GL JSで3次元表示
最後にMapLibre GL JSでベクトルタイルを３次元表示します。以下のコードで表示できます。

```html:index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <title>Display 3D buildings</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link
      rel="stylesheet"
      href="https://unpkg.com/maplibre-gl@4.1.3/dist/maplibre-gl.css"
    />
    <script src="https://unpkg.com/maplibre-gl@4.1.3/dist/maplibre-gl.js"></script>
    <link href="style.css" rel="stylesheet" />
  </head>

  <body>
    <div id="map"></div>
    <script src="main.js"></script>
  </body>
</html>
```


```css:sytle.css
body {
  margin: 0;
  padding: 0;
}
html,
body,
#map {
  height: 100%;
}
```


```javascript:main.js
const style = {
  version: 8,
  sources: {
    gsitile: {
      type: "raster",
      tiles: ["https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png"],
      tileSize: 256,
      attribution:
        "地図の出典：<a href='https://www.gsi.go.jp/' target='_blank'>地理院タイル</a>",
    },
    osmbuilding: {
      type: "vector",
      tiles: [
         // タイルのURLは自身の環境に合わせて変更してください。
        "https://k96mz.github.io/20240423building3Dqiita/VTpractice/{z}/{x}/{y}.pbf",
      ],
      minzoom: 10,
      maxzoom: 20,
      attribution:
        "©<a href='https://www.openstreetmap.org/copyright' target='_blank'> OpenStreetMap:</a> contributors",
    },
  },
  layers: [
    {
      id: "gsitile",
      type: "raster",
      source: "gsitile",
      minzoom: 5,
      maxzoom: 20,
    },
    {
      id: "building",
      type: "fill-extrusion",
      source: "osmbuilding",
      "source-layer": "building",
      minzoom: 10,
      maxzoom: 20,
      paint: {
        "fill-extrusion-color": "#797979",
        "fill-extrusion-height": ["get", "height"],
      },
    },
  ],
};

const map = new maplibregl.Map({
  container: "map",
  style: style, // 地図のスタイル
  center: [139.466895, 35.760381], // 中心座標
  zoom: 16.77, // ズームレベル
  bearing: -30.1, // 方位
  pitch: 50, // 傾き
  hash: true, //緯度経度表示
});

//UI
map.addControl(new maplibregl.NavigationControl(), "bottom-right"); // ナビゲーションボタン表示
map.addControl(new maplibregl.ScaleControl()); // スケールバー表示

//debug
map.showTileBoundaries = false;
map.showCollisionBoxes = false;
```

建物データをMapLibre GL JSで３次元表示出来ました。
![Maplibre.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3684675/1af07e09-dbd8-e4c6-98fc-112370578d20.jpeg)


## その他
### Overpass turboを用いて建物データをエクスポート
OSMから建物データをエクスポートする際には、overpass APIも使用できます。[overpass API](https://overpass-turbo.eu/)から「Wizard」をクリックします。

![overpassedit.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3684675/57dd6120-0c9b-fac3-beda-f9b5abed22bc.jpeg)

その後、①「~building~".*"」として②「build and query」を実行すると建物データを抽出できます。

![overpassWizardedit.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3684675/9e6ac5a6-e29e-23a4-20e4-8fcb6b39da66.jpeg)

次に「Export」ボタンを押して、GeoJSONのdownloadボタンをクリックするとGeoJSONデータをダウンロード出来ます。
![overpassExportedit.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3684675/1e14b6c5-afb8-3650-2bf3-00f6760c9fbe.jpeg)

この後の流れは同様です。こちらのgeojsonファイルはgeojson.ioで整形する必要ありませんでしたので、こちらの方法がよりベターかもしれません。

## まとめ
OSMデータから建物データをエクスポートして、それをSHP→GeoJSON→ベクトルタイルと変換し、MapLibre GL JSで3次元表示しました。３次元は情報を分かりやすく表示出来るのがいいですね。ただ、OSMに入っている高さデータの精度は担保されていない点に注意が必要です。
記事に間違いやコメント等あれば、お知らせいただけると嬉しいです。

## 引用

https://qiita.com/T-ubu/items/76a60bfd4a27d11521e2

https://qiita.com/asahina820/items/b202aade794becb0e621

https://zenn.dev/mapbox_japan/articles/f5e9bef5a00e58