Web 上で島や湖の体積を概算する

## はじめに

土木の世界だと、土の量や水の量を把握したくなる場面があるのではないかと思います。

土量や湛水量を計算するのに、しばしばメッシュ法や点高法といった方法が用いられます。計算方法は色々ありますが、対象の面を細かい区域に分け、その区域の頂点又は代表点の高さや水深（この記事では、z 値とします）を用いて、各区域の体積を求め、それを集計して対象となる面全体の土量や湛水量を計算する方法です。

国土地理院からは、[標高](https://maps.gsi.go.jp/development/ichiran.html#dem)や[湖水深](https://maps.gsi.go.jp/development/ichiran.html#lakedepth)のデータが[標高タイル](https://maps.gsi.go.jp/development/demtile.html)として提供されておりますので、メッシュ法を用いて、Web 上で土地の体積や湖の水量を簡易に計算できるのではないか試行錯誤しましたので、その結果を記事にします。

結果として作成したサイトはこちらです。囲んだ面の範囲内の体積を標高又は湖水深を用いて概算します。

https://mghs15.github.io/land-volume-calc-site/

![湖沼の体積（例）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842832/0ce60b17-c547-3a75-66ab-ebe76fd25f3e.png)

※背景地図は国土地理院最適化ベクトルタイル

## メッシュの生成

メッシュ法を適用するには、まず、対象領域内を細かい区域に区切ってメッシュデータを作成する必要があります。

このような GIS 的な作業を Web 上でさせる場合、まず [Turf.js](https://turfjs.org/) を探すといい感じのツールが見つかることが多いです。

あまり厳密にメッシュの大きさを考えなくても良いと思いますので、単に指定ポリゴン内に形状問わずにメッシュを生成できるツールを探します。

候補としては、各種メッシュを発生させる `hexGrid()`、`pointGrid()`、`squareGrid()`、`triangleGrid()` があります。今回は、メッシュの面積を計算して、そのメッシュの z 値と掛け合わせる必要がありますので、ポリゴンが発生する `hexGrid()`、`squareGrid()`、`triangleGrid()` の3つが利用対象になります。

使い方としては、基本は BBOX 内に設定した大きさのメッシュを生成します。便利な点として、オプションの `mask` へポリゴンを渡すと、その領域内のみにメッシュを発生できます。

```JavaScript
turf.squareGrid(bbox, cellSide, {mask: polygon})
```

その他、TIN を発生させる `tin()` も使えそうでした。特に、対象領域の境界付近を TIN で埋めることができれば、面積の誤差を小さくすることができて有効かと考えました。ただ、TIN を発生させるには、まずはポリゴン内に `pointGrid()` を用いて、点データを準備する必要があります。すると、点から TIN を作る際に、ポリゴンの形状を考慮できないため、対象領域のポリゴンに凹んだ部分があるとその部分を除外せずに TIN が発生してしまいます。これの対処が面倒であるため、利用は見送りました。

![tinのメリットデメリット.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842832/bad856a3-5fd4-94e4-ed3c-15d729ebf55c.png)


## メッシュの代表点に z 値を付与

メッシュデータができましたら、各メッシュに z 値を付与する必要があります。使うデータや取得方法は目的に応じて様々ですが、今回はデモとして以下の標高タイル形式の地理院タイルを用いています。

* [標高](https://maps.gsi.go.jp/development/ichiran.html#dem)
* [湖水深](https://maps.gsi.go.jp/development/ichiran.html#lakedepth)

単純に、各メッシュごとに代表点を生成し、その座標の z 値を標高や湖水深をタイルデータから取得します。代表点は、Turf.js で `center()` や `centroid()` 等が準備されていますので、利用方法に応じて選択できます（今回は簡易なので、あまり厳密に考えていませんが）。

:::note warn
タイルデータなら、ある程度キャッシュが効くので、同一タイル内であれば、それなりの数のメッシュに対して z 値を付与しても大丈夫かと思いますが、１回ごとに API を叩くような場合だと、サーバに負荷をかけないように注意が必要です。
:::

なお、代表点ではなく、各メッシュの頂点の座標値に z 値を与えて、その平均値をメッシュの z 値として利用する方法もあります（「点高法」と調べると、この方法の方がヒットします）。しかし、今回は概算なのでシンプルに各メッシュの代表点での z 値取得にとどめました。

## 結果の集計

集計は単純に各メッシュごとに面積計算を行い、z 値を掛け合わせて体積を出し、集計するだけです。Web 上で利用するにあたり、使った技術等を記載しておきます。

### 面積の計算

Turf.js では、メッシュサイズを指定してグリッドの生成をしていますが、念のため、生成したグリッドは改めて面積計算を行っています。

[地理院地図](https://github.com/gsi-cyberjapan/gsimaps)・[地理院地図Vector](https://github.com/gsi-cyberjapan/gsimaps-vector-experiment)では、面積を計算することができますが、ソースコードが公開されておりますので、その中で必要なもの（`GSI.Utils.AreaCalculator` 関係）を抜粋して利用しました。

### 非同期による z 値の付与

今回は、JavaScript の fetch API でタイル画像を取得しつつ、全メッシュへの z 値付与を `Promise.all()` で確認してから集計に進んでいます。

```JavaScript: z 値付与と体積集計のイメージ
const cellsWithZ = [];
cells.forEach( cell => {
  // メッシュごとの z 値付与
  const cellWithZ = fetch(...).then(...);
  promiseSet.push(cellWithZ)
});
Promise.all(cellsWithZ).then((cells) => {
  // 体積集計処理
  ...
});
```

## 地図上への表示

今回も Web 地図ライブラリとして MapLibre GL JS を用いています。ポリゴンデータを立ち上がらせるように 3D 表示ができる `fill-extrusion` というスタイル設定ができますので、こちらを用いて視覚的に体積を把握できるようにしています。ただし、視覚的であることを重要視していますので、高さ方向は強調して表示させています。

## 結果の検証

今回の実装の精度を検証してみたいと思います。ネットで適当に検索したところ、西之島の体積と摩周湖の水量が見つかりましたので、この2つを比較してみました。だいたい合っていそうです。


### 西之島

ネットで見つけた値は、[体積は9,992万 m<sup>3</sup>（2019年5月）](https://www8.cao.go.jp/okinawa/siryou/singikai/senmoniinkai/14/14-3-2_1.pdf)でした。

今回作成したサイトで測ってみると、以下のような結果でした。
* 9,942万 m<sup>3</sup>（50 m 四角形メッシュ）
* 9,996万 m<sup>3</sup>（50 m 六角形メッシュ）
* 9,897万 m<sup>3</sup>（250 m 三角形メッシュ）

![西之島の体積.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842832/2db2fb07-88eb-9bc2-5631-07592bbd0ead.png)

### 摩周湖

ネットで見つけた値は、[湖容積 28.6億 m<sup>3</sup>](https://db.cger.nies.go.jp/gem/inter/GEMS/mashu/contents/intro.html) でした。

今回作成したサイトで測ってみると、以下のような結果でした。
* 27.9億 m<sup>3</sup>（250 m 四角形メッシュ）
* 27.8億 m<sup>3</sup>（250 m 六角形メッシュ）
* 27.9億 m<sup>3</sup>（250 m 三角形メッシュ）

![摩周湖の体積.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842832/3ebe787f-f20a-ae22-898f-7348b0121824.png)

## おわりに

土や水の体積を概算するサイトを作成してみました。オープンデータやオープンソースが充実しており、イメージ通りに作成を進めることができました。感謝です。