///////////////////////////////////////////////////////////////////////////////
// loadgpx.4.js
//
// Javascript object to load GPX-format GPS data into Google Maps.
//
// Copyright (C) 2006 Kaz Okuda (http://notions.okuda.ca)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
//
// If you use this script or have any questions please leave a comment
// at http://notions.okuda.ca/geotagging/projects-im-working-on/gpx-viewer/
// A link to the GPL license can also be found there.
//
///////////////////////////////////////////////////////////////////////////////
//
// History:
//    revision 1 - Initial implementation
//    revision 2 - Removed LoadGPXFileIntoGoogleMap and made it the callers
//                 responsibility.  Added more options (colour, width, delta).
//    revision 3 - Waypoint parsing now compatible with Firefox.
//    revision 4 - Upgraded to Google Maps API version 2.  Tried changing the way
//               that the map calculated the way the center and zoom level, but
//               GMAP API 2 requires that you center and zoom the map first.
//               I have left the bounding box calculations commented out in case
//               they might come in handy in the future.
//    revision 5 - Removed redundant code, added opacity support (AWW)
//    revision 6 - Extend marker handling based on <type> tag (AWW)
//                 Type = "" display <cmt> element
//                 Type = "TrailStartPoint" display and format contents of <desc>
//                        elements
//    revision 7 - Add support for gpxext xml elements
//    revision 8 - Display waypoint comments in click info tab
//    revision 9 - Add support for hyperlink in map pin tab
//
// Author: Kaz Okuda
// URI: http://notions.okuda.ca/geotagging/projects-im-working-on/gpx-viewer/
//
///////////////////////////////////////////////////////////////////////////////

function GPXParser(xmlDoc, map)
{
	this.xmlDoc = xmlDoc;
	this.map = map;
	this.trackcolour = "#ff00ff"; // red
	this.trackopacity = 0.5; // 50% opacity
	this.trackwidth = 5;
	this.mintrackpointdelta = 0.001;
	this.markerList = new Array();
	this.sideBarHtmlList = new Array();
	this.infoTabTripletList = new Array();
	this.noNameSpace = typeof document.createElementNS == 'undefined';
}

// Set the colour of the track line segements.
GPXParser.prototype.SetTrackColour = function(colour)
{
	this.trackcolour = colour;
}

// Set the opacity of the track line segements.
GPXParser.prototype.SetTrackOpacity = function(opacity)
{
	this.trackopacity = opacity;
}

// Set the width of the track line segements
GPXParser.prototype.SetTrackWidth = function(width)
{
	this.trackwidth = width;
}

// Set the minimum distance between trackpoints.
// Used to cull unneeded trackpoints from map.
GPXParser.prototype.SetMinTrackPointDelta = function(delta)
{
	this.mintrackpointdelta = delta;
}

GPXParser.prototype.TranslateName = function(name)
{
	if (name == "wpt")
	{
		return "Waypoint";
	}
	else if (name == "trkpt")
	{
		return "Track Point";
	}
}

