/////////////////////////////////////////////////////////
// Javascript to load an XML file that contains GPX tags
// and then use Google Map APIs to display the data
//
// NOTE: This javascript requires the definition of the following
// variables:
//
// show_routes -- if set to "on" then any routes in the xml will
//                be shown.
// show_tracks -- if set to "on" then any tracks in the xml will
//                be shown.
// show_waypoints -- if set to "on" then any waypoints in the xml
//                will be shown.
//
// EG: in your .html that calls this script you need to provide
// the following:
//
//
//     <script type="text/javascript">
//     show_tracks = "on";
//     show_routes = "on";
//     show_waypoints = "on";
//     </script>
//
// you should also pass in the xml file using the following:
//     <script type="text/javascript">
//     xml_file = "http://loation-of-the-xml-file-containing-the-gpx-tags";
//     </script>
//
// and in the BODY-TAG of your html you should look like this:
//
// <BODY onload='buildMap(xml_file)'>
//
// Here is an example of GPX xml tags.  This example is of 
// a ROUTE (used by AUTO-ROUTING GPSs)
//
// Other types of "major" tags this program can process are
//  TRACKS, WAYPOINTS...
//
//    <rte>
//      <name>EW-IndianHarbor-WOT</name>
//      <rtept lat="28.151286" lon="-80.599844">
//        <time>2005-06-22T17:55:56Z</time>
//        <name>1302BananaRiver</name>
//        <cmt>1302 Banana River Dr</cmt>
//        <desc>1302 Banana River Dr</desc>
//        <sym>Waypoint</sym>
//      </rtept>
//      <rtept lat="28.431330" lon="-80.788136">
//        <name>Bre1005026</name>
//        <cmt>Grissom Pky</cmt>
//        <desc>Grissom Pky</desc>
//        <sym>Waypoint</sym>
//      </rtept>
//      <rtept lat="28.553597" lon="-82.638057">
//         <ele>3.352800</ele>
//         <time>2005-09-13T15:48:55Z</time>
//         <name>CTRL BAYPORT INN</name>
//         <cmt>03-SEP-05 4:03:57PM</cmt>
//         <desc>03-SEP-05 4:03:57PM</desc>
//         <sym>Flag, Blue</sym>
//      </rtept>
//    </rte>
/////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////
// Global Variables
/////////////////////////////////////////////////////////

// We fill in this to create an icon template
// with which to base creation of the WAYPOINT icons.
var tinyicon = null;

// variables used to control zoom
var maxBounds = new GLatLngBounds();
var routeBounds = new Array();
var trackBounds = new Array();

var tooltip = document.createElement("div");

// Pointer to the Google Map
var map = null;
var wPoints = null;
var zoomLevel = 0;

// Varaibles to plot elevation
var ELEVATIONS_ENABLED = true;
var elevations = new Array();
var nElevations = 0;
var maxele = -10000.0;
var minele = 10000.0;
var elecan = new jsGraphics("elevations_html");

function StringBuffer() { 
  this.buffer = []; 
} 
StringBuffer.prototype.append = function append(string) { 
  this.buffer.push(string); 
  return this; 
}; 
StringBuffer.prototype.toString = function toString() { 
  return this.buffer.join(""); 
}; 
var buf = new StringBuffer();
// buf.append("hello");
// buf.append("world");
// alert(buf.toString());

var routesBuf    = new StringBuffer();
routesBuf.append("Routes (blue):\<br\>");
var tracksBuf    = new StringBuffer();
tracksBuf.append("Tracks (magenta):\<br\>");
var waypointsBuf = new StringBuffer();
waypointsBuf.append("Waypoints:\<br\>");


// Holds arrays rte (route) points each array is a route
var rtGPointArray;
// Holds the polylines for the routes
var polylinesArray;
var lastHighlightedPolyline;
var lastHightlightedRtePtsIndex;

var ROUTE_HIGHLIGHT_COLOR = "#00dd00";

var routeColors = new Array(
"#0000FF",
"#0000FF",
"#0000FF",
"#0000FF",
"#0000FF",
"#0000FF",
"#0000FF",
"#0000FF",
"#0000FF",
"#0000FF",
"#0000FF",
"#0000FF"
);
var maxRouteColors = 12;

 
function processXml() {
   //alert("In processXml()");
   var f = document.user_options;  // This is the form we'll we working with.
   var selectBox = f.xml_file;
   var url = selectBox.options[selectBox.selectedIndex].value;
   //document.forms[0].select.selectedIndex = 2;
 
   buildMap(url);     
   map.centerAndZoom(centerPoint, getZoomLevel(x));
}
 
/////////////////////////////////////////////////////////
// buildMap()
//
// takes a http:// path to the .xml file that
// contains the GPX tags.
//
// It calls initImap which init's the google maps
// then it  calls loadXMLDoc() to load the document 
// NOTE: It is inside the loadXMLDoc that the creation
// of the google maps data is done.
/////////////////////////////////////////////////////////
function buildMap(route_url) {
   if (initImap() == 0) {
      map.clearOverlays();
 
      loadXMLDoc(route_url);
   }
}
 
