駄プログラム日記

自分用のプログラムをいろいろ書いてるのなかで、気がついたことを記しています。

2010.10.16
Erlang B式のExcelマクロ

Erlang B式を計算させるためのexcelマクロです。標準マクロとして登録することでワークシート関数として使えます。 マクロの作成は、

ツール -> マクロ -> Visual Basic Editor
挿入 -> 標準モジュール
ファイル -> 終了してMicrosoft Excelに戻る

そこで、下記の関数を作成。

Function erlang_b(a, s)
    Dim b As Double
    b = 0
    For i = 0 To s
        b = i / a * b + 1
    Next i
    erlang_b = 1 / b
End Function

aは呼量[Erl]。Sは出線数で、呼損率を求めます。

2009.06.07
HTML5のcanvus

HTML5のCANVUS機能を使ってbauncerを作ってみた。とりあえずGoogle Chromeで動作を確認。FireFoxでも見れるはず。 IEについては、explorercanvasで互換表示しています。

動作サンプルはこちら。マウスクリックで動作します。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
	<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
	<meta http-equiv="Content-Language" content="ja" />
	<meta http-equiv="Content-Style-Type" content="text/css" />
	<meta http-equiv="Content-Script-Type" content="text/javascript" />
	<title>Canvas</title>
	<!--[if IE]><script type="text/javascript" src="./excanvas.js"></script><![endif]-->
	<script type="text/javascript">
		var canvas;		//	キャンバスオブジェクト
		var cntxt;		//	コンテキスト
		var xSizeMax = 600;	//	キャンバスのサイズ(x)
		var ySizeMax = 480;	//	キャンバスのサイズ(y)
		var timer;		//	インターバルタイマのID
		var vh;			//	水平方向の速度。ボタンを押している長さに反比例。
		var vv;			//	垂直方向の加速度
		var speed;		//	垂直方向の速度
		var xPos=0;		//	水平方向のボールの場所
		var yPos=200;		//	垂直方向のボールの場所
		var color;		//	指定色

		function init() {
			/*	キャンバスの初期化	*/
			canvas = document.getElementById('bouncecanvas');
			cntxt = canvas.getContext('2d');
			if ( ! canvas || ! canvas.getContext) {
				return false;
			}
		} // init()

		function flick(mode) {
			if (mode==0) {	//	ボタンが押されたとき
				vh = (new Date()).getTime();
			} else {	//	ボタンを離したとき
			/*	離したタイミングでx方向の速度が変わる */
				vh = (new Date()).getTime() - vh;
				vh = xSizeMax/vh;
			/*	いろいろ初期化	*/
				vv = -1;
				speed=0;
				yPos=ySizeMax;
				xPos=0;
				color=0xFF0000;
			/*	描画エリアをクリアする	*/
				cntxt.beginPath();
				cntxt.clearRect(0,0,xSizeMax,ySizeMax);		// 描画エリアをクリア

				timer = setInterval("draw()",10);	// 10ms毎に描画関数を呼び出す。

			}
		} // flick()
	

		function draw() {
		/*	X方向への移動	*/
			xPos += vh;
			if ( xPos > xSizeMax ) {			// 右端まで表示したら終了(タイマをクリア)
				clearInterval(timer);
			}
		/*	y方向への移動	*/
			speed += vv;					// 加速度分スピードアップ
			yPos += speed;					// スピード分 垂直位置を変更
		/*	バウンド処理	*/
			if ( yPos < 0 ) {
				yPos=0;
				speed *= -0.8;
			}
		/*	色のバレルシフト	*/
			if ( 1 & color) {				// 最下位ビットが1ならば
				color >>= 1;
				color += 0x800000;			// 最上位に1ビット追加
			} else {
				color >>= 1;
			}
		/*	描画処理	*/
			cntxt.beginPath();
			cntxt.fillStyle = '#'+color.toString(16);					// #rrggbb形式で色を設定
			cntxt.arc(Math.floor(xPos),ySizeMax-Math.floor(yPos),2,0,Math.PI*2, false);	// 座標系を変換して表示
			cntxt.fill();									// ボールを表示
		}
	</script>
	</head>
	<body onload="init()">
	<h1>bouncer#1</h1>
	<div style="text-align:center;">
	    <canvas style="border:1px solid gray;" id="bouncecanvas" width="600" height="480"></canvas>
	</div>
	<div style="align:center">
	    <input type="button" value="click me" onMouseDown="flick(0)"  onMouseUp="flick(1)" / >
	<div style="align:center">
	</body>
</html>
2008.10.14
Google Earth Plug In

とりあえず、GoogleEarth pluginで軌跡を表示できるようにはなった。

次は、とってきた写真をGoogleEarth上マッピングしたい。KASHMIR 3Dなどを使えばできそうであるが、 それだけを使うのは「鶏を割くに焉んぞ牛刀を用いん」という感じ。なので、perlでKMLを履かせるスクリプトを書いてみた。