GPXParser.prototype.CreateMarker = function(point) {
    var lon = parseFloat(point.getAttribute("lon"));
    var lat = parseFloat(point.getAttribute("lat"));
    var html = '<font face="Arial, Helvetica, sans-serif">';
    var name = "";
    var description = "";
    var details = "";
    var grade = "";
    var links = "Click links for more information";
    var desc = "";
    var i = 0;
    var linkText = "";
    var linkHref = "";
    var linkElement;
    var gpxext = "";
    if (point.getElementsByTagName("name").length > 0) {
        for (i = 0; i < point.getElementsByTagName("name").item(0).childNodes.length; i++) {
            name = point.getElementsByTagName("name").item(0).childNodes[i].nodeValue;
        }
        html += name + "<br>";
    }
    if (point.getElementsByTagName("type").length > 0)
        switch (point.getElementsByTagName("type").item(0).childNodes[0].nodeValue) {
        // <type> is "TrailStartPoint" - display all information in the <desc> element  
        case "TrailStartPoint":
            // Extract description, distance, grade, start and links strings
            if (point.getElementsByTagName("desc").length > 0)
                description = point.getElementsByTagName("desc").item(0).childNodes[0].nodeValue;
            if (this.noNameSpace) { gpxext = "gpxext:"; } else { gpxext = ""; }
            if (point.getElementsByTagName(gpxext + "grade").length > 0) {
                grade = point.getElementsByTagName(gpxext + "grade").item(0).childNodes[0].nodeValue;
                details += "<b>Grade:</b> " + grade;
            }
            if (point.getElementsByTagName(gpxext + "start").length > 0)
                details += "<br><b>Start:</b> " + point.getElementsByTagName(gpxext + "start").item(0).childNodes[0].nodeValue;
            if (point.getElementsByTagName(gpxext + "linkswith").length > 0)
                details += "<br><b>Links with:</b> " + point.getElementsByTagName(gpxext + "linkswith").item(0).childNodes[0].nodeValue;
            if (point.getElementsByTagName(gpxext + "timetocomplete").length > 0)
                details += "<br><b>Time to complete route:</b> " + point.getElementsByTagName(gpxext + "timetocomplete").item(0).childNodes[0].nodeValue;
            if (point.getElementsByTagName("link").length > 0) {
                var linkNodeList = point.getElementsByTagName("link")
                for (i = 0; i < linkNodeList.length; i++) {
                    var linkNode = linkNodeList.item(i);
                    linkHref = linkNode.getAttribute("href");
                    linkText = linkNode.getElementsByTagName("text").item(0).childNodes[0].nodeValue;
                    //	                links +=  "<br>" + linkText.link(linkHref).replace(/>/, ' target="_blank" >');
                    links += "<br>" + linkText.link(linkHref).replace(/>/, '>');
                }
            }
            // Populate the info tabs with html strings
            var infoTabs = [
                new GInfoWindowTab(name, '<div class="body" style="width:280px"><p>' + description + '</p></div>'),
                new GInfoWindowTab("More info", '<div class="body" style="width:280px"><p>' + details + '</p></div>'),
                new GInfoWindowTab("Links", '<div class="body" style="width:280px"><p>' + links + '</p></div>')];
            // Create marker, save infoWindowTab information and add click event function
            var marker = new GMarker(new GLatLng(lat, lon), { title: name + " trail: " + grade });
            this.markerList.push(marker);
            this.infoTabTripletList.push(infoTabs);
            this.sideBarHtmlList.push(name);
            GEvent.addListener(marker, "click",
		        function() {
		            marker.openInfoWindowTabsHtml(infoTabs);
		        }
		        );
            break;
        default:
            break;
    }
    else {
        // No <type> specified - currently used by cafe stop  and white road waypoints
        if (point.getElementsByTagName("cmt").length > 0) {
            for (i = 0; i < point.getElementsByTagName("cmt").item(0).childNodes.length; i++) {
                html += point.getElementsByTagName("cmt").item(0).childNodes[i].nodeValue + "<br>";
            };
        };
        if (point.getElementsByTagName("desc").length > 0) {
            for (i = 0; i < point.getElementsByTagName("desc").item(0).childNodes.length; i++) {
                var lines = point.getElementsByTagName("desc").item(0).childNodes[i].nodeValue.split(";");
                for (var i in lines)
                    if (lines[i] != "") {
                    if (lines[i].indexOf("http:") == 0) {
                        html += '<a href="' + lines[i] + '" target="_blank">' + lines[i] + "</a>" + "<br>";
                    }
                    else {
                        html += lines[i] + "<br>";
                    }
                };
            }
        };
        if (point.getElementsByTagName("url").length > 0) {
            for (i = 0; i < point.getElementsByTagName("url").item(0).childNodes.length; i++) {
                var lines = point.getElementsByTagName("url").item(0).childNodes[i].nodeValue.split(";");
                for (var i in lines)
                    if (lines[i] != "") {
                    if (lines[i].indexOf("http:") == 0) {
                        html += '<a href="' + lines[i] + '" target="_blank">' + lines[i] + "</a>" + "<br>";
                    }
                    else {
                        html += lines[i] + "<br>";
                    }
                };
            }
        };
        html += "</font>";
        var marker = new GMarker(new GLatLng(lat, lon), { title: name });
        GEvent.addListener(marker, "click",
	        function() {
	            marker.openInfoWindowHtml(html);
	        }
        );
    };
    this.map.addOverlay(marker);
}