/////////////////////////////////////////////////////////
// initImap()
//
/////////////////////////////////////////////////////////
function initImap() {
   if (GBrowserIsCompatible()) {
      if (tinyicon == null) {
         tinyicon = new GIcon();
         tinyicon.image = "http://labs.google.com/ridefinder/images/mm_20_green.png";
         tinyicon.shadow = "http://labs.google.com/ridefinder/images/mm_20_shadow.png";
         tinyicon.iconSize = new GSize(14, 14);
         tinyicon.shadowSize = new GSize(14, 14);
         tinyicon.iconAnchor = new GPoint(6, 0);
         tinyicon.infoWindowAnchor = new GPoint(5, 1);
      }

      if (map == null) {
         //alert("Creating a new Map instance.");
         var map_html = document.getElementById("map_html")

         // Note: for some reason GMap2 fails???
         //map = new GMap2(map_html);
         map = new GMap(map_html);

         map.addControl(new GLargeMapControl());
         //map.addControl(new GMapTypeControl());
         map.addControl(new GHierarchicalMapTypeControl());

         map.addControl(new GScaleControl());
         map.addControl(new GOverviewMapControl());

         // Add the new terrain button to the MapTypeControl
         map.addMapType(G_PHYSICAL_MAP);


         // ==== It is necessary to make a setCenter call of some description before adding markers ====
         // ==== At this point we dont know the real values ====
         map.setCenter(new GLatLng(0,0),0);
 
         // ====== set up marker mouseover tooltip div ======
         map_html.appendChild(tooltip);
         tooltip.style.visibility="hidden";

         //            map.centerAndZoom(new GPoint(-81.373973, 28.432274), 9);
         //var bccPoint = new GPoint(-80.67166, 28.16788);
         //var orlandoPoint = new GPoint(-81.373973, 28.432274);
         //map.centerAndZoom(bccPoint, 5);
         //map.centerAndZoom(orlandoPoint, 8);

         //var center = getCenterLatLng();
         //var html = "<span class=\"small\">Wickham Pavilion<br>(next to BCC Campus)</span>";
         //var marker = createMapMarker(new GPoint(-80.67166, 28.16788), html, "red");
         //map.addOverlay(marker);

         // Caution: This causes only one route to show.
         //GEvent.addListener(map, 'addoverlay', function(overlay) {
         //   clearmessage();
         //   if (overlay)
         //   {
         //   }
         //});
         GEvent.addListener(map, 'click', function(overlay, point) {
            if (point) {
               //alert("Clicked on point!!! " + point);
               //map.addOverlay(new GMarker(point));
               document.getElementById('coordinate_html').innerHTML= "Coordinate: " + point;
               //     document.getElementById('A1').innerHTML=xmlhttp.status
               //     document.getElementById('A2').innerHTML=xmlhttp.statusText
               //     document.getElementById('A3').innerHTML=xmlhttp.responseText
               //alert("Clicked on point 1 " + point);
               //document.getElementById('debug').innerHTML="Before createMarker 1 for " + point;

               //map.addOverlay(createMapMarker(point, "", "yellow"));
               //alert("Clicked on point 2 " + point);
            }
         });
         // Display lon/lat above map
         GEvent.addListener(map, "moveend",
            function() {
               var center = map.getCenter();
               document.getElementById("mapinfo_html").innerHTML = 
//                 'Zoom Level: ' + map.getZoomLevel() + ' (v2 level is: ' + map.getZoom() + ')<br>' +
                 'Zoom level: ' + map.getZoom() + '<br>' +

                 'Map Center: ' + center.toString();
            }
         );
      }
      return 0;
   } // End if (GBrowserIsCompatible())
   else {
      alert("Sorry, your browser is not compatible with the Google Maps API.\n\n" +
            "Google Maps currently supports recent versions of Firefox/Mozilla, IE 5.5+,\n" +
            "Safari 1.2+, and sort of supports Opera. IE 5.0 is not supported.");
      return -1;
   }
}

