GPSリアルタイムトラッキングしてみた

秋月のGPS受信機 GT-730F/L と PHP を使い、id:hidep22:20080503:1209815231 のようなことができないかと悶々としていたが、シリアルポートProxyを使い実装できたのでメモしておく。
AIR-EDGEとの組み合わせで、やっとカーナビ?が使えるようになった。

準備

  1. Windows XP SP3 Homeを起動する
  2. PHPでsocketが使えるようにしておく
  3. Apache 2.2でPHPが使えるようにする
  4. GT-730F/L のシリアルポートを serproxy に設定する。
  5. serproxyを実行しておく
  6. コマンドラインでget_gps_log.phpを実行し、「OK,...」が表示されれば準備OK

手順

ブラウザでHTML(gps.html)を開くだけ。

  1. HTML(gps.html)中でjavascriptPHPファイルを定期的に呼ぶ(AJAX)。
  2. PHPファイル(get_gps_log.php)がソケットで serproxy 越しにシリアルポートの出力を得る。

HTML(gps.html)

 <!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>GPS</title>
 <script type="text/javascript" src="jquery.js"></script>
 <script type="text/javascript" src="http://maps.google.com/maps?file=api&amp;v=2&amp;sensor=true_or_false&amp;key=(API_KEY)"></script>
 </head>
 <body>
 <form action="#" method="get" onsubmit="return false">
 経度<input type="text" id="lat" size="10" />
 緯度<input type="text" id="lng" size="10" />
 速度<input type="text" id="speed" size="6" />
 向き<input type="text" id="course" size="6" />
 <input type="checkbox" id="repeat" value="1"/><label for="repeat">繰り返し更新する</label>
 ※serproxyを起動すること!
 </form>
 <div id="map_canvas" style="width: 980px; height: 450px"></div>
 <script type="text/javascript">
 var map;
 var zoom = 15;
 var poli;
 
 function set_center()
 {
 	$.get("get_gps_log.php", function(data){
 		if (data.substr(0,2) != 'OK') return;
 		va = data.split(","); //0:ステータス,1:経度,2:緯度,3:速度,4:向き
 		$('#lat').val(va[1]);
 		$('#lng').val(va[2]);
 		$('#speed').val(va[3]);
 		$('#course').val(va[4]);
 		var p = new GLatLng(va[1], va[2]);
 		map.setCenter(p, zoom);
 		var latlngs = create_arrow(p, 60, 80, va[4]);
 		if (typeof poli != 'undefined') map.removeOverlay(poli);
 		poli = new GPolygon(latlngs, "#ff0000", 5, 0.5, "#ff6666", 0.1);
 		map.addOverlay(poli);
 		if ($('#repeat').attr('checked')) {
 			setTimeout( "set_center()", 5000);
 		}
 	});
 }
 
 /**
  * 矢印型の経緯度配列を返す。
  *
  * @param GLatLng latlng 地図の中心経緯度
  * @param ingeger w      矢印の幅。ピクセル数
  * @param integer h      矢印の高さ。ピクセル数
  * @param integer deg    矢印の向き。角度。上(北):0, 右(東):90,..
  */
 function create_arrow(latlng, w, h, deg)
 {
 	//基準点の位置取得
 	var point = map.fromLatLngToContainerPixel(latlng);
 	var x = point.x;
 	var y = point.y;
 	//回転角度をrad変換し、向きを逆転
 	var a = -2 * Math.PI * deg / 360;
 	//右下
 	var latlng2 = map.fromContainerPixelToLatLng(point_rotate( w/2,  h*.2, a, x, y));
 	//上
 	var latlng3 = map.fromContainerPixelToLatLng(point_rotate( 0,   -h*.8, a, x, y));
 	//左下
 	var latlng4 = map.fromContainerPixelToLatLng(point_rotate(-w/2,  h*.2, a, x, y));
 	var latlngs = [];
 	latlngs.push(latlng);
 	latlngs.push(latlng2);
 	latlngs.push(latlng3);
 	latlngs.push(latlng4);
 	latlngs.push(latlng);
 	return latlngs;
 }
 
 /**
  * (x1,y1) を a だけ回転させた結果を返す。
  *
  * @param integer x1	
  * @param integer y1	
  * @param integer a		回転角rad
  * @param integer x0	X軸オフセット
  * @param integer y0	Y軸オフセット
  * @return GPoint
  */
 function point_rotate(x1, y1, a, x0, y0)
 {
 	var x = Math.round( x1 * Math.cos(a) + y1 * Math.sin(a));
 	var y = Math.round(-x1 * Math.sin(a) + y1 * Math.cos(a));
 	return new GPoint(x + x0, y + y0);
 }
 
 $(document).ready(function(){
 	if (GBrowserIsCompatible()) {
 		map = new GMap2(document.getElementById("map_canvas"));
 		map.addControl(new GLargeMapControl());
 		GEvent.addListener(map, "zoomend", function(old_zoom, new_zoom) {
 			zoom = new_zoom;
 		});
 		setTimeout( "set_center()", 5000);
 	}
 });
 $(document).unbind(function(){
 	GUnload();
 });
 
 $('#repeat').change(function(){
 	if ($(this).attr('checked')) {
 		setTimeout( "set_center()", 5000);
 	}
 });
 
 </script>
 </body>
 </html>

