//
// Javascript to load a GPS gpx format track and using the Google
// maps API with AJAX draw this map onto a web page. All informaiton
// including times, description etc are taken from the .gpx file
// (which needs to be renamed .xml to be loaded here).
//
// The Title/Description seems to be an extension to the standard
// gpx XML so is dependent on the program creating the gpx file.
// This currently works with Memory-Map v5 and SportTracks gpx
// files.
//
// (C) John Bigg  john at bigg me uk
// 1.0 1st Feb 2008
// 1.1 9th Mar 2008 - minor update for SportTracks v2
// 1.2 29th Jun 2010 - add Terrain/Physical map type
//
//<![CDATA[

//
// Globals
var map;							// The map object!
var markerIcon;
var arrowIcon;
var base;							// URL base
var markerson = 1;		// Boolean
var markers = [];			// Start, Finish etc
var arrowfreq = 30;		// Arrow frequency
var month = new Array (
		"January",
		"February",
		"March",
		"April",
		"May",
		"June",
		"July",
		"August",
		"September",
		"October",
		"November",
		"December" );
var maptypes = new Array (
		G_NORMAL_MAP,
		G_SATELLITE_MAP,
		G_HYBRID_MAP,
		G_PHYSICAL_MAP );

//
// Create a lettered marker.
function letterMarker(point, letter) {
	var letteredIcon = new GIcon(markerIcon);
	letteredIcon.image = base + "icons/marker" + letter + ".png";
	return new GMarker(point, { icon:letteredIcon });
}

//
// Format and ISO 8601 GPX time in a human readable format.
// The mask specifies if date/time or both are required.
function formatTime(datetime, fmtmask) {
	var dt = datetime.split('T');
	var t = dt[1].replace('Z', ' GMT');
	var d = dt[0].split('-');
	var fdate = d[2] + ' ' + month[d[1] - 1] + ' ' + d[0];
	var fmt = '';
	if (fmtmask & 2) {
		fmt += fdate;
	}
	if (fmtmask & 1) {
		if (fmt) { fmt += ' '; }
		fmt += t;
	}
	return fmt;
}

//
// Calculate elapsed time from 2 clock times assuming there is less than
// 24 hours between them.
function elapsedTime(begin, end) {
	var el = '';
	var bs = seconds(begin);
	var es = seconds(end);
	if (bs > es) { es += 86400 }
	var secs = es - bs;

	var hr = Math.floor(secs / 3600);
	var min = Math.floor((secs % 3600) / 60);
	var sec = secs % 60
	if (hr) { el += hr + ' hr ';}
	el += min + ' min ' + sec + ' sec';
	return el;
}

//
// Return a clock time as a number of seconds since
// midnight.
function seconds(isotime) {
	var t = isotime.split(/T/)[1].replace(/Z/, '').split(':');
	return t[0] * 3600 + t[1] * 60 + parseInt(t[2]);
}
	
//
// Toggle markers on the map on or off.
function togMarker() {
		var i;
	if (markerson) {
		for (i = 0; i < markers.length; i++) {
		  markers[i].hide();
		}
		markerson = 0;
	} else {
		for (i = 0; i < markers.length; i++) {
		  markers[i].show();
		}
		markerson = 1;
	}
}

//
// Calculate the bearing in degrees between two points
// with north == 0 increasing clockwise.
function bearing(from, to) {
	// Convert to radians.
	var lat1 = from.latRadians();
	var lon1 = from.lngRadians();
	var lat2 = to.latRadians();
	var lon2 = to.lngRadians();

	// Calculate the angle.
	var angle = - Math.atan2( Math.sin( lon1 - lon2 ) * Math.cos( lat2 ), Math.cos( lat1 ) * Math.sin( lat2 ) - Math.sin( lat1 ) * Math.cos( lat2 ) * Math.cos( lon1 - lon2 ) );
	if ( angle < 0.0 )
angle  += Math.PI * 2.0;

	// Convert back to degrees.
	angle = angle * 180.0 / Math.PI;		// degrees per radian calculation
	angle = angle.toFixed(1);

	return angle;
}

//
// Add an arrow every X points based on the previous and next
// points.
function addArrows(points,zoom) {
	for (var i=1; i < points.length-1; i++) {
		if (i % arrowfreq == 0) {
			var dir = bearing(points[i-1], points[i+1]);
			// We have icons for every 3 degrees
			dir = Math.round(dir/3) * 3;
			arrowIcon.image = base  + "icons/narrow" + dir + ".png";
			map.addOverlay(new GMarker(points[i], arrowIcon));
		}
	}
}