function getMarkerHTTP(markerStyle) {
   switch (markerStyle){
   case "Shopping Center":
      return "http://www.flacyclist.com/gmap/symbols/shopping_center.png";
   case "Fast Food":
      return "http://www.flacyclist.com/gmap/symbols/fast_food.png";
   case "Gas Station":
      return "http://www.flacyclist.com/gmap/symbols/gas_station.png";
   case "Convenience Store":
      return "http://www.flacyclist.com/gmap/symbols/convenience_store.png";
   case "Car":
      return "http://www.flacyclist.com/gmap/symbols/car.png";
   case "Parking Area":
       return "http://www.flacyclist.com/gmap/symbols/parking_area.png";
   case "Information":
       return "http://www.flacyclist.com/gmap/symbols/information.png";
   case "Lodging":
      return "http://www.flacyclist.com/gmap/symbols/lodging.png";
   case "Restaurant":
      return "http://www.flacyclist.com/gmap/symbols/restaurant.png";
   case "Airport":
      return "http://www.flacyclist.com/gmap/symbols/airport.png";
   case "Car Rental":
      return "http://www.flacyclist.com/gmap/symbols/car_rental.png";
   case "Truck Stop":
      return "http://www.flacyclist.com/gmap/symbols/truck_stop.png";
   case "Tunnel":
      return "http://www.flacyclist.com/gmap/symbols/tunnel.png";
   case "Ground Transportation":
      return "http://www.flacyclist.com/gmap/symbols/ground_transportation.png";
   case "Toll Booth":
      return "http://www.flacyclist.com/gmap/symbols/toll_booth.png";

   case "Park":
      return "http://www.flacyclist.com/gmap/symbols/park.png";
   case "Zoo":
      return "http://www.flacyclist.com/gmap/symbols/zoo.png";
   case "Stadium":
      return "http://www.flacyclist.com/gmap/symbols/stadium.png";
    case "Bridge":
       return "http://www.flacyclist.com/gmap/symbols/bridge.png";
    case "Crossing":
       return "http://www.flacyclist.com/gmap/symbols/crossing.png";
    case "Exit":
       return "http://www.flacyclist.com/gmap/symbols/exit.png";

   case "Bike Trail":
      return "http://www.flacyclist.com/gmap/symbols/bike_trail.png";
   case "Trail Head":  // Garmin
      return "http://www.flacyclist.com/gmap/symbols/trail_head.png";
   case "Trailhead":   // Some other GPS software uses this name
      return "http://www.flacyclist.com/gmap/symbols/trail_head.png";
   case "Swimming Area":
      return "http://www.flacyclist.com/gmap/symbols/swimming_area.png";
   case "Scenic Area":
      return "http://www.flacyclist.com/gmap/symbols/scenic_area.png";
   case "Summit":
      return "http://www.flacyclist.com/gmap/symbols/summit.png";

   case "Waypoint":
      return "http://www.flacyclist.com/gmap/symbols/waypoint.png";
   case "Flag":
      return "http://www.flacyclist.com/gmap/symbols/flag.png";
   case "Flag, Blue":
      return "http://www.flacyclist.com/gmap/symbols/flag_blue.png";
   case "Flag, Red":
      return "http://www.flacyclist.com/gmap/symbols/flag_red.png";
   case "Flag, Green":
      return "http://www.flacyclist.com/gmap/symbols/flag_green.png";
   case "Light":
      return "http://www.flacyclist.com/gmap/symbols/light.png";
   case "Navaid, Red":
      return "http://www.flacyclist.com/gmap/symbols/navaid_red.png";
    case "Dot":
       return "http://www.flacyclist.com/gmap/symbols/white_dot.png";
    case "White Dot":
       return "http://www.flacyclist.com/gmap/symbols/white_dot.png";
    case "White Buoy": // This is in the PBP file
       return "http://www.flacyclist.com/gmap/symbols/white_buoy.png";
    case "Buoy, White": // This is in some other files
       return "http://www.flacyclist.com/gmap/symbols/white_buoy.png";

    case "Yellow Buoy":
       return "http://www.flacyclist.com/gmap/symbols/yellow_buoy.png";
    case "Blue Buoy":
       return "http://www.flacyclist.com/gmap/symbols/blue_buoy.png";
    case "Circle with X":
       return "http://www.flacyclist.com/gmap/symbols/circle_with_x.png";
 
    case "Geocache":
       return "http://www.flacyclist.com/gmap/symbols/geocache.png";
    case "Geocache Found":
       return "http://www.flacyclist.com/gmap/symbols/geocache_found.png";

    case "Drinking Water":
       return "http://www.flacyclist.com/gmap/symbols/drinking_water.png";
    case "Picnic":
       return "http://www.flacyclist.com/gmap/symbols/picnic.png";
    case "Campground":
       return "http://www.flacyclist.com/gmap/symbols/campground.png";
    case "Restroom":
       return "http://www.flacyclist.com/gmap/symbols/restroom.png";
    case "Shower":
       return "http://www.flacyclist.com/gmap/symbols/shower.png";
    case "Residence":
       return "http://www.flacyclist.com/gmap/symbols/residence.png";
    case "Museum":
       return "http://www.flacyclist.com/gmap/symbols/museum.png";
    case "Cemetery":
       return "http://www.flacyclist.com/gmap/symbols/cemetery.png";
    case "Amusement Park":
       return "http://www.flacyclist.com/gmap/symbols/amusement_park.png";
    case "Police Station":
       return "http://www.flacyclist.com/gmap/symbols/police_station.png";

    case "Military":
       return "http://www.flacyclist.com/gmap/symbols/military.png";
    case "Mine":
       return "http://www.flacyclist.com/gmap/symbols/mine.png";
    case "Oil Field":
       return "http://www.flacyclist.com/gmap/symbols/oil_field.png";
    case "Tall Tower":
       return "http://www.flacyclist.com/gmap/symbols/tall_tower.png";
    case "Tall Tower, Green":
       return "http://www.flacyclist.com/gmap/symbols/tall_tower_green.png";
    case "Tall Tower, Red":
       return "http://www.flacyclist.com/gmap/symbols/tall_tower_red.png";
    case "Short Tower":
       return "http://www.flacyclist.com/gmap/symbols/short_tower.png";
    case "Short Tower, Green":
       return "http://www.flacyclist.com/gmap/symbols/short_tower_green.png";
    case "Short Tower, Red":
       return "http://www.flacyclist.com/gmap/symbols/short_tower_red.png";
    case "City (Small)":
       return "http://www.flacyclist.com/gmap/symbols/city_small.png";
    case "City (Medium)":
       return "http://www.flacyclist.com/gmap/symbols/city_medium.png";
    case "City (Large)":
       return "http://www.flacyclist.com/gmap/symbols/city_large.png";
    case "City (Capitol)":
       return "http://www.flacyclist.com/gmap/symbols/city_capitol.png";

    case "Beach":
       return "http://www.flacyclist.com/gmap/symbols/beach.png";
    case "Dam":
       return "http://www.flacyclist.com/gmap/symbols/dam.png";
    case "Levee":
       return "http://www.flacyclist.com/gmap/symbols/levee.png";

    case "Marina":
       return "http://www.flacyclist.com/gmap/symbols/marina.png";
    case "Boat Ramp":
       return "http://www.flacyclist.com/gmap/symbols/boat_ramp.png";
    case "Dock":
       return "http://www.flacyclist.com/gmap/symbols/dock.png";
    case "Bait and Tackle":
       return "http://www.flacyclist.com/gmap/symbols/bait_and_tackle.png";
    case "Shipwreck":
       return "http://www.flacyclist.com/gmap/symbols/shipwreck.png";
    case "Fishing Area":
       return "http://www.flacyclist.com/gmap/symbols/fishing_area.png";
    case "Drop Off":
       return "http://www.flacyclist.com/gmap/symbols/drop_off.png";
    case "Weed Bed":
       return "http://www.flacyclist.com/gmap/symbols/weed_bed.png";
    case "Anchor":
       return "http://www.flacyclist.com/gmap/symbols/anchor.png";
    case "Anchor Prohibited":
       return "http://www.flacyclist.com/gmap/symbols/anchor_prohibited.png";
    case "Reef":
       return "http://www.flacyclist.com/gmap/symbols/reef.png";
    case "Danger Area":
       return "http://www.flacyclist.com/gmap/symbols/danger_area.png";
    case "Skull and Crossbones":
       return "http://www.flacyclist.com/gmap/symbols/skull_and_crossbones.png";

    default:
       return "http://www.flacyclist.com/gmap/symbols/navaid_red.png";
   }
}

