はじめに
土木の世界だと、土の量や水の量を把握したくなる場面があるのではないかと思います。
土量や湛水量を計算するのに、しばしばメッシュ法や点高法といった方法が用いられます。計算方法は色々ありますが、対象の面を細かい区域に分け、その区域の頂点又は代表点の高さや水深(この記事では、z 値とします)を用いて、各区域の体積を求め、それを集計して対象となる面全体の土量や湛水量を計算する方法です。
国土地理院からは、標高や湖水深のデータが標高タイルとして提供されておりますので、メッシュ法を用いて、Web 上で土地の体積や湖の水量を簡易に計算できるのではないか試行錯誤しましたので、その結果を記事にします。
結果として作成したサイトはこちらです。囲んだ面の範囲内の体積を標高又は湖水深を用いて概算します。
※背景地図は国土地理院最適化ベクトルタイル
メッシュの生成
メッシュ法を適用するには、まず、対象領域内を細かい区域に区切ってメッシュデータを作成する必要があります。
このような GIS 的な作業を Web 上でさせる場合、まず Turf.js を探すといい感じのツールが見つかることが多いです。
あまり厳密にメッシュの大きさを考えなくても良いと思いますので、単に指定ポリゴン内に形状問わずにメッシュを生成できるツールを探します。
候補としては、各種メッシュを発生させる hexGrid()
、pointGrid()
、squareGrid()
、triangleGrid()
があります。今回は、メッシュの面積を計算して、そのメッシュの z 値と掛け合わせる必要がありますので、ポリゴンが発生する hexGrid()
、squareGrid()
、triangleGrid()
の3つが利用対象になります。
使い方としては、基本は BBOX 内に設定した大きさのメッシュを生成します。便利な点として、オプションの mask
へポリゴンを渡すと、その領域内のみにメッシュを発生できます。
turf.squareGrid(bbox, cellSide, {mask: polygon})
その他、TIN を発生させる tin()
も使えそうでした。特に、対象領域の境界付近を TIN で埋めることができれば、面積の誤差を小さくすることができて有効かと考えました。ただ、TIN を発生させるには、まずはポリゴン内に pointGrid()
を用いて、点データを準備する必要があります。すると、点から TIN を作る際に、ポリゴンの形状を考慮できないため、対象領域のポリゴンに凹んだ部分があるとその部分を除外せずに TIN が発生してしまいます。これの対処が面倒であるため、利用は見送りました。
メッシュの代表点に z 値を付与
メッシュデータができましたら、各メッシュに z 値を付与する必要があります。使うデータや取得方法は目的に応じて様々ですが、今回はデモとして以下の標高タイル形式の地理院タイルを用いています。
単純に、各メッシュごとに代表点を生成し、その座標の z 値を標高や湖水深をタイルデータから取得します。代表点は、Turf.js で center()
や centroid()
等が準備されていますので、利用方法に応じて選択できます(今回は簡易なので、あまり厳密に考えていませんが)。
タイルデータなら、ある程度キャッシュが効くので、同一タイル内であれば、それなりの数のメッシュに対して z 値を付与しても大丈夫かと思いますが、1回ごとに API を叩くような場合だと、サーバに負荷をかけないように注意が必要です。
なお、代表点ではなく、各メッシュの頂点の座標値に z 値を与えて、その平均値をメッシュの z 値として利用する方法もあります(「点高法」と調べると、この方法の方がヒットします)。しかし、今回は概算なのでシンプルに各メッシュの代表点での z 値取得にとどめました。
結果の集計
集計は単純に各メッシュごとに面積計算を行い、z 値を掛け合わせて体積を出し、集計するだけです。Web 上で利用するにあたり、使った技術等を記載しておきます。
面積の計算
Turf.js では、メッシュサイズを指定してグリッドの生成をしていますが、念のため、生成したグリッドは改めて面積計算を行っています。
地理院地図・地理院地図Vectorでは、面積を計算することができますが、ソースコードが公開されておりますので、その中で必要なもの(GSI.Utils.AreaCalculator
関係)を抜粋して利用しました。
非同期による z 値の付与
今回は、JavaScript の fetch API でタイル画像を取得しつつ、全メッシュへの z 値付与を Promise.all()
で確認してから集計に進んでいます。
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万 m3(2019年5月)でした。
今回作成したサイトで測ってみると、以下のような結果でした。
- 9,942万 m3(50 m 四角形メッシュ)
- 9,996万 m3(50 m 六角形メッシュ)
- 9,897万 m3(250 m 三角形メッシュ)
摩周湖
ネットで見つけた値は、湖容積 28.6億 m3 でした。
今回作成したサイトで測ってみると、以下のような結果でした。
- 27.9億 m3(250 m 四角形メッシュ)
- 27.8億 m3(250 m 六角形メッシュ)
- 27.9億 m3(250 m 三角形メッシュ)
おわりに
土や水の体積を概算するサイトを作成してみました。オープンデータやオープンソースが充実しており、イメージ通りに作成を進めることができました。感謝です。