放射線量可視化(2):グラフ用ライブラリの検討/データを集計する
注: この作業によって作成した可視化グラフはhttp://FromTo.Cc/rad/で、結果の定期ツイートは@xckbradで公開されています。
さて、線量計DoseRae2のシリアルポートからデータを取得できるようにした前回からの続きです。
グラフ用のライブラリを選ぼう
さて、線量を可視化するにあたって、グラフをWebに描画するライブラリを選択する必要があります。古典的にはgnuplotやImageMagick、GD::Graphなどがあるのでしょうが、 リアルタイムで更新できるグラフを記述できて、 なおかつ見栄えの良いグラフを生成できるのは、 調べた感じでXML/SWF ChartsとHighcharts JSあたりかと。
双方のDemo Galleryを見ると、どちらもカッコいいグラフがいろいろ並んでいるかと思います。
で、今回はHighcharts JSを用いました。 これは以前某所でXML/SWF Chartsを使ったので、 今回はHighcharts JSを使ってみようかな…、 というのが最大の理由ですが、 結果的に両方使った経験から、 今回に関してはHighcharts JSを利用してよかったと思います。 独断と偏見でこの2つのライブラリを比較するとこんな感じです。
XML/SWF Charts | Highcharts JS | |
---|---|---|
公式サイト | XML/SWF Charts | Highcharts JS |
描画方法 | Flash | SVGまたはVML(IE6〜8) |
見栄え | とても綺麗 | 綺麗 |
データ入力 | XML | JavaScript |
リアルタイムデータ | HTTP GETでのXMLデータ | AjaxのJSON, XML等のデータ |
使うのに必要な技術 | XMLの生成 | JavaScriptプログラミング |
リアルタイム更新に必要な技術 | XMLの生成以外特になし | Ajax(jQuery他のフレームワーク利用可) |
非商用ライセンス | 無料 | 無料 |
非商用制限 | グラフをクリックすると公式サイト | ほぼ制限なし |
商用ライセンス | $49/ドメイン, $599/無制限 | $80/サイト, $360/人, 等 |
時間軸の扱い | 特別扱いなし | 時間軸のサポートが充実 |
iOSデバイス | 非対応 | 対応 |
日本語関係の問題 | 日本語フォントの回転ができない | 気づいた点は特になし |
最大の特徴はHighcharts JSがFlash不要でiOSデバイスにも対応している点 (ただし、Android Browser 2.xは未対応?)。 一方、XMLを生成するだけでリアルタイム更新まで対応できるXML/SWF Chartsは利用が簡単です(次回更新までのタイムアウト値をXMLに記述できる)。
意外にポイントなのは、時系列軸の扱い。 Highcharts JSは横軸が時系列の場合、 期間などに応じて自動的に値をフォーマットしてくれる便利な自動処理が付いていて、 時系列グラフを描くことがとても容易です。
ということで、Highcharts JSはなかなかいいグラフ用ライブラリです。
データの5分単位の集計
さて、前回の内容で、 /tmp/raedata.txtというファイルに現在の線量が5秒おきに更新されるようになったわけですが、これを5分単位で集計するプログラムを作ってみました。 データの格納場所は単に慣れているというだけでPostgreSQLを使いましたが、 明らかにオーバースペックです(笑)。テーブルはこんな感じ。 tstampに時刻を、usvにその時点の測定値(μSv/h)を記録します。
CREATE TABLE radiation ( id SERIAL, tstamp TIMESTAMP WITH TIME ZONE, usv DOUBLE PRECISION );
で、5秒に1回データを読み取り、5分に1回書き出します。 具体的には、AnyEventを使って、
#!/usr/bin/perl use strict; use AnyEvent; use Time::HiRes; use DBI qw(:sql_types); use DBD::Pg qw(:pg_types); sub timer_event1(); sub timer_event2(); my $data_file = '/tmp/raedata.txt'; my %values = (); my $db = DBI->connect ("dbi:Pg:dbname=doserae2", 'getrae', '****パスワード****', { RaiseError => 1, PrintError => 0, AutoCommit => 1, }); if (not $db) { die "connecting database failed"; } my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(time); my $modmin = 5.5 - (($min % 5) + $sec / 60.0); my $cv = AnyEvent->condvar; my $w1 = AnyEvent->timer ( after => 0, interval => 5, cb => \&timer_event1, ); my $w2 = AnyEvent->timer ( after => ($modmin * 60), interval => (5 * 60), cb => \&timer_event2, ); $cv->recv();
のようにして、timer_event1()に5秒ごとに起動されるデータ読み取りルーチンを、timer_event2()に5分毎に起動されるデータ書き出しルーチンを記述します。イベントドリブンの無限ループなので、基本的にこのプログラムは終了しません。
5秒に1度起動される、読み取り部分はこんな感じ。 ファイルの最終更新から5秒以上経っていれば何もしない (何らかの事情で一時的にgetraeが死んでいる)。 データはハッシュ%valuesに格納される。 5分(300秒)以上経ったデータはハッシュから削除される。
sub timer_event1() { my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = stat($data_file); my $cur = time; my $age = $cur - $mtime; if ($age > 5) { # do nothing return; } open my $dataf, '<:utf8', $data_file or die "cannot open $data_file"; my $data = <$dataf>; close $dataf; my $val = 0.0; if ($data =~ /^RAE([\d\.]+)$/) { $val = $1; $values{$cur} = $val; } foreach my $i (keys %values) { if ($i < $cur - 300) { delete $values{$i}; } } }
そして、5分に1回起動される記録部分はこんな感じです。 読み取り部分と同時実行されないように冒頭で2秒スリープ。 その後、現在の%valuesの値の平均を計算し、 データベースに記録します。
sub timer_event2() { sleep 2; my $cnt = 0; my $sum = 0; foreach my $i (keys %values) { $sum += $values{$i}; $cnt++; } if ($cnt) { my $avg = sprintf("%.3f", $sum * 1.0 / $cnt); my $t = localtime; print STDERR "$t $avg\n"; eval { my $sth = $db->prepare(<<EOT); INSERT INTO radiation (tstamp, usv) VALUES (CURRENT_TIMESTAMP, ?) EOT $sth->bind_param(1, $avg + 0.0, {sql_type => SQL_DOUBLE}); $sth->execute(); }; if ($@) { die "DB Error :$@"; } } }
あとは、このプログラムを/rae/sbin/raeminuteという名前で保存して実行ビットを立て、前回と同様の自動監視プログラムをcronで毎分起動し、落ちていればraeminuteを起動するようにしました。
これでめでたく5分おきのデータが記録できるようになりました。
%psql doserae2 pgsql psql (9.0.5) "help" でヘルプを表示します. doserae2=# select * from radiation order by id; id | tstamp | usv ------+-------------------------------+------- 1 | 2011-11-27 17:29:53.412789+09 | 0.075 2 | 2011-11-27 17:34:53.411397+09 | 0.079 3 | 2011-11-27 17:39:53.411472+09 | 0.077 4 | 2011-11-27 17:44:53.411608+09 | 0.076 ...(略)...
そして日曜プログラミングは来週に続く。