/////////////////////////////////////////////////////////
// createMapMarker()
//
// This function is passed a color (marker style)
// and a string (html) that is to be displayed in the
// info window when the marker is selected.
/////////////////////////////////////////////////////////
function createMapMarker(point, name, desc, color) {
   //document.getElementById('debug').innerHTML="createMarker 1 for " + point;

   var icon = new GIcon(tinyicon);
   var html = name;
   if (desc != "") {
      html = html + '<br>[' + desc + "]";
   }

   icon.image = getMarkerHTTP(color);

   var marker = new GMarker(point, icon);
   // === store the name so that the tooltip function can use it ===
   marker.tooltip = '<div class="tooltip">' + html + '</div>';

   // Show this marker's index in the info window when it is clicked
   GEvent.addListener(marker, 'click', function() {
      if (html == "") {
         alert("no html for marker");
         map.removeOverlay(marker);
      }
      else {
         marker.openInfoWindowHtml(html);
      }
   });

   //  ======  The new marker "mouseover" and "mouseout" listeners  ======
   GEvent.addListener(marker,"mouseover", function() {
      showTooltip(marker);
   });        
   GEvent.addListener(marker,"mouseout", function() {
      tooltip.style.visibility="hidden";
   });        

    return marker;
}
 
/////////////////////////////////////////////////////////
// loadXMDDoc()
//
// This function downloads the contents of the file passed
// in by url and stores it into the global variable
// xmlhttp.
//
// The document is to call the function state_Change
// if the document changes (download is completed)
/////////////////////////////////////////////////////////
 
// The pointer to the downloaded xml document
var xmlhttp;

function loadXMLDoc(url) {
   // code for Mozilla, etc.
   if (window.XMLHttpRequest) {
      //alert("Attempting to get GPX file '" + url + "'");

      xmlhttp=new XMLHttpRequest();
      xmlhttp.onreadystatechange=state_Change;
      xmlhttp.open("GET",url,true);
      xmlhttp.send(null);
   }
   // code for IE
   else if (window.ActiveXObject) {
      xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
      if (xmlhttp) {
         xmlhttp.onreadystatechange=state_Change;
         xmlhttp.open("GET",url,true);
         xmlhttp.send();
      }
   }
}
 
/////////////////////////////////////////////////////////
// state_Change
//
// This is the real beginning of the processing of the 
// gpx xml file...
//
// This function is called when the state of the xml document
// changes.  In this case we are only concerned when the
// download completes... Once that occurs we start
// processing the document.
/////////////////////////////////////////////////////////
function state_Change() {
   // if xmlhttp shows "loaded"
   if (xmlhttp.readyState==4) {
      // if "OK"
      if (xmlhttp.status==200) {
         //alert("XML data OK, show_routes=" + show_routes);
         var xmlDoc = xmlhttp.responseXML;
         var nRtePts = 0;
         var nTrkPts = 0;

         if (null == xmlDoc ) {
            alert("xmlDoc is NULL.  XMLHttpRequest status is: " + xmlhttp.statusText);
         }

         if (show_routes == "on" ) {
            var rteElems = xmlDoc.documentElement.getElementsByTagName("rte");
            nRtePts = plotRoute(rteElems);
            
            //alert("Finished routes.");
         }

         if (show_tracks == "on" ) {
            //alert("Starting on track elements...");
            var trkElems = xmlDoc.documentElement.getElementsByTagName("trk");
            nTrkPts = plotTracks(trkElems, nRtePts);
            plotElevations();
            
            //alert("Finished tracks.");
         }

         if (show_waypoints == "on" ) {
            //alert("Starting on waypoints elements...");
            wPoints = xmlDoc.documentElement.getElementsByTagName("wpt");
            var zoomFlag;
            //alert("nrte = " + nRtePts + "nTrk =" + nTrkPts);
         
            if (nRtePts == 0 && nTrkPts == 0) {
               zoomFlag = 1;
            }
            else {
               zoomFlag = 0;
            }
            plotWayPoints(wPoints, zoomFlag);
            
            //alert("Finished waypoints.");            
         }

         //document.getElementById('A1').innerHTML=xmlhttp.status
         //document.getElementById('A2').innerHTML=xmlhttp.statusText
         //document.getElementById('A3').innerHTML=xmlhttp.responseText
         document.getElementById("buttons_html").innerHTML = 
         '<table border=1>' +
             '<tr><td><input type="button" value="Reset Zoom" id="zoomButton" onclick="showRoute(-1)"></input></td></tr>' +
             '<tr><td>' + routesBuf.toString() + '</td></tr>' +
             '<tr><td>' + tracksBuf.toString() + '</td></tr>' +
             '<tr><td style="white-space: nowrap;">' + waypointsBuf.toString() + '</td></tr>' +
         '</table>';

         // ===== Start with an empty GLatLngBounds object =====     

         // ===== determine the zoom level from the maxBounds =====
         map.setZoom(map.getBoundsZoomLevel(maxBounds));

         // ===== determine the centre from the maxBounds ======
         var clat = (maxBounds.getNorthEast().lat() + maxBounds.getSouthWest().lat()) /2;
         var clng = (maxBounds.getNorthEast().lng() + maxBounds.getSouthWest().lng()) /2;
         map.setCenter(new GLatLng(clat,clng));
      }
      else {
         alert("Problem retrieving XML data:" + xmlhttp.statusText);
      }
   }
}
 
