Tech Sketch Bucket of Technical Chips by TIS Inc.

Google Maps APIを利用して花粉飛散データを可視化する

Pocket

特定の場所に紐づいたデータの見せ方を考えるとき、地図というのは有力な選択肢になりえます。
この記事ではGoogle Maps JavaScript API v3を用いて、地図上にデータをプロットし、データを視覚的に分かりやすく表現する例を示します。


はじめに

前回の記事では一般的なグラフを用いてデータを表示する方法についてまとめました。
数字の大きさに意味があるデータを表示する場合には前回のようにグラフを使用すると分かりやすく表現することができますが、場所に強く紐づいたデータを表す場合、一般的なグラフを使うよりも地図を利用した方が直感的に分かりやすい表示を行えることがあります。

今回は様々な地点で計測された花粉の飛散データを視覚的に分かりやすい形で表示してみます。

使用するデータ

今回使用するデータはこちらのサイトで公開されている花粉の飛散データです。

環境省花粉観測システム 愛称:はなこさん ( http://kafun.taiki.go.jp/ )

地域毎にデータが公開されていますが、今回は関東地方のデータとして27箇所の地点で計測されたデータを用いました。
データはCSV形式でダウンロードすることができ、以下のような情報を含んでいます。

  • 測定局名(測定地点の施設名)
  • 日時
  • 測定局の種別(都市部/山間部)
  • 花粉飛散数(個/m3)
  • 風向き(16方位)
  • 風速(m/s)
  • 気温(℃)
  • 降水量(mm)

場所によって測定期間が異なりますが、基本的に花粉の飛散が活発となる2月初め~5月末のデータを1時間単位で取得しているため、27地点 x 2880時間で7万8000件ほどのデータを使うことができます。

作成したアプリ

今回のデータのように、場所に紐づくデータを表す場合は地図の上にデータを表現すると視覚的に分かりやすく表現することができます。地図表示としてすぐに思いつくのはGoogle Mapsですが、これはJavaScript向けのAPIが公開されており、これを用いることで自分のサイト上に地図を表示することが可能です。
今回はこのAPIを用いて地図を表示し、同じくこのAPIで提供されている図形描画機能、ヒートマップ描画機能を利用して花粉飛散データを視覚的に分かりやすく表現してみました。

花粉飛散マップ

今回作成したアプリは以下のURLから見ることが出来ます。
理由は後述しますが、データ容量が8MB近くある為、読み込みには時間がかかるかと思います。

花粉飛散マップ ( /pollen/index.html )

introduce.png

IE9 / Firefox 22.0 / Google Chrome 27で動作確認を行っていますが、下部のスライダーを表示するために <input type="range"> を用いています。
Firefox 23以上か、Google Chromeではスライダーが表示されますが、IEやFirefox 22以下はこの記述に未対応の為、スライダー部分が単なるテキストボックスになってしまいます。Firefox 23以上でも一部の見た目が崩れるため、 Google Chromeでの表示を強く推奨します。

browser.png


機能は以下の通りです。

  • 花粉の飛散状況を図形かヒートマップで表示、右上のShape/Heatmapで切替可能
  • 下部のスライダーを動かしたり、カーソルキーの左右で日時を変更
    • (Shift+左右で1日単位、Ctrl+左右で4時間単位)
  • 左下の再生ボタンで自動再生
  • 右上に平均気温と天気(晴れ・雨のみ)を表示

1分半ほど掛かりますが、Shapeモードで2月~3月中旬のデータを再生していただけると花粉症の人が3月を嫌う理由が良くわかるかと思います。

データの整形

それではアプリの裏側を見ていきましょう。まずは環境省のサイトで公開されている花粉飛散データの取得と整形からです。

花粉の飛散データはCSV形式でダウンロードすることが出来ますが、不要なデータも含まれており、JavaScriptで読み込ませるためにはデータを事前に整形しておく方が手軽に取り扱えます。

公開されているデータフォーマット ( http://kafun.taiki.go.jp/DownLoadData/format.csv )を元に、以下の情報を取り出しました。

  • 測定局名 / Position.name
  • 測定局名から検索した緯度・経度情報 / Position.latitude, Position.longitude
  • 花粉飛散数(個/m3) / Data.count
  • 風向き(16方位) / Data.direction
  • 風速(m/s) / Data.speed
  • 気温(℃) / Data.temperature
  • 降水量(mm) / Data.precipitation

schema.png

データ例

今回はJavaScriptから容易にアクセスできることを優先してJSON形式を選択しましたが、データファイルが8MBほどになってしまいました。本来のデータ以外の部分でファイル容量が増えてしまうため、データ容量によっては公開前のminifiedや、多次元配列を用いたデータ表現なども選択肢として上げられます。

Google Maps JavaScript APIの利用申請

地図表示、地図上の扇形の描画、ヒートマップの描画には、いずれもGoogle Maps JavaScript APIの機能を利用しています。
APIを使用する為にはAPI Keyが必要となるため、まずはGoogle API Consoleから以下の手順に従ってサービスの利用申請を行います。

  1. Google API Console( https://code.google.com/apis/console/ )にアクセスします
  2. 左のメニューからServicesを選択します
  3. 表示されたアプリ一覧からGoogle Maps API v3を探してStatusをONに変更します
  4. 左のメニューからAPI Accessを選択します
  5. Simple API Accessの中に表示されているAPI Keyを控えておきます
  6. <script> タグを用いてGoogle Maps JavaScript APIを読み込む際に控えておいたAPI Keyを指定します

地図の表示

それではAPIを用いて地図の表示を行っていきます。
Google Maps JavaScript APIについては詳細なリファレンスと豊富なサンプルが公開されている為、たいていの事はサンプルを元にコードを記述すればできるようになっています。

API Reference( https://developers.google.com/maps/documentation/javascript/reference )
Examples( https://developers.google.com/maps/documentation/javascript/examples/ )

地図の表示についてもほぼサンプルそのままの為、あまり説明する場所がありませんが、今回表示するデータは関東地方の物のため、埼玉県草加市を中心にして地形を表示しています。

緯度・経度の表記と変換

特定の箇所を表示したい場合、Googleの緯度・経度形式は10進法の為、分・秒で表された緯度・経度情報を使うには変換を行う必要があります。
例えばGoogle Maps上で東京都港区にある日本経緯度原点(北緯35度39分29秒1572, 東経139度44分28秒8869)を表示する場合、(35.39291572, 139.44288869)として表示すると、神奈川県藤沢市の全く関係ないところを表示してしまいます。

これは分・秒で表記された位置情報が60進法の為で、以下のように変換を行います。
北緯35度39分29秒1572 = 35 + (1/60) * 39 + (1/3600) * 29.1572 = 35.6580992222222222
東経139度44分28秒8869 = 139 + (1/60) * 44 + (1/3600) * 28.8869 = 139.741357472222

この座標を用いてGoogle Mapsを検索すると、ロシア大使館の裏手が表示され、正しく日本経緯度原点が表示できていることが確認できます。
https://maps.google.co.jp/?q=35.6580992222222,139.741357472222

図形の描画

続いてはShapeモードを見ていきたいと思います。
このモードは風向き、風速、花粉の飛散量から扇形の図形や、円状の図形(無風の場合)を描画することで、花粉の飛散状況を分かりやすく表現することを目指しています。例えば、花粉の飛散がかなり活発になった2013年3月10日のデータを表示すると以下のようになります。

shape.png

地図の上に図形を描画するため、こちらもGoogle Maps APIを用いて描画を行います。
これによって、緯度・経度で指定して図を描画することができ、ドラッグ&ドロップによる地図の移動やズームにも自動的に対応することができます。

Googleのサンプルの中ではPolygon Auto-completionの例が近いかと思います。
Polygon Auto-completion ( https://developers.google.com/maps/documentation/javascript/examples/polygon-autoclose )

特にサンプルと違うことはやっていませんが、ソースコードの中で数字を振った箇所を順に説明していきたいと思います。

[1] 頂点情報の作成

図形を描画するために緯度・経度を使って扇形の頂点情報を作成します。
Google Maps APIでは曲線を描画することが出来ないため、測定地点と、円周上の4頂点、合わせて5頂点を用いて扇形に近い図形を表現しています。
なお、当初は風速をそのまま扇形の半径に適用していましたが、風速が2倍になったときに面積が4倍になってしまい風速の見た目への影響が強すぎるため、風速と面積が比例するように平方根を取るように変更しています。

[2] 図形の色の決定

続いて飛散量の情報に応じて図形の色を決定します。飛散量のデータは1立方メートル当たりの花粉個数となっていますが、データを見てみると最小値0個、最大値71405個、平均105個という、時期に応じて極端に変化の大きいデータになっています。
また、最大値の大きさに目が行きがちですが、実際には1000個を超えるデータは全体の1.78%しか存在しないという特徴もあります。
このように変化の大きいデータを扱うため、今回は以下の表のように、底を2とした対数を取って色(透明度)を決定しています。

chart.png

[3] 描画する多角形の作成

頂点と色が決定したので、Google Map上に描画する多角形を作成します。今回は枠無しの赤い図形とし、[2]で求めた値を透明度にすることで色を変化させています。なお、Google Mapsはメルカトル図法を採用しているため、同じ大きさの扇形でも向きによって微妙に見た目が異なります。

ヒートマップの描画

Google Maps APIが提供してくれるAPIでは図形にグラデーションを使用することが出来ないため、前項のように図形を描画すると描画された部分とされていない部分にハッキリとした差が出てしまいます。また、観測地点から離れるにつれて影響を弱めるといったことも表現しづらくなっています。
これらの問題を解決する方法としてヒートマップの描画を行いました。図形描画と同じ2013年3月10日のデータを表示すると、以下のような見た目になります。

heatmap.png

ヒートマップは交通事故の発生場所など、データの位置情報に基づいて色づけを行い、頻発する地域ほど色を濃くするといった表現を行うことができますが、今回は全てのデータを一律に扱うのではなく、それぞれの観測地点で計測された花粉の飛散量に応じて重み付けを行った上でヒートマップを描画しています。

ヒートマップも公式でサンプルが公開されています。
Heatmaps ( https://developers.google.com/maps/documentation/javascript/examples/layer-heatmap )

ヒートマップの描画コードはカスタマイズしている箇所が多いため、細かく説明しながらコードを見ていこうと思います。

[1] 重み付けの工夫

花粉の飛散量に応じて重みをつけるわけですが、もともとヒートマップは「位置」と「頻度」を表現するのに向いた表現の為、今回のように位置やデータの取得タイミングが固定されたデータを表現する場合には一工夫しないと問題が発生します。

ヒートマップは頻度を表現するのに向いているため、狭い範囲にデータポイントが集中すると個々のデータを合計して評価を行います。つまり、隣接した2件の観測所でそれぞれ10個/m3の花粉が観測された場合と、孤立した観測所で20個/m3の花粉が観測された場合では、ほぼ同様の描画を行います。
これは頻度を表現するヒートマップの特徴ですが、現実的に考えると前者の周囲に漂っている花粉は10個/m3、後者の周囲に漂っている花粉は20個/m3であり、これでは実態に即した表現をすることができません。

そこで、今回は重みの計算式を調整し、花粉の飛散量に応じて明確な差を設定しました。
今回の重み付けでは、飛散量が2倍になると4倍の影響を持つように設定しており、隣接した観測所による影響をある程度抑制できています。

[2] 影響を与える範囲を動的に決定

また、Googleのヒートマップ機能では各データポイントが影響を与える範囲を現実の距離ではなく、地図上のピクセルで指定するようになっています。公式のHeatmapsサンプルでも確認できますが、この方法では拡大・縮小に伴ってヒートマップの見た目が変化してしまいます。
今回は拡大・縮小した場合にもヒートマップの見た目を維持したかったため、拡大・縮小時に自動的に半径を再設定し、常に50kmの範囲に影響を及ぼすように変更してあります。
ただし、ヒートマップの描画は負荷の高い処理になっており、また、負荷が描画する面積に依存するため、1段階(2倍)拡大すると描画負荷が4倍になってしまいます。今回は一定以上の拡大率にした場合にはヒートマップの描画を打ち切るという形で対応しました。

[3] データ更新時の工夫

データの更新時にも工夫が必要です。公式サンプルのように画面表示時に一度だけヒートマップを作成するのであれば問題ないのですが、今回のアプリでは時間軸に沿った再生機能があり、動的に表示を切り替える必要があります。当初は毎回ヒートマップを作成し、古いヒートマップを消して、新しいヒートマップを setMap メソッドでオーバーレイ表示していましたが、ヒートマップの計算は setMap メソッドが呼ばれた後に実行されるため、この方法では古いヒートマップを消してから、計算が終了するまでの間にヒートマップが表示されないタイミングが出来てしまいました。
幸い、ヒートマップを表す google.maps.visualization.HeatmapLayer にはデータを更新するメソッド setData が存在するため、今回はこれを用いてデータの更新を行っています。 setData メソッドを呼んだ後再計算が開始されますが、再計算中は元のヒートマップが表示され続けるため、再生中でも違和感の少ない表現が出来るようになっています。

まとめ

数字だけで表されていた元データと作成したアプリを見比べると、花粉症患者にとっての3月のつらさや、日ごとの変化が分かりやすくなっており、表現手法としては成功かと思います。記事の中でも見てきたようにグラデーションが使えないことによる制約などはありますが、Google Maps JavaScript APIを使用することで詳細な地図の表示やオーバーレイ機能を容易に使うことができるため、位置情報に基づくデータを表現する際には候補として検討してみては如何でしょうか。

エンジニア採用中!私たちと一緒に働いてみませんか?