PHPファイル(get_gps_log.php)

 <?php
 // Arduino Serial Proxy 経由で GPS ログを取得する
 // http://www.arduino.cc/en/Main/Software
 
 // GPS受信機出力データ書式
 // NMEA 0183 Standard Version 3.0
 // http://ssro.ee.uec.ac.jp/ssro/uchuu-tsuushin/gps/GPS-data-format.html
 
 define('MY_HOST', 'localhost');
 define('MY_PORT', 5332);
 
 //GPS出力
 $out = getLog(MY_HOST, MY_PORT);
 if ($out) {	
 	$out = split(',', $out);
 	//緯度[度] latitude
 	$lat = calc($out[3]);
 	if ($out[4] == 'S') $lat = -$lat;
 	//経度[度] longitude
 	$lng = calc($out[5]);
 	if ($out[6] == 'W') $lng = -$lng;
 	//速度[Km/h] 元はノット
 	$speed = $out[7] * 1.852;
 	//向き[度] 北:0度, 東:90度, ...
 	$cource = $out[8];
 	echo  "OK,{$lat},{$lng},{$speed},{$cource}";
 } else {
 	echo 'NG';
 }
 exit;
 
 /**
  * NMEA-0813フォーマットの経緯度をGoogleMapで読める経緯度に変換する
  * GPS出力が「13443.6343」の場合、134度43分6343=134度43分38秒058を示す
  * GoogleMapでは度分秒でなく度だけ使用するため、秒換算の経緯度を3600で割る
  */
 function calc($str)
 {
 	if (!preg_match('/^(\d+)(\d\d)\.(\d+)$/', $str, $ma)) return false;
 	$hour = $ma[1];
 	$min = $ma[2];
 	$sec = $ma[3] * 60 / 10000;
 	return ($hour * 3600 + $min * 60 + $sec) / 3600;
 }
 
 /**
  * GPS LOGを得る
  * @param string  $host Serproxy接続先IP
  * @param integer $port Serproxy接続先ポート
  */
 function getLog($host, $port)
 {
 	//ソケット作成
 	$mysock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
 	if ($mysock == FALSE) return false;
 	//ソケット・コネクト
 	$result = socket_connect($mysock, $host, $port);
 	if (!$result) {
 		socket_clear_error($mysock);
 		socket_close($mysock);
 		return false;
 	}
 	//ソケット・リード
 	while (true) {
 		$buffer = socket_read($mysock, 1024, PHP_BINARY_READ);
 		$pos = strpos($buffer, '$GPRMC');
 		if ($pos !== false) {
 			$out = substr($buffer, $pos);
 			break;
 		}
 	};
 	while (true) {
 		$buffer = socket_read($mysock, 1024, PHP_BINARY_READ);
 		$pos = strpos($buffer, '*');
 		if ($pos !== false) {
 			$out .= substr($buffer, 0, $pos);
 			break;
 		}
 		$out .= $buffer;
 	}
 	//ソケット・クローズ
 	socket_close($mysock); 
 	return $out;
 }
 ?>