/////////////////////////////////////////////////////////
// plotRoutes
//
// This function is passed the <rte> sections of the 
// xml document.  If the <rte> section length is not zero
// then process each of the <rte> (routes) and
// create a google Maps poly-line from the <rtepts> 
// (routes points) in each. Then attach the poly line to
// the map object.
//
// Also if there were routes then try to calculate a
// zoomlevel for the initial map display
/////////////////////////////////////////////////////////
function plotRoute(rteElems) {
   if (rteElems.length > 0) {
      var rtName, name;
      var maxLon = -1000;
      var minLon =  1000;
      var maxLat = -1000;
      var minLat =  1000;
      var lon, lat;
      //var markers = new Array();
      //var markerIdx = 0;
      //var markers   = new Array(500); // TJ: debug
      // Set global route points array
      rtGPointArray = new Array(rteElems.length);
      //maxRoutes = rteElems.length;
      routesBuf.append('<UL>');

      for (var i=0; i<rteElems.length; i++) {
         //rtName = rteElems[i].getElementsByTagName("name")[0].childNodes[0].nodeValue;
         var rtNodeList = rteElems[i].getElementsByTagName("name");
         if (rtNodeList.length > 0) {
	   	rtName = rtNodeList[0].childNodes[0].nodeValue;
	   }
	   else {
	   	rtName = "route " + i;
	   }

         routesBuf.append(
           '<li style="color: ' + routeColors[i % maxRouteColors] + '" >' +
           '<input type="button" value="' + rtName + '" id="rtButton' + i + '" onclick="showRoute(' + i + ')"></input></li>');

         routeBounds[i] = new GLatLngBounds();
 
         var rtePtElems = rteElems[i].getElementsByTagName("rtept");

         if (rtePtElems.length > 0) {
            var gPoints = new Array(rtePtElems.length);
            var name, cmt, html, color;
 
            for (var j=0; j<rtePtElems.length; j++) {
               lon = parseFloat(rtePtElems[j].getAttribute("lon"));
               lat = parseFloat(rtePtElems[j].getAttribute("lat"));
 
               maxLon = Math.max(maxLon, lon);
               minLon = Math.min(minLon, lon);
               maxLat = Math.max(maxLat, lat);
               minLat = Math.min(minLat, lat);
 
               maxBounds.extend(new GLatLng(lat,lon));
               routeBounds[i].extend(new GLatLng(lat,lon));
      
               gPoints[j] = new GPoint(lon, lat);
 
               var point = new GPoint(lon, lat);
               //var marker = createMapMarker(gPoints[j], "rtept", "orange");
               //name = rtePtElems[j].getElementsByTagName("name")[0].childNodes[0].nodeValue;
 
               // Debug code...
               //if (prevPoint != null) {
               //   var d = gcd(prevPoint.x, prevPoint.y, point.x, point.y);
               //   if (d > 50) {
               //      alert("Distance from '" + prevName + "' to '" + name + " is " + d + " miles.");
               //   }
               //}
               //prevPoint = point;
               //prevName  = name;
            } // End of FOR each rtePtElems
 
            rtGPointArray[i] = gPoints;
         }

      } // End of FOR each rteElems

	
	polylinesArray = new Array(rtGPointArray.length);

      for (var m=0; m<rtGPointArray.length; m++) {
         //alert("Plotting polyline " + (m+1) + " of " + rtGPointArray.length);
         polylinesArray[m] = plotPolyline(rtGPointArray[m], routeColors[i % maxRouteColors], 3, 0.4, 200);
      }

      zoomAndCenter(maxLon, minLon, maxLat, minLat);

      routesBuf.append('</UL>');
   } 

   return rteElems.length;
}
 
 
/////////////////////////////////////////////////////////
// plotWaypoints
//
// This function is passed the <wpt> sections of the 
// xml document.  If the <wpt> section length is not zero
// then process each of the <wpt> (waypoints) and
// create a google Maps marker.
//
// THe map marker is based off of the waypoints 
// <sym> tag and passes that to the createMapMarker()
//
// Also if there were no routes and no tracks then try
// to calculate a zoomlevel for the initial map display
/////////////////////////////////////////////////////////
function plotWayPoints(wPoints, zoomFlag) {
   if (wPoints.length > 0) {
      var maxLon = -1000;
      var minLon =  1000;
      var maxLat = -1000;
      var minLat =  1000;
      var name;
      var lon;
      var lat;
      var description;
      var markerText;
      var markerStyle = "none";
      var markerPtr;

      //alert("zoom = " + zoomFlag);
      for (var i=0; i<wPoints.length; i++) {
         lon = parseFloat(wPoints[i].getAttribute("lon"));
         lat = parseFloat(wPoints[i].getAttribute("lat"));
         // calculate center
         maxBounds.extend(new GLatLng(lat,lon));
         maxLon = Math.max(maxLon, lon);
         minLon = Math.min(minLon, lon);
         maxLat = Math.max(maxLat, lat);
         minLat = Math.min(minLat, lat);
         //alert("maxLon=" + maxLon + ",minLon=" + minLon +
         //     ",maxLat=" + maxLat + ",minLat=" + minLat);
      }
	
      // RegExp for the " character. We must change " to ' for text in
      // a button; otherwise text is truncated at first ".
	var re = new RegExp("\"", "g");

      for (var i=0; i<wPoints.length; i++) {
         //name = wPoints[i].getElementsByTagName("name")[0].childNodes[0].nodeValue;
         var wptNodeList = wPoints[i].getElementsByTagName("name");
	   if (wptNodeList.length > 0) {
	   	name = wptNodeList[0].childNodes[0].nodeValue;
	   }
	   else {
	   	name = "waypoint " + i;
	   }
         
         lon = parseFloat(wPoints[i].getAttribute("lon"));
         lat = parseFloat(wPoints[i].getAttribute("lat"));
         markerStyle = "none";
         markerPtr = wPoints[i].getElementsByTagName("sym")[0];
         if ( markerPtr ) {
            markerStyle = markerPtr.childNodes[0].nodeValue;
         }
         markerPtr = wPoints[i].getElementsByTagName("desc")[0];
         if (markerPtr) {
            description = markerPtr.childNodes[0].nodeValue;
		description = description.replace(re, "'");
         }
         else {
            description = "";
         }
         
         waypointsBuf.append(  
             '<img src="' + getMarkerHTTP(markerStyle) +
             '"> <input type="button" value="' + name + 
             '" title="' + description +
             '" id="wayButton' + i + 
             '" onclick="showWaypoint(' + i + ')" ></input><br>');
	   
         markerText = name + '<br>' + description;
         var wPointMarker = createMapMarker(new GPoint(lon, lat), name, description, markerStyle);
         map.addOverlay(wPointMarker);
      }
      waypointsBuf.append('<BR>');
   }
}

