放射線量可視化(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
...(略)...
そして日曜プログラミングは来週に続く。
