0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

「MapLibre GL JSでダムに沈んだ村を可視化する」をラスタ合成で

Last updated at Posted at 2025-04-23

はじめに

MapLibre GL JSでダムに沈んだ村を可視化する という素敵な記事があります。

この記事では 地理院標高タイル を使った 別解 をやってみます。

できあがり

image.png

デモ: https://frogcat.github.io/underwater-village/
ソース: https://github.com/frogcat/underwater-village

解説

方針

こちらは 地理院標高タイル:dem5a_png を直接表示してみたものです。どうやら水部が赤く塗り分けられているようですね?

image.png

地理院標高タイル によるとこの赤い部分は無効値である #800000 で塗られているということになります。

無効値(標高タイル(テキスト形式)の「e」に該当する箇所)は(R, G, B)=(128, 0, 0)です。

入力されたタイル画像について、対応する標高PNGタイルが #800000 の部分だけを維持し、他は transparent black にして返すことができれば求める画像が得られそうです。

実装

短いのでそのまま貼っておきます。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>underwater-village</title>
    <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0" />
    <link href="https://unpkg.com/maplibre-gl@5.4.0/dist/maplibre-gl.css" rel="stylesheet" />
    <script src="https://unpkg.com/maplibre-gl@5.4.0/dist/maplibre-gl.js"></script>
    <script>
      function createLoadFn() {
        const load = (url) =>
          new Promise((resolve) => {
            const img = new Image();
            img.crossOrigin = "anonymous";
            img.onload = () => {
              const canvas = document.createElement("canvas");
              canvas.width = img.naturalWidth;
              canvas.height = img.naturalHeight;
              const context = canvas.getContext("2d");
              context.drawImage(img, 0, 0);
              const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
              resolve({ canvas, context, imageData });
            };
            img.src = url;
          });
        const toBlob = (canvas) =>
          new Promise((resolve) => {
            canvas.toBlob(resolve);
          }).then((blob) => blob.arrayBuffer());

        return async function (params) {
          const [y, x, z] = params.url.split(/[\./]/).reverse().slice(1);
          const url = [
            params.url.replace("mask://", "https://"),
            `https://cyberjapandata.gsi.go.jp/xyz/dem5a_png/${z}/${x}/${y}.png`,
          ];
          const [src, dem] = await Promise.all(url.map(load));
          for (let i = 0; i < dem.imageData.data.length; i += 4) {
            if (
              dem.imageData.data[i + 0] !== 0x80 ||
              dem.imageData.data[i + 1] !== 0x00 ||
              dem.imageData.data[i + 2] !== 0x00
            )
              src.imageData.data.fill(0, i, i + 4);
          }
          src.context.putImageData(src.imageData, 0, 0);
          return {
            data: await toBlob(src.canvas),
          };
        };
      }
    </script>
  </head>
  <body>
    <div id="map" style="position: absolute; top: 0; left: 0; right: 0; bottom: 0"></div>
    <script>
      maplibregl.addProtocol("mask", createLoadFn());
      new maplibregl.Map({
        container: "map",
        center: [136.485452, 35.69576],
        zoom: 13,
        hash: true,
        style: "style.json",
      });
    </script>
  </body>
</html>

addProtocol についてはこちらの記事を参照してください。

本記事のコアはこのあたりです。

          const [src, dem] = await Promise.all(url.map(load));
          for (let i = 0; i < dem.imageData.data.length; i += 4) {
            if (
              dem.imageData.data[i + 0] !== 0x80 ||
              dem.imageData.data[i + 1] !== 0x00 ||
              dem.imageData.data[i + 2] !== 0x00
            )
              src.imageData.data.fill(0, i, i + 4);
          }
          src.context.putImageData(src.imageData, 0, 0);
  • 元画像と pngdem を取得して ImageData として参照できるようにしておく
  • pngdem の RGBA をイテレートして、 #800000 でない場合には元画像の対応箇所を透明黒で塗る
  • 終わったら canvas に imageData を描き戻し、PNG Blob を返す

おわりに

ラスタ合成によってタイル画像から選択的なクリッピングを行う事例を紹介しました。マスクとして使える適当なタイルがある場合には手軽な方式だと思います。

実際には標高タイル/ズームレベルによっては水部に無効値ではなくて標高値(水面の標高値?)が入っていることがあったり、5m メッシュ標高は計測対象外エリアが無効値であったりするので注意が必要です。また画像側が z=16~18 をサポートしていても DEM が z=15 まで、みたいな場合の処理は課題です。

標高タイルにこだわらず、地理院の標準地図をマスクとして 水色の部分だけをスルー みたいな雑な使い方も面白い効果を生むかもしれません。

おまけ

特段エリアを限定していないので、神奈川県の 宮ヶ瀬村 の様子も見ることができました。他にも見つかるかもしれませんね。

image.png

https://frogcat.github.io/underwater-village/#13.43/35.52305/139.23113

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?