html + js で風を可視化

html + js で風を可視化

技術系の記事投稿先を自前のBlogに移しました。初心者向け,AWSなど幅広いTechジャンルで記事投稿がんばってますので、よろしかったらこちらも参照ください！

https://hiyuzawa.jp/


2016.09.20 12:00 頃
台風16号接近中の風の可視化↓

https://s3-ap-northeast-1.amazonaws.com/hiyuzawa/wind_visualize/index.html

![wind.png](https://qiita-image-store.s3.amazonaws.com/0/36803/a2d2c655-0cbe-5e08-8173-33c31623fa18.png "wind.png")

## TL;DR

```bash
$ git clone https://github.com/hiyuzawa/wind_visualize.git
$ cd wind_visualize/
$ npm install
$ ./node_modules/.bin/gulp

# ブラウザで http://127.0.0.1:8000 にアクセス
```

## 気象データ

### はじめに

気象データとは一般的に気温,風,気圧,降水量等のことですが、全国の[アメダス](http://www.jma.go.jp/jma/kishou/know/amedas/kaisetsu.html)観測地点に設置された計器による測定値と、それらを元に計算しメッシュ上の各地点のその値を求めたものの2種類があるようです。
今回は後者のものを利用します。
データは商用利用であればしかるべき組織より購入して利用するようですが学術的な利用であれば入手可能のようです。

以下の大学サーバ?によりホストされている

* [地球電脳体クラブ](http://blog.syo-ko.com/?eid=1228)

上のホームページより「データサーバ」→「気象庁データ」→「数値予報GPV」と遷移すると[ここ](http://database.rish.kyoto-u.ac.jp/arch/jmadata/gpv-original.html)が表示されるがその中のリンク[数値予報GPV](http://database.rish.kyoto-u.ac.jp/arch/jmadata/data/gpv/original/)で今回利用するデータがダウンロードできます。
日付のリンクをたどり末端に遷移すると日別にデータが保存されています。さまざまなデータ種別がごちゃごちゃに置かれていますが、利用するのは

* Z__C_RJTD_YYYYMMDDHH0000_MSM_GPV_Rjp_L-pall_FH00-15_grib2.bin 

```
(例) Z__C_RJTD_20160922180000_MSM_GPV_Rjp_L-pall_FH00-15_grib2.bin
```

というデータです。これは 2016/09/22 18時(UTC) のMSMデータであり気圧面(pall)の0時間〜15時間先までの予測データであるという意味です。

MSMって何かというと少し前の[ページ](http://database.rish.kyoto-u.ac.jp/arch/jmadata/gpv-original.html)により[メソ数値予報モデルGPV (MSM) ](http://www.jmbsc.or.jp/jp/online/file/f-online10200.html)のリンク先をみるとどんなデータか雰囲気はわかると思います。ここで重要なのはこのデータは日本を中心とした格子データでありその範囲は
* 北緯22.4度～47.6度、東経120度～150度
* 0.1度×0.125度（格子数253×241） (※今回利用するのは気圧面)

その他にも今回利用しませんが[GSM](http://www.jmbsc.or.jp/jp/online/file/f-online10100.html)などの全球(全地球)データもあるようです。ワクワクしますねw

### GRIB2

さて、利用するデータを1つダウンロードしてみます
[Z__C_RJTD_20160922210000_MSM_GPV_Rjp_L-pall_FH00-15_grib2.bin](http://database.rish.kyoto-u.ac.jp/arch/jmadata/data/gpv/original/2016/09/22/Z__C_RJTD_20160922210000_MSM_GPV_Rjp_L-pall_FH00-15_grib2.bin) (48MB)
フォーマットはGRIB2形式と呼ばれ、国際気象機関が定める専用のフォーマット(仕様書:[WMO INTERNATIONAL CODES](http://www.wmo.int/pages/prog/www/WMOCodes.html))になります。

このデータを自力で一部を解読されている[素晴らしいサイト](http://blog.syo-ko.com/?eid=1228)がありましたがこれはほんとに骨が折れる作業なので今回はwgrib2というNOAA(アメリカ海洋大気庁)が提供する解読ツールを利用します。

* [wgrib2](http://www.cpc.ncep.noaa.gov/products/wesley/wgrib2/) - [source download](http://www.ftp.cpc.ncep.noaa.gov/wd51we/wgrib2/)

#### install wgrib2

以下のようにソースをダウンロードしてmakeすると実行バイナリが作成されます. 

```bash
#
# 最新のソースだと私の環境(mac)では make test でエラーになったので v2.0.4 を利用しています。 
#

$ wget wget http://www.ftp.cpc.ncep.noaa.gov/wd51we/wgrib2/wgrib2.tgz.v2.0.4
$ mv wgrib2.tgz.v2.0.4 wgrib2.tgz
$ tar zxvf wgrib2.tgz
$ cd grib2
$ make
$ ./wgrib2/wgrib2 -version
v0.2.0.4 2/2016 Wesley Ebisuzaki, Reinoud Bokhorst, John Howard, Jaakko Hyvätti, Dusan Jovic, Kristian Nilssen, Karl Pfeiffer, Pablo Romero, Manfred Schwarb, Arlindo da Silva, Niklas Sondell, Sergey Varlamo
```

作成されたバイナリ `./wgrib2/wgrib2 ` を任意の場所にコピーして利用します

### wgrib2の利用法

引数なしで起動して表示されるhelpで多機能さに圧倒されそうですが、今回利用する部分に限って説明します

* wgrib2 [grib2データファイルパス]

grib2の中に格納されているデータ群がリストで表示されます。例えば上の例でダウンロードしたMSM Grib2データを与えると以下のようにこの中には552種類のデータがあることがわかります。

```bash
$ ./wgrib2 Z__C_RJTD_20160922210000_MSM_GPV_Rjp_L-pall_FH00-15_grib2.bin
1.1:0:d=2016092221:HGT:1000 mb:anl:
1.2:0:d=2016092221:UGRD:1000 mb:anl:
1.3:0:d=2016092221:VGRD:1000 mb:anl:
1.4:0:d=2016092221:TMP:1000 mb:anl:
...
1.550:0:d=2016092221:VGRD:100 mb:15 hour fcst:
1.551:0:d=2016092221:TMP:100 mb:15 hour fcst:
1.552:0:d=2016092221:VVEL:100 mb:15 hour fcst:
```

少しだけ解説すると例えば
`1.2:0:d=2016092221:UGRD:1000 mb:anl:`

はデータID 1.2 であり 1000気圧面におけるanl(観測による)UGRD(東西方向風力) の値という意味で

`1.551:0:d=2016092221:TMP:100 mb:15 hour fcst:`

はデータID 1.551であり100気圧面における15時間後予想のTMP(気温)の値という意味です。

GRIB2データごとに中身もことなりそれぞれのGRIB2データごとの仕様書にもとづいています
今回は先程も紹介した[MSMデータ解説書](https://www.data.jma.go.jp/add/suishin/jyouhou/pdf/205.pdf)にその中身も書いてありますのでその他のデータも何であるか予想つくと思います。

* wgrib2 [grib2データファイルパス] -d [データID] -csv [出力フアイルパス]

指定したデータIDの中身が確認できます。

```bash
$ ./wgrib2 Z__C_RJTD_20160922210000_MSM_GPV_Rjp_L-pall_FH00-15_grib2.bin -d 1.2 -csv - 
1.2:0"2016-09-22 21:00:00","2016-09-22 21:00:00","UGRD","1000 mb",120,22.4,-1.55987
"2016-09-22 21:00:00","2016-09-22 21:00:00","UGRD","1000 mb",120.125,22.4,-1.48174
"2016-09-22 21:00:00","2016-09-22 21:00:00","UGRD","1000 mb",120.25,22.4,-1.38799
"2016-09-22 21:00:00","2016-09-22 21:00:00","UGRD","1000 mb",120.375,22.4,-0.997366
"2016-09-22 21:00:00","2016-09-22 21:00:00","UGRD","1000 mb",120.5,22.4,-0.309866
...

```

例えば1行目末尾に `120,22.4,-1.55987` とあります。これは
東経120.0度,北緯22.4度の地点の値は-1.55987ということを表しています。

* wgrib2 [grib2データファイルパス] -d [データID] -no_header -ieee [出力フアイルパス]

今回利用する出力方法です. 中身を見るという意味では先程の -csv と同じです。ブラウザからJavaScriptで読み込ませるにデータ量を少なくするために各数値を4バイトで表現しそれを連結させます。

```bash
$ ./wgrib2 Z__C_RJTD_20160922210000_MSM_GPV_Rjp_L-pall_FH00-15_grib2.bin -d 1.2 -no_header -ieee UGRD.ieee
$ ls -l UGRD.dat
-rw-r--r--  1 hiyuzawa  staff  243892  9 23 10:56 UGRD.ieee
```
ファイルサイズが 格子点の数(253×241) x 4 = 243892 と一致しています。

### データ作成

今回は以下のようなshを作成して読み込ませるデータを作成しました。

```mkdata.sh
#!/bin/sh

DATETIME=$1

YEAR=${1:0:4}
YEAR=${1:0:4}
MONTH=${1:4:2}
DAY=${1:6:2}
wget http://database.rish.kyoto-u.ac.jp/arch/jmadata/data/gpv/original/${YEAR}/${MONTH}/${DAY}/Z__C_RJTD_${DATETIME}_MSM_GPV_Rjp_L-pall_FH00-15_grib2.bin

./wgrib2 Z__C_RJTD_${DATETIME}_MSM_GPV_Rjp_L-pall_FH00-15_grib2.bin -d 1.2  -no_header -ieee UGRD_00.ieee
./wgrib2 Z__C_RJTD_${DATETIME}_MSM_GPV_Rjp_L-pall_FH00-15_grib2.bin -d 1.3  -no_header -ieee VGRD_00.ieee
./wgrib2 Z__C_RJTD_${DATETIME}_MSM_GPV_Rjp_L-pall_FH00-15_grib2.bin -d 1.4  -no_header -ieee TMP_00.ieee

cat UGRD_00.ieee VGRD_00.ieee TMP_00.ieee >  wind.ieee
```

UGRD,VGRD,TMP の格子状のデータを連結して一つのファイルにしたデータです。

```bash
$ ./mkdata.sh 20160922210000
$ ls -l wind.ieee
-rw-r--r--  1 hiyuzawa  staff  731676  9 23 11:05 wind.ieee # 253x241x4x3 = 731676
```

## JavaScript 開発準備

本題に入る前にまず開発環境を整えます。最近は gulp とか ES6(ES2015) とか流行りなのでそれに習ってやります。

### node (npm) 環境の準備

npm を用いてJavaScriptライブラリ等をインストールするのでまず node の環境を用意します。macでの環境整備になりますが先日記載したQiitaの別の記事

* [Macで一通りの開発環境を揃えるメモ - Qiita](http://qiita.com/hiyuzawa/items/4306a85869ac5c0563ed)

の nodebrew セットアップは参考になるかもしれません。何れにせよnodeが起動する環境を準備してください

```bash
$ npm -v
2.15.9
```

作業ディレクトリを作成しnpm init します。（以下はすべてこのディレクトリ内での作業です。）

```bash
$ mkdir wind_visualize
$ cd wind_visualize/
$ npm init
```

開発に必要なnpmパッケージをインストールします

* [gulp](http://gulpjs.com)

``` bash
$ npm install gulp --save-dev
$ npm install gulp-webserver --save-dev
$ npm install vinyl-source-stream --save-dev
```

* [browserify](http://browserify.org)

```bash
$ npm install browserify --save-dev
```

* [babel](https://babeljs.io)

```bash
$ npm install babelify --save-dev
$ npm install babel-preset-es2015 --save-dev
```

そのあと gulpのタスクファイルを作成します。私はいつもこんな感じの `./gulpfile.js` を作ります。

```javascript
var gulp = require('gulp');
var browserify = require('browserify')
var webserver = require('gulp-webserver');
var source = require('vinyl-source-stream');
var babelify = require('babelify');

gulp.task('browserify', function() {
    browserify('./js/index.js', {debug: true})
        .transform(babelify, {presets: ["es2015"]})
        .bundle()
        .on("error", function(e){
            console.log(e.message);
        })
        .pipe(source('bundle.js'))
        .pipe(gulp.dest('./'))
});

gulp.task('watch', function(){
    gulp.watch('./js/*.js', ['browserify'])
});

gulp.task('webserver', function(){
    gulp.src('./')
        .pipe(webserver({
                host: '127.0.0.1',
                livereload: true
        }));
});

gulp.task('default', ['browserify', 'watch', 'webserver']);
```

bundle.jsを読み込ませた確認用`index.html`と`js/index.js`を準備してgulpを起動するとwebserverが立ち上がり http://127.0.0.1:8000 にてそれが確認できます。

```bash
$ ./node_modules/.bin/gulp
[14:06:33] Using gulpfile ~/tmp/wind_visualize/gulpfile.js
[14:06:33] Starting 'browserify'...
[14:06:33] Finished 'browserify' after 35 ms
[14:06:33] Starting 'watch'...
[14:06:33] Finished 'watch' after 21 ms
[14:06:33] Starting 'webserver'...
[14:06:33] Webserver started at http://127.0.0.1:8000
[14:06:33] Finished 'webserver' after 33 ms
[14:06:33] Starting 'default'...
[14:06:33] Finished 'default' after 26 μs
```

この状態のコードを [GitHub](https://github.com/hiyuzawa/wind_visualize/tree/59957367dd1c68f5c1c7704f6a6ba2c4d73c2b53) におきます

## 日本地図をブラウザに描く

日本地図をブラウザに描く方法は色々ありそうですが、JSのデータビジュアライズライブラリである[D3.js](https://d3js.org/)を使って描く方法が一般的なのでそれに習います。([「d3.js 日本地図」で検索](https://www.google.co.jp/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=d3.js%20%E6%97%A5%E6%9C%AC%E5%9C%B0%E5%9B%B3))

### 日本地図データ

国土データ(海岸線)データが必要です。色々なサイトにてそのデータが提供されていますが今回は [Natural Earth](http://www.naturalearthdata.com/)で提供されているデータを利用します. 英語ですし色々なリンクがあり混乱しますが [1:50m Cultural Vectors](http://www.naturalearthdata.com/downloads/50m-cultural-vectors/) の 「Admin 0 – Countries」というデータを利用します。
詳しい説明は省略しますが、以下の手順でデータを作成します

1. Natural Earth からデータをダウンロード (全世界の国の海岸線が含まれるshp(シェイプ)ファイル形式)
2. shpファイルから必要な国のみを抽出しかつGeoJSON形式に変換
3. GeoJSON形式では細かい表現すぎて容量が大きいのでTopoJson形式に変換
4. JSから読み込ます場所にデータを移動

実際は以下のようにします。まず以下の２つのツールが必要です

* [GDAL](http://www.gdal.org/) - Step.2の変換で必要
* [topojson](https://github.com/mbostock/topojson) - Step.3の変換で必要

```
$ brew install gdal
$ npm install -g topojson
```

準備が整ったらデータ作成します

```bash
# Step.1
$ wget http://www.naturalearthdata.com/http//www.naturalearthdata.com/download/50m/cultural/ne_50m_admin_0_countries.zip
$ unzip ne_50m_admin_0_countries.zip 
 
# Step.2
$ ogr2ogr -f GeoJSON -where "adm0_a3 IN ('JPN', 'KOR', 'PRK', 'TWN', 'CHN', 'RUS')" map_geo.json ne_50m_admin_0_countries.shp
 
# Step.3
$ topojson --id-property su_a3 -p NAME=name -p name -o map_topo.json map_geo.json
$ ls -l map_*
-rw-r--r--  1 hiyuzawa  staff  518770  9 23 17:06 map_geo.json
-rw-r--r--  1 hiyuzawa  staff   85497  9 23 17:06 map_topo.json   # サイズが削減されている

# Step.4
$ mkdir ./data
$ mv map_topo.json ./data/
```

### 日本地図をブラウザに表示

さて、データ(`./data/map_topo.json`)も準備できたので、これをブラウザで描画します。必要なnpmパッケージを追加で導入します. (それぞれ動作確認がとれているバージョン指定で...)

```bash
$ npm install d3@3.5.17 --save-dev
$ npm install topojson@@1.6.26 --save-dev
```

地図を描画するJavaScriptモジュールを作ります 

* [./js/MapJp.js](https://github.com/hiyuzawa/wind_visualize/blob/f929719b7e0a7fb7540f95760e62f834c0c94363/js/MapJp.js)

それ以外のファイルも修正します. [修正箇所](https://github.com/hiyuzawa/wind_visualize/commit/f929719b7e0a7fb7540f95760e62f834c0c94363)


これで日本地図がブラウザに描画されるはずです。
(d3で日本地図を描くこの部分はソースを見ながら色々参考になるサイトがありますのでそちらで補完ください。)

## 風を可視化する

基本的な流れは以下のとおり。
1. 格子データを読み込む。
2. 風に見立てた一定数の粒子をランダムに配置する。粒子はfill-circleで表す 
3. 粒子はデータエリア(緯度経度)内の任意の地点に存在するが、その任意地点の南北風値,東西風値,気温値はその座標が含まれる格子四隅の値から[線形補間](https://ja.wikipedia.org/wiki/%E7%B7%9A%E5%BD%A2%E8%A3%9C%E9%96%93)して求められる
4. 得られた値から粒子の次の位置が決定しその位置に粒子を移動させる(移動量が少なくすれば粒子の移動は軌跡にみえる)
5. 軌跡は一定後に徐々に消えるように描画すれば、各粒子は尾を引くように見える
6. 粒子には一定の生存期間を設けそれに達する、または範囲外に移動した際は再度ランダムな初期値を与える
7. 粒子を移動させながら描画を繰り返す

それぞれをプログラム解説する。完成したプログラムは[GitHub](https://github.com/hiyuzawa/wind_visualize)に保存されている。

#### Step.1 格子データを読み込む。

[./js/WindDataManager.js](https://github.com/hiyuzawa/wind_visualize/blob/master/js/WindDataManager.js) にてそれを行っている. XHRにて先に作った wind.ieee を読み込む(./dataフォルダに格納)。読み込み後に4バイト浮動小数点の連続データをJavaScriptの1次元配列に格納している.

```javascript
const dataView = new DataView(buffer);
this.wind = new Float32Array(buffer.byteLength / 4);
for (let i = 0, len = this.wind.length; i < len; i++) {
    this.wind[i] = dataView.getFloat32(i * 4);
}
```

読み込み完了後に次の処理に進みたいため、その終了後にCallback関数を呼び出し次の処理に進むようにする
[./js/index.js (L11-L12)](https://github.com/hiyuzawa/wind_visualize/blob/master/js/index.js#L11-L12)

#### Step.2 風に見立てた一定数の粒子をランダムに配置する。粒子はfill-circleで表す

[./js/ParticleManager.js](https://github.com/hiyuzawa/wind_visualize/blob/master/js/ParticleManager.js) にて粒子全体 (サンプルでは2000と定義)を管理し, 各粒子は [./js/Particle.js](https://github.com/hiyuzawa/wind_visualize/blob/master/js/Particle.js)でインスタンス化される構成。粒子生成時に[初期値](https://github.com/hiyuzawa/wind_visualize/blob/master/js/Particle.js#L21-L28)を与える。粒子の描画は[fill-circleで表現する](https://github.com/hiyuzawa/wind_visualize/blob/master/js/ParticleManager.js#L37-L48)


#### Step.3 粒子はデータエリア(緯度経度)内の任意の地点に存在するが、その任意地点の南北風値,東西風値,気温値はその座標が含まれる格子四隅の値から線形補間して求められる

データは格子点データのみであるが任意座標の値は4隅のデータから[線形補間して求める](https://github.com/hiyuzawa/wind_visualize/blob/master/js/Particle.js#L76-L112)


#### Step.4 得られた値から粒子の次の位置が決定しその位置に粒子を移動させる

各粒子の現在位置のそれぞれの値を[求めて更新する](https://github.com/hiyuzawa/wind_visualize/blob/master/js/Particle.js#L40-L43)。風の南北、東西方向の強さに応じて緯度経度値を変化させる。

```javascript
this.lat = this.lat + 0.003 * this._calcWindPower(w, "v");
this.lon = this.lon + 0.003 * this._calcWindPower(w, "u");
this.prevtmp = this.tmp;
this.tmp = this._calcWindPower(w, "t");
```

#### Step.5 軌跡は一定後に徐々に消えるように描画すれば、各粒子は尾を引くように見える

Canvas関数の[globalCompositeOperationを利用すれば](https://github.com/hiyuzawa/wind_visualize/blob/master/js/ParticleManager.js#L53-L57)すでに描画した領域に対して重ね塗りができる。これを利用して透明度を徐々に上げるような見せ方が可能。

```javascript
this.ctx.fillStyle = "rgba(0, 0, 0, 0.95)";
var prev = this.ctx.globalCompositeOperation;
this.ctx.globalCompositeOperation = "destination-in";
this.ctx.fillRect(0, 0, this.cvs.width, this.cvs.height);
this.ctx.globalCompositeOperation = prev; 
```

参考: [globalCompositeOperation - html5.jp](http://www.html5.jp/canvas/ref/property/globalCompositeOperation.html)

#### Step.6 粒子には一定の生存期間を設けそれに達する、または範囲外に移動した際は再度ランダムな初期値を与える

[この辺がその処理](https://github.com/hiyuzawa/wind_visualize/blob/master/js/Particle.js#L45-L61)

#### Step.7 粒子を移動させながら描画を繰り返す

粒子を移動しCanvasに描き[requestAnimationFrame](https://github.com/hiyuzawa/wind_visualize/blob/master/js/ParticleManager.js#L24-L32)を繰り返し呼び出すことでアニメーションさせる

```javascript
_process() {
   this.particles.forEach((p) => {
       // 粒子を移動させる
       p.move(this.wind);
   })

   this._render();
   requestAnimationFrame(this._process.bind(this));
}
```


## 実行

```bash
$ git clone https://github.com/hiyuzawa/wind_visualize.git
$ cd wind_visualize/
$ npm install
$ ./node_modules/.bin/gulp

# ブラウザで http://127.0.0.1:8000 にアクセス
```

![wind.png](https://qiita-image-store.s3.amazonaws.com/0/36803/a2d2c655-0cbe-5e08-8173-33c31623fa18.png "wind.png")


## 備考

本記事やプログラムは
[風データをビジュアルに表現する - Techscore Blog](
http://www.techscore.com/blog/2015/12/13/wind_visualization/)を多大に参考させて頂いています。

技術系の記事投稿先を自前のBlogに移しました。初心者向け,AWSなど幅広いTechジャンルで記事投稿がんばってますので、よろしかったらこちらも参照ください！

https://hiyuzawa.jp/