以下がそのスクリプトだが、perlのXML系のライブラリはいま一つ使い勝手が悪くて、Geo::GPXなどは使うことをあきらめた。

このスクリプトは、引数として、gpxファイルとexifデータをもった写真のファイルを要求し、標準出力にkmlを吐くというもの。 GPXの時間データ 写真ファイルはglobとして展開するので、ワイルドカードも使えます(unix系では要クオート)。
perl kml-map-photo.pl --gpx="./0730.gpx" --pfile="./images/*.JPG" --correction=-150 > photo-0730.kml
correctinオプションは、カメラの時計とGPSの時間がずれていた場合に補正するためのもの。 非常に遅いが、一応マッピングできた。

#/usr/bin/perl
use strict;
use XML::Simple;
use Getopt::Long;
use Time::Local;
use IO::File;
use Image::ExifTool qw(:Public);
use Geo::GoogleEarth::Document;

#********************************************************************
#********************************************************************
sub gpx2tm($$) {
  # use Time::Local;
  # @$tm : GPXファイルからTrackPointの時系列データを格納するための配列
  #        $tm->[i]->(
  #                               'time' =>    TrackPointのgmt 1900/1/1 0:0:0からの経過秒
  #                               'lat'  =>    TrackPointの緯度
  #                               'lon'  =>    TrackPointの経度
  #                               'ele'  =>    TrackPointの高度
  #                       )
  # @$in : GPXファイルをXL::Simple->XMLin()で展開したハッシュツリー 
        my ($tm,$in) = @_;
        
        my $REF_CHECK = ref($in);       # 引数として引き渡されたリファレンスの種別を判定

        if ($REF_CHECK eq 'HASH') {                     # ハッシュの場合
                if (defined($in->{'lat'})) { # 緯度が記録されていたら、そのリファレンスはtrkptと判断
                        # 時刻形式は、ISO 8601形式 & UTCと決め打ち
                        $in->{'time'} =~ /^(\d+)-(\d+)-(\d+)T(\d+):(\d+):(\d+)Z$/;
                        # 特に問題ないので、UTC = GT 
                        my $gt = timegm($6,$5,$4,$3,$2-1,$1);
                        push(@$tm,{(                            # 無名リファレンスの形式で配列に追加
                                'gmt' => $gt,
                                'lat' => $in->{'lat'},
                                'lon' => $in->{'lon'},
                                'ele' => $in->{'ele'},
                                )}
                        );
                        return $in;             # GPXデータポイントを追加した場合は、処理したハッシュを返す。
                } else {                        # それ以外は、さらに展開
                        foreach my $out (keys(%$in)) {
                                &gpx2tm($tm,$in->{$out}) if ref($in->{$out}) ne 'SCALAR';
                        }
                }
        } elsif ($REF_CHECK eq 'ARRAY') {       # 配列の場合は、その回数分展開し、再帰的に呼び出す
                my $i_max = @$in;
                for ( my $i = $[ ; $i < $i_max ; $i++) {
                        &gpx2tm($tm,$in->[$i]) if ref($in->[$i]) ne 'SCALAR';
                }
        }
        return 0;       # 配列への登録がなかった場合は、0を返す。
} # gpx2tm

#********************************************************************
#********************************************************************
sub exif_times($$$$) {
  #********************************************************************
  # Exifデータから、タイムスタンプを作成する
  # 引数1 タイムゾーン
  # 引数2 時刻補正(GPXデータとEXIFデータの時計ずれ)
  # 引数3 暦時間をキーとしたハッシュ(戻り値)
  #       %ptm     key   --> "exifデータのタイムスタンプ(GMT)"
  #       %ptm     value --> "ファイル名"
  #                                             連写するとバッティングするが、普通は連写データを全部は公開しないので大丈夫。
  # 引数4 画像ファイル配列
  #********************************************************************
        my ($tz,$crct,$ptm,$in) = @_;
        my($exifTool) = new Image::ExifTool;

        foreach my $f ( @$in ) {
                my($exifInfo) = $exifTool->ImageInfo($f);                                            # exif データからタイムスタンプを取得
                $exifInfo->{'DateTimeOriginal'} =~ /(\d+)[:\/](\d+)[:\/](\d+) (\d+):(\d+):(\d+)/;    # 暦時間に変換
                my $lt =  timegm($6,$5,$4,$3,$2-1,$1);
                $f =~ /([^\/\\]*$)/;
                $ptm->{$lt-3600*$tz+$crct} = $1;
        }

} #eixf_times

#********************************************************************
#********************************************************************
sub time_format($$) {
  #********************************************************************
  # タイムスタンプから日時文字列を生成する。
  # 引数1 タイムシリアル
  # 引数2 タイムゾーン
  #********************************************************************
        my ($tserial,$tz) = @_;
        my @tm = gmtime($tserial+$tz*3600);
        return sprintf("%d/%d %02d:%02d:%02d",$tm[4]+1,$tm[3],$tm[2],$tm[1],$tm[0]);
} #time_format

#####################################################################
#
# メインルーチン
#
#####################################################################

#********************************************************************
# 定数
#********************************************************************
my      $base                   = 'url of basement';
my      $image_dir              = 'directory of images';
my      $thumbnail_dir          = 'directory of thumbnails';

#********************************************************************
my      ($gpx_file);                            # 読み込むGPXファイル
my      ($kml_file);                            # KMLファイルの場所指定(未使用)
my      ($photo_glob);                          # 写真保管ディレクトリ
my      ($xml);                                         # GPXファイルの読み込み用
my      (@Placemark);                           # KML出力用
my      (%exif_tm);                                     # EXIFデータ時間
my      ($tz);                                          # タイムゾーン
my      ($crct);                                        # 時刻修正(GPSとカメラの差分)

my $kml = Geo::GoogleEarth::Document->new();

GetOptions('gpx=s' => \$gpx_file, 'kml=s' => \$kml_file, 'pfile=s' => \$photo_glob, 'TZ=i' => \$tz, 'correction=i' => \$crct);

if ( $gpx_file ) {                              # GPXファイルが指定されていた場合はXMLデータとして変数にすべて読み込む。
        open FH, $gpx_file;
        local $/;
        $xml = <FH>;
        close FH;
}

my $parser = XML::Simple->new;
my $parsed_xml = $parser->XMLin($xml);

#********************************************************************
# デフォルト値
#********************************************************************
$tz = +9 if !defined($tz);              # EXIFデータのタイムゾーン。指定されなかった場合はJST(+9)。
$crct = 0 if !defined($crct);   # EXIFデータとカメラの時間差補正。

#print "TimeZone : $tz \t Time Correction : $crct\n";

my @timestump;
&gpx2tm(\@timestump,$parsed_xml);
@timestump = sort { $a->{'gmt'} <=> $b->{'gmt'} } @timestump;       # 時刻順にソート

my @photo_files = glob $photo_glob;     # pfileで指定された文字列をglobとして展開。

&exif_times($tz,$crct,\%exif_tm,\@photo_files);

my $min_array = $[;                                             # タイムスタンプ配列の最小値
my $max_array = $#timestump;                    # タイムスタンプ配列の最大値

foreach my $tm (sort keys(%exif_tm)) {
        my $startp  = $min_array;
        my $endp        = $max_array;
        if ($timestump[$startp]->{'gmt'} > $tm)   {               # GPXデータの最小値より小さい場合は次のデータを処理
                next;
        } elsif ($timestump[$endp]->{'gmt'} < $tm) {      # GPXデータの最大値より大きくなったら終了
                last;
        }
        for ( my $p = int(($startp+$endp)/2); ; ) {
                my $tmp;
                if ($timestump[$p]->{'gmt'} == $tm) {
                        $kml->Placemark(
                                name => &time_format( $tm, $tz ),
                                lat => $timestump[$p]->{'lat'},
                                lon => $timestump[$p]->{'lon'},
                                alt => $timestump[$p]->{'ele'},
                                description =>
                                '<a class="gge_photo" href="'.$base.$image_dir.$exif_tm{$tm}.'" target="_photo">'.
                                '<img src="'.$base.$thumbnail_dir.$exif_tm{$tm}.'" /></a>',
                        );
                        last;
                } elsif($timestump[$p]->{'gmt'} > $tm) {
                        if ($endp>$p) {
                                $endp=$p;
                                $p = int(($startp+$endp)/2);
                        } else {
                                $kml->Placemark(
                                        name => &time_format( $tm, $tz ),
                                        lat => ($timestump[$p]->{'lat'}+$timestump[$startp]->{'lat'})/2,
                                        lon => ($timestump[$p]->{'lon'}+$timestump[$startp]->{'lon'})/2,
                                        alt => ($timestump[$p]->{'ele'}+$timestump[$startp]->{'ele'})/2,
                                        description =>
                                        '<a class="gge_photo" href="'.$base.$image_dir.$exif_tm{$tm}.'" target="_photo">'.
                                        '<img src="'.$base.$thumbnail_dir.$exif_tm{$tm}.'" /></a>',
                                );
                                last;
                        }
                } else {
                        if ($startp<$p){
                                $startp=$p;
                                $p = int(($startp+$endp)/2);
                        } else {
                                $kml->Placemark(
                                        name => &time_format( $tm, $tz ),
                                        lat => ($timestump[$p]->{'lat'}+$timestump[$endp]->{'lat'})/2,
                                        lon => ($timestump[$p]->{'lon'}+$timestump[$endp]->{'lon'})/2,
                                        alt => ($timestump[$p]->{'ele'}+$timestump[$endp]->{'ele'})/2,
                                        description =>
                                        '<a class="gge_photo" href="'.$base.$image_dir.$exif_tm{$tm}.'" target="_photo">'.
                                        '<img src="'.$base.$thumbnail_dir.$exif_tm{$tm}.'" /></a>',
                                );
                                last;
                        }
                }
        }
}

print $kml->render;