PlaceEngineの位置ログをGoogle Maps上にプロット

PlaceEngineには、10秒〜に一度自動的に電測を行ってログを記録するオートスポットという機能があります(「環境設定>ログ」の中)。この機能を使って、MacBook Proで新幹線での移動中(とりあえず横浜〜名古屋辺り)を記録してみました。Mac版では記録した結果は~/Library/PlaceEngine/ 内に保存されます。これをPlaceEngineのサイトにあるログ変換ツールに送信すると、位置情報のログに変換できます。このデータを使って、Googleマップ上に移動経路をプロットするスクリプトを作ってみました。

このスクリプトでプロットした結果のサンプルは、 こんな感じ 。恐らく似たようなことをGPS付きの端末でやって位置登録した人がいるのでしょう、みごとに新幹線の経路上にプロットされてます。^^;

なお、途中明らかに間違った位置にプロットされた点が3つほどあったので、プロット対象から除去してあります(新幹線という高速移動中に位置登録されたためか、新幹線上の名古屋側に「時間的」にずれてしまっていました)。

ただプロットするだけじゃ面白くないので、実時間の100倍速で地図を追尾させる機能を付けてみました。「実時間x100で追尾」ボタンをクリックするとスタート。途中で拡大/縮小もできます。
ついでにPlaceEngine自体にも対応していますので、クライアントをお持ちの方は自分の現在位置へ移動させることも可能です。

以下、プロット用のスクリプトGoogle MapsAPIキー(必須)とPlaveEngineのAPIキー(PEを使う場合のみ)をそれぞれのサイトで取得してスクリプト内の*****部分に設定し、CGI(ruby)の動作するサーバへ設置すれば動作すると思います。CGIを呼び出すとXYTファイルをアップロードする画面になるので、上記サイトでXYT形式に変換したログを送信するとプロット結果が出ます。変換時に住所を付与するよう設定しておくと、住所も出ます。

#!/usr/bin/ruby
require "cgi"
cgi = CGI.new

plotdata = cgi['plotdata']
if (plotdata == "") then
	print <<EOD
Content-type: text/html; charset=UTF-8

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>地図プロット</title>
</head>
<body bgcolor="#dddddd">
<p><a href="http://www.placeengine.com/ui/log">PlaceEngine クライアントログ変換ツール</a>でXYT形式に変換したログファイルをGoogle Maps上にプロットします。</p>
<form method="POST" action="http://localhost/~neocat/utils/maps.cgi" enctype="multipart/form-data">
<table border><tr>
<td>XYT形式ファイル[上限:1MB]:</td>
<td><input type="file" name="plotdata"><input type="submit" value="送信"></td>
</tr></table>
</form>
</body>
</html>
EOD
	exit
end