GPXParser.prototype.AddTrackSegmentToMap = function(trackSegment, colour, width, opacity)
{
	var trackpoints = trackSegment.getElementsByTagName("trkpt");
	if (trackpoints.length == 0)
	{
		return; //latlngbounds;
	}

	var pointarray = [];

	// process first point
	var lastlon = parseFloat(trackpoints[0].getAttribute("lon"));
	var lastlat = parseFloat(trackpoints[0].getAttribute("lat"));
	var latlng = new GLatLng(lastlat,lastlon);
	pointarray.push(latlng);

	// Create a marker at the begining of each track segment
	// this.CreateMarker(trackpoints[0]);

	for (var i=1; i < trackpoints.length; i++)
	{
		var lon = parseFloat(trackpoints[i].getAttribute("lon"));
		var lat = parseFloat(trackpoints[i].getAttribute("lat"));

		// Verify that this is far enough away from the last point to be used.
		var latdiff = lat - lastlat;
		var londiff = lon - lastlon;
		if ( Math.sqrt(latdiff*latdiff + londiff*londiff) > this.mintrackpointdelta )
		{
			lastlon = lon;
			lastlat = lat;
			latlng = new GLatLng(lat,lon);
			pointarray.push(latlng);
		}

	}
	var polyline = new GPolyline(pointarray, colour, width, opacity);
	this.map.addOverlay(polyline);
}

GPXParser.prototype.AddTrackToMap = function(track, colour, width, opacity)
{
	var segments = track.getElementsByTagName("trkseg");
	for (var i=0; i < segments.length; i++)
	{
		var segmentlatlngbounds = this.AddTrackSegmentToMap(segments[i], colour, width, opacity);
	}
}

GPXParser.prototype.CenterAndZoom = function (trackSegment, maptype)
{

	var pointlist = new Array("trkpt", "wpt");
	var minlat = 0;
	var maxlat = 0;
	var minlon = 0;
	var maxlon = 0;

	for (var pointtype=0; pointtype < pointlist.length; pointtype++)
	{

		// Center the map and zoom on the given segment.
		var trackpoints = trackSegment.getElementsByTagName(pointlist[pointtype]);

		// If the min and max are uninitialized then initialize them.
		if ( (trackpoints.length > 0) && (minlat == maxlat) && (minlat == 0) )
		{
			minlat = parseFloat(trackpoints[0].getAttribute("lat"));
			maxlat = parseFloat(trackpoints[0].getAttribute("lat"));
			minlon = parseFloat(trackpoints[0].getAttribute("lon"));
			maxlon = parseFloat(trackpoints[0].getAttribute("lon"));
		}

		for (var i=0; i < trackpoints.length; i++)
		{
			var lon = parseFloat(trackpoints[i].getAttribute("lon"));
			var lat = parseFloat(trackpoints[i].getAttribute("lat"));

			if (lon < minlon) minlon = lon;
			if (lon > maxlon) maxlon = lon;
			if (lat < minlat) minlat = lat;
			if (lat > maxlat) maxlat = lat;
		}
	}

	if ( (minlat == maxlat) && (minlat = 0) )
	{
		this.map.setCenter(new GLatLng(49.327667, -122.942333), 14);
		return;
	}

	// Center around the middle of the points
	var centerlon = (maxlon + minlon) / 2;
	var centerlat = (maxlat + minlat) / 2;

	var bounds = new GLatLngBounds(new GLatLng(minlat, minlon), new GLatLng(maxlat, maxlon));

	this.map.setCenter(new GLatLng(centerlat, centerlon), this.map.getBoundsZoomLevel(bounds), maptype);
}

GPXParser.prototype.CenterAndZoomToLatLngBounds = function (latlngboundsarray)
{
	var boundingbox = new GLatLngBounds();
	for (var i=0; i<latlngboundsarray.length; i++)
	{
		if (!latlngboundsarray[i].isEmpty())
		{
			boundingbox.extend(latlngboundsarray[i].getSouthWest());
			boundingbox.extend(latlngboundsarray[i].getNorthEast());
		}
	}

	var centerlat = (boundingbox.getNorthEast().lat() + boundingbox.getSouthWest().lat()) / 2;
	var centerlng = (boundingbox.getNorthEast().lng() + boundingbox.getSouthWest().lng()) / 2;
	this.map.setCenter(new GLatLng(centerlat, centerlng), this.map.getBoundsZoomLevel(boundingbox));
}


GPXParser.prototype.AddTrackpointsToMap = function ()
{
	var tracks = this.xmlDoc.documentElement.getElementsByTagName("trk");

	for (var i=0; i < tracks.length; i++)
	{
		this.AddTrackToMap(tracks[i], this.trackcolour, this.trackwidth, this.trackopacity);
	}
}

GPXParser.prototype.AddWaypointsToMap = function ()
{
	var waypoints = this.xmlDoc.documentElement.getElementsByTagName("wpt");

	for (var i=0; i < waypoints.length; i++)
	{
		this.CreateMarker(waypoints[i]);
	}
}