/////////////////////////////////////////////////////////
// plotTracks
//
// This function is passed the <trk> sections of the 
// xml document.  If the <trk> section length is not zero
// then process each of the <trk> (tracks) and
// create a google Maps poly-line from the <trkpts> 
// (track points) in each. Then attach the poly line to
// the map object.
//
// Also on the side, if not already done by the 
// plotRoute function... Calculate a ZoomLevel
// for the tracks.
/////////////////////////////////////////////////////////
function plotTracks(trkElems, nRtePts) {
   if (trkElems.length > 0) {
      var name;
      var maxLon = -1000;
      var minLon =  1000;
      var maxLat = -1000;
      var minLat =  1000;
      var lon, lat;
      tracksBuf.append('<UL>');

      for (var i=0; i<trkElems.length; i++) {
         //name = trkElems[i].getElementsByTagName("name")[0].childNodes[0].nodeValue;
	   var trkNodeList = trkElems[i].getElementsByTagName("name");
	   if (trkNodeList.length > 0) {
	   	name = trkNodeList[0].childNodes[0].nodeValue;
	   }
	   else {
	   	name = "track " + i;
	   }

         tracksBuf.append('<LI style="color: ' + routeColors[i%maxRouteColors] + '" ><input type="button" value="' + name + '" id="trButton'+i+'" onclick="showTrack(' + i + ')"></input>');
         var trkPointElems = trkElems[i].getElementsByTagName("trkpt");
         trackBounds[i] = new GLatLngBounds();

         // Add the trail head marker
         lon = parseFloat(trkPointElems[0].getAttribute("lon"));
         lat = parseFloat(trkPointElems[0].getAttribute("lat"));
         var html = '<span class="small">Trail Start<br>' + name + '</span>';
         var trlHeadMarker = createMapMarker(new GPoint(lon, lat), html, "" , "purple");

         var gPoints = new Array(trkPointElems.length);
         var maxSize = 200;
         var ele;

         for (var j=0; j<trkPointElems.length; j++) {
            lon = parseFloat(trkPointElems[j].getAttribute("lon"));
            lat = parseFloat(trkPointElems[j].getAttribute("lat"));
                    
            if (ELEVATIONS_ENABLED == true) {
               //TLJ: trackpoint may not have elevations
               eleElems = trkPointElems[j].getElementsByTagName("ele");
               if (eleElems.length > 0) {
                  ele = parseFloat(eleElems [0].childNodes[0].nodeValue);

                  // only calc elevations for first track for now
                  if (i==0) {
                     if (ele) {
                        elevations[nElevations] = ele;
                        nElevations++;
                        if (ele  < minele) {
                           minele = ele;
                        }
                        if (ele > maxele) {
                           maxele = ele;
                        }
                        //if (maxele < minele) {
                        //  var temp;
                        //  temp = maxele;
                        //  maxele = minele;
                        //  minele = temp;
                        //}
                     }
                  }
               }
            }

            maxBounds.extend(new GLatLng(lat,lon));
            trackBounds[i].extend(new GLatLng(lat,lon));
            maxLon = Math.max(maxLon, lon);
            minLon = Math.min(minLon, lon);
            maxLat = Math.max(maxLat, lat);
            minLat = Math.min(minLat, lat);
            gPoints[j]  = new GPoint(lon, lat);
         }

         plotPolyline(gPoints, "#FF00FF", 3, 0.4, 200);
      }
      tracksBuf.append('</UL>');
   }
   return trkElems.length;
}
 