function doMap() {
if (GBrowserIsCompatible()) {
	// Set some defaults
	var trkwidth = 3;
	var trkopacity = 0.8;
	var trkcolour = "#7A96DF"; // #0000C0
	var trkfile = '';
	var miles = 0;	// Don't use miles by default


	// 0 - normal, 1 - satellite, 2 - hybrid
	var maptype = G_HYBRID_MAP;

	//
	// Get any parameters from the URL
	var URL = document.URL;
	base =  URL.substr(0,URL.lastIndexOf('/')+1);
	var params = URL.replace(/.*\.(html|htm|php)\?/,"").split("&");
	for (var p = 0; p < params.length; p++) {
		var param = params[p].split("=",2);
		switch (param[0]) {
			case 'colour':
			case 'color':
			case 'c':
				trkcolour = '#' + String(param[1]);
				break;

			case 'freq':
			case 'f':
				arrowfreq = parseInt(param[1]);
				break;

			case 'miles':
			case 'm':
				miles = parseInt(param[1]);
				break;

			case 'track':
			case 'trk':
			case 't':
				trkfile = param[1];
				break;

			case 'width':
			case 'w':
				trkwidth = param[1];
				break;

			case 'y':
			case 'type':
				maptype = maptypes[parseInt(param[1])];
				break;
		}
	}

	//
	// Create map object
	map = new GMap2(document.getElementById("map"), {mapTypes:maptypes});
	map.addControl(new GLargeMapControl());
	map.addControl(new GMapTypeControl());
	map.addControl(new GScaleControl( )); 

	//
	// Load and parse the GPS GPX (XML) file.
	var bounds = new GLatLngBounds();
	var request = GXmlHttp.create();
	var XML;
	if (trkfile) {
		XML = base + trkfile + '.xml';
	} else {
		XML = URL.replace(/\.(html|htm|php).*$/,".xml");
	}

  //
  // Create the marker icons
	markerIcon = new GIcon();
	markerIcon.shadow = base + "icons/shadow50.png";
	markerIcon.iconSize = new GSize(20, 34);
	markerIcon.shadowSize = new GSize(37, 34);
	markerIcon.iconAnchor = new GPoint(9, 34);
	markerIcon.infoWindowAnchor = new GPoint(9, 2);
	markerIcon.infoShadowAnchor = new GPoint(18, 25);

  //
	// Create the direction arrow icons
	arrowIcon = new GIcon();
	arrowIcon.iconSize = new GSize(16,16);
	arrowIcon.shadowSize = new GSize(0,0);
	arrowIcon.iconAnchor = new GPoint(8,8);
	arrowIcon.infoWindowAnchor = new GPoint(0,0);

	//
	// When the data is ready, do this
	request.onreadystatechange = function() {
		if (request.readyState == 4) {
			var gpxDoc = request.responseXML;
			if( !gpxDoc ) {
				alert("Could not load GPX file " + XML);
			} else if( !gpxDoc.documentElement ) {
				alert("Document " + URL + "\nwas not recognized by the XML loader");
			} else {
				//
				// Get track details from GPX file.

				// The description differs on the creator of the GPX file and how
				// we parse this is browser dependent!
				// IE seems to need st:activity whereas mozilla does not want the st:
				// Maybe if I knew XML better I'd uderstand this!
				var activity;
				var description = '';
				// First for SportTracks v1
				activity = gpxDoc.documentElement.getElementsByTagName("st:activity");
				if (!activity.length) { // Try mozilla flavour
					activity = gpxDoc.documentElement.getElementsByTagName("activity");
				}
				if (activity.length) {
					description = activity[0].getAttribute("location");
				// Otherwise SportTracks v2 & Memory-Map
				} else {
					var name = gpxDoc.documentElement.getElementsByTagName("name");
					if (name.length) {
						description = GXml.value(name[0]);
					}
				}

				if (!description) { description = 'GPS Track'; }
				document.title =
					document.getElementById("heading").innerHTML =
//				document.getElementById("heading").firstChild.nodeValue =
					description.replace(/\d{2,4}-\d{2}-\d{2,4}\s*/, '');
				// I date some things and I do not want this included.

				//
				//	Parse tracks in <trk></trk>
				var trk = gpxDoc.documentElement.getElementsByTagName("trk");
				if (trk.length) {
					//
					// Process each point of track. We ignore all but first track.
					var point;
					var points = [];
					var lat, lon;
					var start, finish;
					var distance = 0;
					var zoomlevel;
					var trkpt = trk[0].getElementsByTagName("trkpt");
					for (var k = 0; k < trkpt.length; k++) {
						lat = parseFloat(trkpt[k].getAttribute("lat"));
						lon = parseFloat(trkpt[k].getAttribute("lon"));
						point = new GLatLng(lat, lon);
						points.push(point);
						bounds.extend(point);
						if (k == 0) {
							// time is in ISO 8601 format
							start = GXml.value(trkpt[k].getElementsByTagName("time")[0]);
							markers.push(letterMarker(new GLatLng(lat, lon), 'S'));
						} else {
							distance += point.distanceFrom(points[k-1]);
						}
					}

					//
					// Set the centre and zoom level of the map
					// based on the points of the GPS track.
					zoomlevel = map.getBoundsZoomLevel(bounds);
					map.setCenter(bounds.getCenter(), zoomlevel, maptype);
					markers.push(letterMarker(new GLatLng(lat, lon), 'F'));
					map.addOverlay(new GPolyline(points, trkcolour, trkwidth, trkopacity));
					for (var i = 0; i < markers.length; i++) {
						map.addOverlay(markers[i]);
					}
					addArrows(points, zoomlevel);
					// Convert to km to the nearest 100th.
					if (miles) {
						distance *= 0.621371192;
					}
					distance = Math.round(distance / 10) / 100;
					distance += miles ? ' miles' : ' km';
					finish = GXml.value(trkpt[k-1].getElementsByTagName("time")[0]);
					document.getElementById("start").innerHTML = formatTime(start, 1);
					document.getElementById("finish").innerHTML = formatTime(finish, 1);
					document.getElementById("date").innerHTML = formatTime(start, 2);
					document.getElementById("distance").innerHTML = distance;
					document.getElementById("elapsed").innerHTML = elapsedTime(start,finish);
				} // trk
			} // gpxDoc
		} // readyState
	} // function

	request.open("GET", XML, true);
	request.send(null);
} else {
	alert("Sorry, your browser is not compatible do display GPS maps");
}
}