print <<EOD
Content-type: text/html; charset=UTF-8

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>地図プロット結果</title>
<script src="http://maps.google.com/maps?file=api&amp;v=2&amp;key=*****Google MapsのAPIキー*****" type="text/javascript"></script>
<script src="http://www.placeengine.com/javascripts/pengine.js" type="text/javascript"></script>
<script type="text/javascript">
var points = [
EOD

sum_x = sum_y = 0.0
num = 0
ts = msgs = "";
while (line = plotdata.gets) do
	line.strip!
	if /^#/ !~ line then
		begin
			data = line.split(/ +/, 4)
			puts "	new GLatLng(#{data[1]}, #{data[0]}),"
			ts += "#{data[2]},"
			msgs += "'\##{num+1} #{Time.at(data[2].to_i).strftime('%Y/%m/%d %H:%M:%S')}<br>#{data[3]}',"
			num += 1
		rescue
		end
	end
end

print <<EOD;
];
var msgs = [#{msgs}];
var ts = [#{ts}];
var map;
function load() {
	if (GBrowserIsCompatible()) {
		map = new GMap2(document.getElementById("map"));
		map.addControl(new GMapTypeControl());
		map.addControl(new GOverviewMapControl());
		map.addControl(new GLargeMapControl());
		new GKeyboardHandler(map);
		map.enableContinuousZoom();
		map.enableScrollWheelZoom();
		map.setCenter(points[0], 9);
		map.setMapType(G_HYBRID_MAP);

		var markerIcon = new GIcon();
		markerIcon.image = "http://labs.google.com/ridefinder/images/mm_20_yellow.png";
		markerIcon.shadow = "http://labs.google.com/ridefinder/images/mm_20_shadow.png";
		markerIcon.iconSize = new GSize(12,20);
		markerIcon.shadowSize = new GSize(22,20);
		markerIcon.iconAnchor = new GPoint(6,20);
		markerIcon.infoWindowAnchor = new GPoint(6,1);
		markerIcon.infoShadowAnchor = new GPoint(13,13);

		function addMarker(point, msg) { // make closure for marker
			var marker = new GMarker(point, {icon:markerIcon});
			map.addOverlay(marker);
			GEvent.addListener(marker, "click", function(){ this.openInfoWindowHtml(msg); });
			return marker;
		}
		map.addOverlay(new GPolyline(points, "#ff0000", 8));
		for (var i = 0; i < #{num}; i++)
			addMarker(points[i], msgs[i]);

		document.control.cid.value = 1;
		set_cid();
	}
}
function set_cid() {
	var cid = parseInt(document.control.cid.value)-1;
	if (cid >= 0 && cid < points.length) {
		map.panTo(points[cid]);
		document.getElementById("msg").innerHTML = msgs[cid].replace("<br>", " - ");
	}
	return false;
}
function dec_cid() {
	var new_cid = parseInt(document.control.cid.value) - 1;
	if (new_cid > 0) {
		document.control.cid.value = new_cid;
		set_cid();
		return true;
	}
	return false;
}
function inc_cid() {
	var new_cid = parseInt(document.control.cid.value) + 1;
	if (new_cid <= points.length ) {
		document.control.cid.value = new_cid;
		set_cid();
		return true;
	}
	return false;
}
var autoTimer = false;
function auto_inc() {
	autoTimer = false;
	if (inc_cid()) {
		var cid = parseInt(document.control.cid.value) + 1;
		if (points[cid-1]) {
			autoTimer = setTimeout(auto_inc, (ts[cid-1]-ts[cid-2])*10);
			document.getElementById("b_auto").disabled = true;
			document.getElementById("b_stop").disabled = false;
		} else {
			document.getElementById("b_auto").disabled = false;
			document.getElementById("b_stop").disabled = true;
		}
	}
}
function start_auto_inc() {
	stop_auto_inc();
	var cid = parseInt(document.control.cid.value);
	if (cid <= 0 || cid >= points.length) {
		document.control.cid.value = 1;
		set_cid();
	}
	auto_inc();
}
function stop_auto_inc() {
	if (autoTimer)
		clearTimeout(autoTimer);
	autoTimer = false;
	document.getElementById("b_auto").disabled = false;
	document.getElementById("b_stop").disabled = true;
}
</script>
</head>
<body onload="load()" onunload="GUnload()" bgcolor="#dddddd">
<form style="margin: 3px;" name="control">ID: #<input type="text" name="cid" size="2" value="1">
<input type="submit" value="Jump" onClick="return set_cid()"><input type="button" value="<<" onClick="dec_cid()"><input type="button" value=">>" onClick="inc_cid()">
<input id="b_auto" type="button" value="実時間x100で追尾" onClick="start_auto_inc()">
<input id="b_stop" type="button" value="停止" onClick="stop_auto_inc()" disabled>
<span id="msg"></span>
</form>
<div class="peui" style="margin: 2px 0; border: solid 1px green; padding: 2px; width: 750px; font-size: small; background-color: #fff;">
<input alt="位置を教える" src="http://www.placeengine.com/images/wide_bt2.png" type="image" onclick='pe.registerLocation(map)'>
<input alt="現在地を取得" src="http://www.placeengine.com/images/wide_bt1.png" type="image" onclick='pe.getLocation()' />
<span style="position: relative; top: -5px;" id="pestatus"></span>
<a href="http://www.placeengine.com/"><img border="0" style="margin: 2px; position:absolute; left: 640px" src="http://www.placeengine.com/images/logo.png"></a>
</div>
<script type="text/javascript">
var pe = new PEngine({
  onGetLocation: function(x, y, r, info) {
    if (map != null) map.panTo(new GLatLng(y, x));
    if (document.getElementById("pestatus")!=null) {
      document.getElementById("pestatus").innerHTML = info.addr;
    }
  },
  idstatus:"pestatus",
  appkey:"*****PlaceEngineのAPIキー*****"
});
</script>
<div id="map" style="margin: 3px; width: 750px; height: 500px"></div>
</body>
</html>
EOD