// Plot a polyline in segments to avoid a bug in Mozilla
function plotPolyline(gPoints, colorHex, weight, tranparency, chunkSize) {
   var size1    = gPoints.length / chunkSize; // 2682 / 100 = 26
   var lastSize = gPoints.length % chunkSize; // 2682 % 100 = 82

   // Array to repeatedly fill with points for each polyline segment.
   // Make it one bigger than the chunkSize to fill-in the gap between segments.
   var array = new Array(chunkSize + 1);
   var idx = 0;
   var polyline;

   for (var i=0; i < (size1 - 1); i++) {
      var jLast;
      for (var j=0; j<chunkSize; j++) {
         array[j] = gPoints[idx];
         idx = idx + 1;
         jLast = j;
      }

      if (lastSize > 0) {
         // Manually add duplicate of first point in the next section to
         // avoid a gap between polylines.
         array[jLast+1] = gPoints[idx];
      }

      polyline = new GPolyline(array, colorHex, weight, tranparency);
      //alert("  Overlaying polyline (" + i + ")...");
      map.addOverlay(polyline);
   }

   // Plot the last chuck
   array = new Array(lastSize);
   for (var j=0; j<lastSize; j++) {
      array[j] = gPoints[idx];

      idx = idx + 1;
   }
   polyline = new GPolyline(array, colorHex, weight, tranparency);
   //alert("  Overlaying polyline (" + gPoints.length + " points)...");
   map.addOverlay(polyline);
   
   return polyline;
}
 
function getZoomLevel(distance) {
   var max = 3000;
   if (distance > max) {
      return 13;
   }
   else if (distance > (max/2)) {
      return 12;
   }
   else if (distance > (max/4)) {
      return 11;
   }
   else if (distance > (max/8)) {
      return 10;
   }
   else if (distance > (max/16)) { // 187
      return 9;
   }
   else if (distance > (max/32)) { // 94
      return 8;
   }
   else if (distance > (max/64)) { // 47
      return 7;
   }
   else if (distance > (max/128)) { // 24
      return 6;
   }
   else if (distance > (max/256)) { // 12
      return 5;
   }
   else if (distance > (max/512)) { // 6
      return 4;
   }
   else {
      return 3;
   }
}      

var centerPoint;
var x;
 
function zoomAndCenter(maxLon, minLon, maxLat, minLat) {
   var spanLon = Math.abs(maxLon - minLon);
   var spanLat = Math.abs(maxLat - minLat);
   //alert("maxLon=" + maxLon + ",minLon=" + minLon +
   //      ",maxLat=" + maxLat + ",minLat=" + minLat +
   //      ",spanLon=" + spanLon + ",spanLat=" + spanLat);

   centerPoint = new GPoint(avg(maxLon, minLon),
                            avg(maxLat, minLat));

   x = gcd(maxLon, minLat, minLon, maxLat);
   map.centerAndZoom(centerPoint, getZoomLevel(x));
}

// ====== This function displays the tooltip ======
// it can be called from an icon mousover or a side_bar mouseover
function showTooltip(marker) {
   tooltip.innerHTML = marker.tooltip;
   var point=map.getCurrentMapType().getProjection().fromLatLngToPixel(map.getBounds().getSouthWest(),map.getZoom());
   var offset=map.getCurrentMapType().getProjection().fromLatLngToPixel(marker.getPoint(),map.getZoom());
   var anchor=marker.getIcon().iconAnchor;
   var width=marker.getIcon().iconSize.width;
   var pos = new GControlPosition(G_ANCHOR_BOTTOM_LEFT, new GSize(offset.x - point.x - anchor.x + width,- offset.y + point.y +anchor.y)); 
   pos.apply(tooltip);
   tooltip.style.visibility="visible";
}

//} // End of buildXML()

function plotElevations() {
   //alert("Plotting elevations: nElevations=" + nElevations + ", ELEVATIONS_ENABLED=" + ELEVATIONS_ENABLED);

   if (nElevations == 0 || ELEVATIONS_ENABLED == false) {
   	//alert("Exiting plotElevations!!!");
      return;
   }

   // jg_doc.setColor("#00ff00"); // green
   //  jg_doc.fillEllipse(100, 200, 100, 180); // co-ordinates related to the document
   //  jg_doc.setColor("maroon");
   //  jg_doc.drawPolyline(new Array(50, 10, 120), new Array(10, 50, 70));
   //  jg_doc.paint(); // draws, in this case, directly into the document
   //elecan.htm += '<table align="left" width="354" cellpadding="0">' +
   // '<tr><td>Min elevation=' + minele + ', Max elevation=' + maxele + '</tr></td>' +
   // '<tr><td height="150" valign="top">' +
   // '   <div id="elevations" style="position:relative; width:350px; height:150px;">' +
   // '</tr></td></table>';

   elecan.htm += '<table align="left" width="354" cellpadding="0">' +
   '<tr><td height="150" valign="top">' +
   '<div id="elevations" style="position:relative; width:350px; height:150px;">'

   elecan.setColor("#ff0000"); // red
   // elecan.drawLine(10, 113, 220, 55);
   var j=0;
   var xscale = 700.0/(nElevations);
   var yscale = 200.0/(maxele - minele); 
   var oldele = elevations[0];
   var x = Array(nElevations);
   var y = Array(nElevations);

   //for (j=0;j<nElevations;j++)
   // {
   //elecan.drawLine(j*xscale,200-Math.round((oldele-minele)*yscale),(j+1)*xscale,200-Math.round((elevations[j]-minele)*yscale)); 
   //oldele = elevations[j];
   // }
   for (j=0; j<nElevations; j++) {
      x[j] = j * xscale;
      y[j] = 200 - Math.round((elevations[j] - minele) * yscale); 
   }

   elecan.drawPolyline(x, y);
   
   elecan.setColor("#000000"); // black

   // NOTE: origin (0,0) is the upper-left corner
   elecan.drawLine(0,200, 700,200);  // x-axis
   elecan.drawLine(0,  0,   0,200);  // y-axis'
   elecan.drawString("Max=" + Math.round(maxele * 3.28084) + " ft", 5, 0);
   elecan.drawString("Min=" + Math.round(minele * 3.28084) + " ft", 5, 200);
   elecan.paint();
}

