Processingでお手軽3次元データビジュアライズ（地形編）

本記事は、[Processing Advent Calendar 2021](https://adventar.org/calendars/6163)の21日目の記事です。

[昨年](https://qiita.com/masakiyamabe/items/b131c2c2d6ddd2886ba5)、[一昨年に続き](https://qiita.com/masakiyamabe/items/5c5ebc1878865b524d4b)、Processingでお手軽に3次元データビジュアライズするシリーズです。

# はじめに
昨今、「VR」「メタバース」「デジタルツイン」という言葉に代表されるように3次元表現が注目されつつありますが、Processingでも意外と簡単に3次元の地形を再現することができます。今回は「標高モデル」というオープンデータのビジュアライズとクリエイティブ・コーディングを組み合わせる内容です。

データさえ用意すれば日本全国どこの地形でも表示できますが、今回はお正月向けに富士山周辺の地形を使って遊んでみます。

サンプルコードは[こちらからダウンロード](https://files.masakiyamabe.com/public/advc2021.zip)できます。
文中のサンプルコードのファイル名と対応しているので適宜参照しながらお読みください。

__完成例__

![step4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80141/156d07bc-9c95-478a-224d-ff774abaa927.gif)


# 前提条件
使用するライブラリは次の通りです。マウスで視点操作を行うので、ライブラリはPeasyCamを使います。

* PeasyCam 302

# 素材の準備

## 使用するデータ

富士山周辺の標高データを国土地理院のサイトから取得します。今回利用するのは、基盤地図情報数値標高モデル（表赤枠部分）を使います。（表の[引用元](https://maps.gsi.go.jp/development/hyokochi.html)、[利用規約](https://maps.gsi.go.jp/help/termsofuse.html)）

![table.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80141/3aa3584f-ae74-eabe-d2db-4afd46d44f4c.jpeg)


## 標高データを探す
[タイル座標確認ページ](https://maps.gsi.go.jp/development/tileCoordCheck.html#5/35.362/138.731)にアクセスして、富士山周辺で目的の標高データが含まれるタイル（区画）を調べます。

![map.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80141/78633d9f-409d-98d7-4334-e27bbe9b7357.png)


今回は、「10/906/404」と表示されているタイルが良さそうです。この３つの数値はそれぞれズームレベル（Z）、 X座標（X）、Y座標（Y）を表します。これらは次のステップで使います。

## データの取得

目的のタイルを調べたら、次のURLにアクセスします。（URLの最後の部分が先ほど調べたタイルのZ/X/Yに一致しています）

[https://cyberjapandata.gsi.go.jp/xyz/dem_png/10/906/404.png](https://cyberjapandata.gsi.go.jp/xyz/dem_png/10/906/404.png)

表示されたPNG画像を保存しておきましょう。

![10-906-404.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80141/8d6885b9-5278-3537-f04f-1efde02c9cfc.png)

## 画像の色情報から標高を求める

等高線のように見えるこの画像は、縦横が256ピクセルの画像で、1つのピクセルの色情報がその位置の標高の数値に対応しています。
色情報（RGB）から標高を求めるには、標高(m) ＝ R * 256 * 256 + G * 256 + B *0.01 の計算式を使います。

__注：（2021/12/31追記）__
海面（標高0）や標高が無効のピクセルは(R,G,B) = (128,0,0)となっています。
本記事のサンプルでは海面を含まないのでこの条件式を加えていませんが、海面を含むタイルを使う場合は、上の計算式に加えて、この条件で標高が0になるように条件式を加えてください。

（[標高タイルの詳細仕様](https://maps.gsi.go.jp/development/demtile.html)）

# 様々な作例

ダウンロードした標高タイル画像をProcessingにロードして遊んでみましょう。

## 1. 標高データでboxを並べる

まずは、標高の位置にboxを並べてみましょう。タイル画像の色情報を読み出し、前述した計算式で標高を求めています。（サンプルコード：terrain3d_step1）

__結果__
![step1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80141/b215f9f5-2786-787e-0d6c-4faabe30fef0.png)
少し高さを強調していますが、富士山の美しい形が現れました。
以下はコードの中身です。Processingで画像を読み込み、ピクセルの色情報にアクセスするコードを書いたことがある人にはとっては特に難しくはないのではないでしょうか。

```java:terrain3d_step1.pde
import peasy.*; //PeasyCamライブラリをインストールしてインポート
PeasyCam cam;

PImage loadPng;

void setup() {
  size(1024, 768, P3D);
  //カメラの初期化
  cam = new PeasyCam(this, 700);
  //標高タイル画像を読み込む
  loadPng = loadImage("10-906-404.png");
}

void draw(){
  background(0);
  noStroke();
  
  float pixMargin = 5.0;
  for(int j=0; j<loadPng.height; j++){
    for(int i=0; i<loadPng.width; i++){
      
      //画像からピクセルのカラー（RGB）を取得
      color pxColor = loadPng.get(i,j);
      float red = red(pxColor);
      float green = green(pxColor);
      float blue = blue(pxColor);
      
      float x = (i - loadPng.width /2) * pixMargin;
      float y = (j - loadPng.height /2) * pixMargin;
      //R,G,Bの値から高さを求める
      float z = (red * 256 * 256 + green * 256 + blue) *0.0005;
      pushMatrix();
      
      translate(x,y,z);
      box(2);
      
      popMatrix();
    }
  }
  
}
```

## 2. メッシュで表示

大量のboxを表示するとややパフォーマンスが落ちるので、PShapeを使って点群やメッシュで表示してみます。（サンプルコード：terrain3d_step2）

__結果__

点群で表示
![step1_points.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80141/ca7db711-174d-4dad-a225-e39727b63c18.png)

メッシュで表示
![step2_mesh.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80141/359b4cf8-b9ab-7efd-f38f-00eba540640e.png)

## 3. 富士山にノイズを加える

なんと罰当たりなことに富士山にノイズを加えてみます。いやいや、上下を反転すれば湖面に映る逆さ富士のようで風情あるイメージになりますよ（たぶん）。（サンプルコード：terrain3d_step3）
![step3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80141/0b3d7cbd-1f79-c07e-1177-7e4c835553a1.png)

## 4. 富士山に粉雪を舞い散らかす

アニメーションする点群を用意して、雪のように舞い散らかします。これはだいぶ風流ですね！（サンプルコード：terrain3d_step4）

![step4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80141/156d07bc-9c95-478a-224d-ff774abaa927.gif)


## 5. より広域を表示する

周辺のタイル画像をダウンロードして結合すれば、ソースコードにほとんど手を加えることなく広域の地形を再現できます。
ここでは、9枚の画像を結合して768 x 768のタイル画像を用意しました。（サンプルコード：terrain3d_step5）

__タイル画像__

![mtfuji_10-904402-904404.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80141/e80d2cce-18fd-fc93-6f0c-923dd7e8a920.png)


__結果__

ライティングなどを調整した結果はこのようになりました。リアルな地形を様々な視点から眺めることができます。

![step5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80141/a290bea0-00be-2740-81bc-930fd5d8180b.png)



# 終わりに

オープンデータを使って、データビジュアライズとクリエイティブコーディングを融合させる表現はまだまだ探究の余地がありそうです。ぜひ面白い作品にトライしてください。