function showRoute(i) {
//   setmessage("<b>Repositioning... Please Wait</b>");
//   setTimeout("sr("+i+")",1);
//}
//   
//function sr(i) {
   //GLog.write("showRoute: route selected is "+i);

   if (i>=0) {
      map.removeOverlay(polylinesArray[i]);
      if (lastHighlightedPolyline) {
         //alert("removing polyline idx = " + lastHightlightedRtePtsIndex);
	   map.removeOverlay(lastHighlightedPolyline);
         polylinesArray[lastHightlightedRtePtsIndex] = plotPolyline(rtGPointArray[lastHightlightedRtePtsIndex], 
                      "#0000FF", 3, 0.4, 200);   
      }
      //alert("Plotting polyline idx = " + i);
      lastHightlightedRtePtsIndex = i;
      lastHighlightedPolyline = plotPolyline(rtGPointArray[i], ROUTE_HIGHLIGHT_COLOR, 3, 0.4, 200);
      
      // ===== determine the zoom level from the maxBounds =====
      map.setZoom(map.getBoundsZoomLevel(routeBounds[i]));

      // ===== determine the center from the maxBounds ======
      var clat = (routeBounds[i].getNorthEast().lat() + routeBounds[i].getSouthWest().lat()) /2;
      var clng = (routeBounds[i].getNorthEast().lng() + routeBounds[i].getSouthWest().lng()) /2;
      map.setCenter(new GLatLng(clat,clng));
      //GLog.write("showRoute: lat,lon="+clat+","+clng+" zoom="+map.getBoundsZoomLevel(routeBounds[i]));
   }
   else {
      // ===== determine the zoom level from the maxBounds =====
      map.setZoom(map.getBoundsZoomLevel(maxBounds));

      // ===== determine the centre from the maxBounds ======
      var clat = (maxBounds.getNorthEast().lat() + maxBounds.getSouthWest().lat()) /2;
      var clng = (maxBounds.getNorthEast().lng() + maxBounds.getSouthWest().lng()) /2;
      map.setCenter(new GLatLng(clat,clng));
   }
}

function showTrack(i) {
//      setmessage("<b>Repositioning... Please Wait</b>");
//      setTimeout("st("+i+")",1);
//   }
//
//function st(i) {
   //GLog.write("showRoute: route selected is "+i);
   if (i>=0) {
      // ===== determine the zoom level from the maxBounds =====
      map.setZoom(map.getBoundsZoomLevel(trackBounds[i]));

      // ===== determine the centre from the maxBounds ======
      var clat = (trackBounds[i].getNorthEast().lat() + trackBounds[i].getSouthWest().lat()) /2;
      var clng = (trackBounds[i].getNorthEast().lng() + trackBounds[i].getSouthWest().lng()) /2;
      map.setCenter(new GLatLng(clat,clng));
      //GLog.write("showRoute: lat,lon="+clat+","+clng+" zoom="+map.getBoundsZoomLevel(routeBounds[i]));
   }
   else {
      // ===== determine the zoom level from the maxBounds =====
      map.setZoom(map.getBoundsZoomLevel(maxBounds));

      // ===== determine the centre from the maxBounds ======
      var clat = (maxBounds.getNorthEast().lat() + maxBounds.getSouthWest().lat()) /2;
      var clng = (maxBounds.getNorthEast().lng() + maxBounds.getSouthWest().lng()) /2;
      map.setCenter(new GLatLng(clat,clng));
   }
}
   
function showWaypoint(i) {
   var lat = parseFloat(wPoints[i].getAttribute("lat"));
   var lon = parseFloat(wPoints[i].getAttribute("lon"));
   zoomToLatLon(lat, lon, 15);
}

// latInput  Text field holding latitude  +-90.00000
// lonInput  Text field holding longitude +-180.00000
// zoomLevel Zoom level 0 (furthest out) to 17 (closest in).
function goToLatLon(latInput, lonInput, zoomLevel) {
   var lat = parseFloat(latInput.value);
   var lon = parseFloat(lonInput.value);
   if (isNaN(lat)) {
       alert( "The latitude " + latInput.value + " is not valid.");
   }
   else if (isNaN(lon)) {
       alert( "The longitude " + lonInput.value + " is not valid.");
   }
   else {
      zoomToLatLon(lat, lon, zoomLevel);   
   }
}

// lat and lon are floats
function zoomToLatLon(lat, lon, zoomLevel) {
   //var centerPoint = new GPoint(lon,lat);
   //map.centerAndZoom(centerPoint, 4);
   map.setZoom(zoomLevel);
   map.setCenter(new GLatLng(lat,lon));
}


function avg(a, b) {
   return (a + b) / 2;
}
 
// Compute the great-circle distance between 2 geo-points (miles)
function gcd(lon1, lat1, lon2, lat2) {
  //       dlon = lon2 - lon1
  //       dlat = lat2 - lat1
  //       a = (sin(dlat/2))^2 + cos(lat1) * cos(lat2) * (sin(dlon/2))^2
  //       c = 2 * arcsin(min(1,sqrt(a)))
  //       distance = (Earth Radius) * c
  var degToRadians = 3.141592658 / 180;

  var dlon = (lon2 - lon1) * degToRadians;
  var dlat = (lat2 - lat1) * degToRadians;
  var a = Math.pow((Math.sin(dlat/2)), 2) + Math.cos(lat1) * Math.cos(lat2) * Math.pow((Math.sin(dlon/2)), 2);
  //var c = 2 * Math.atan2( Math.sqrt(a), Math.sqrt(1-a) );
  var c = 2 * Math.asin(Math.min(1, Math.sqrt(a)));
  var d = 3964 * c;  // miles

  //alert("distance = " + d + " miles, between (" + lon1 + "," + lat1 + ") and (" +
  //      lon2 + "," + lat2 + ")");
  return d;
}
 
