﻿// Virtual Earth Mapping functions
// Copyright 2007-2008 Mentor Engineering Inc.

// This file contains all methods and operations for manipulating
// the VirtualEarth API
//
// To use this file, it must be used in conjuction with utils.js
// which provides additional helper objects and methods for
// interpreting the schedule files.
//  
// You need to make sure you define these variables in the body tag of your ASPX file before
// the javascript is loaded: m_nInitialLongitude, m_nInitialLatitude, m_nInitialZoomLevel
// i.e.
// 
//     <script type="text/javascript">
//        var m_nInitialLongitude = '<%= System.Configuration.ConfigurationSettings.AppSettings["InitialLongitude"] %>'; 
//        var m_nInitialLatitude = '<%= System.Configuration.ConfigurationSettings.AppSettings["InitialLatitude"] %>';
//        var m_nInitialZoomLevel = '<%= System.Configuration.ConfigurationSettings.AppSettings["InitialZoomLevel"] %>';
//    </script>
//
// To include this in your project, the best way is to include it
// with the virtual earth API via the ScriptManager, i.e.:
//   <asp:ScriptManager ID="ScriptManager1" 
//          EnablePartialRendering="true" 
//          EnablePageMethods="true" runat="server">
//      <Scripts>
//         <asp:ScriptReference 
//            Path="http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6" />
//         <asp:ScriptReference Path="script/utils.js" />
//         <asp:ScriptReference Path="script/map.js" />  
//      </Scripts>        
//   </asp:ScriptManager>
//
// To load the maps you either make a call to one of the following:
//      GetMapAllRoutes    - loads all routes on puts them on the map
//      GetMapRoute        - loads just a particular route as specified from the querystring
//
// TODO: KM - Aug 12, 2008 Refactor out all the repeatitive parentElement.parentElement.parentElement
// stuff. It is super confusing to follow.

// Constants
var ROUTE_LINE_WIDTH = 4;
var ROUTE_LINE_WIDTH_HOVER = 10;
var ROUTE_LINE_TRANSPARENCY = 0.5;
var ROUTE_LINE_TRANSPARENCY_HOVER = 0.7;
var ROUTE_ICON_LEFT = 100;
var ROUTE_ICON_TOP = 100;
var POINTTYPE_BUS = "1";
var POINTTYPE_TIMEPOINT = "2";
var TIMER_INTERVAL = 60000;
var TRIP_BREAKER = 6;

var NOTAVAIL = "n/a";

// Variables
var map = null;
var m_oRouteShape = null;
var m_oShapePoints = null;
var m_oXmlDoc = null;
var m_oXMLHTTP = null;
var m_oTimeXMLHTTP = null;
var m_oRoutesXMLHTTP = null;
var m_oPreviousRouteShapeID = null;
var m_oLoadingControl = null;
var m_oRoutesNode = null;
var m_oVehicleLayer = null;
var m_nLateEarlyMins = null;
var m_oPickUpStop = new Object;
var m_oDropOffStop = new Object;
var m_oIconContainer = null;
var m_sCurrentRouteKey = null;
var m_bCurrentRouteIsLoop = false;
var m_oCurrentVehicleListXML = null;
var m_bLoadingAllRoutes = true;

// Dictionaries

// m_oTimePointDict - TimePoint Dictionary, contains all points
// in the particular route
// Key = PatternPointKey
// Value = Point object - contains long/lat/shape of point
var m_oTimePointDict = new oDictionary();

// m_oRouteShapeDict - RouteShapes Dictionary, contains a relationship
// between routes and virtual earth shapes
// Key = RouteKey
// Value = ShapeID - a string generated by VE that identifies
// this route on the map
var m_oRouteShapeDict = new oDictionary();

// m_oVehiclesOnTrip - contains of vehicles on each trips
// Key = TripKey
// Value = vehicle string of vehicles on the trip
var m_oVehiclesOnTrip = new oDictionary();

// m_oPatternPointDTFDict - contains the DistanceTimeFactor (DTF)
// for each patternpointkey
// Key = PatternPointKey
// Value = int representing how many seconds from the timepoint 
// that a bus will reach this point (estimate)
var m_oPatternPointDTFDict = new oDictionary();

// m_oPatternPointShapeDict - contains all pushpin points on
// the map and their relationship to which patternpointKey
// they belong to
// Key = ShapeID - a string generated by VE that identifies this
// shape on the map
// Value = PatternPointKey - string
var m_oPatternPointShapeDict = new oDictionary();

// key = pattern point
// value = shapeID
var m_oTimePointPatternPoint = new oDictionary();

// m_oPreviousTimePointPatternPointKeyDict - contains a relationship between
// which TimePoint patternPointKeys is the previous time point to each
// patternPointKey.
// Key = PatternPointKey
// Value = Previous Time Point PatternPointKey
var m_oPreviousTimePointPatternPointKeyDict = new oDictionary();

// Timer
var m_nTimerID = 0;

// Icons
// Icon used for route name that we put in the VE banner bar
var m_sRouteIcon = "<div class='RouteIcon' style='font-size: 1.1em;'>&nbsp;&nbsp;<div class='RouteIconInside' style='background-color:{0}; color: {2};'>Route: {1}</div></div>";
// Icon used for bus stops on the map
var m_sStopIcon = "<div class='BusStop' style='background-color:{0}'>" + "<img alt='Bus Stop' src='img/stop-sq.gif' />" + "</div>";
// Icon used for Vehicles on the map
var m_sVehicleIcon = "<div style='position:relative;height:24px; width:24px; top:-12px; color:#fff; text-align:center; font: bold 24px Arial; cursor: pointer;'>" + "<img alt='Bus' src='img/BlackBus.gif' height='24' width='19' />" + "</div>";
// Icon used for showing a Loading indicator on the map
var m_sLoaderIcon = "<img alt='Loading' src='img/ajax-loader.gif' />";

// registerEvents
// <summary>
// Register events for processing after a postback has occurred. This will
// ensure that map is reloaded after a postback.
// </summary>
function registerEvents() {
    Sys.WebForms.PageRequestManager.getInstance().add_endRequest(AfterRequest);
}

// REGION: Event Handlers

// AfterRequest
// <summary>
// Event to fire after the page has been requested.
// Resets variables, stops the timer from requesting vehicle locations
// and loads the map again.
//
// Global vars: m_oPickUpStop, m_oDropOffStop are reset for next user load
// </summary>
function AfterRequest() {
    m_oVehicleLayer = null;
    if (m_nTimerID) {
        clearTimeout(m_nTimerID);
    }

    // reset variables and reload map
    m_oPickUpStop = new Object;
    m_oDropOffStop = new Object;
    GetMapRoute();
}


// MouseOverShape
// <summary>
// Handles the event for when the user moves their mouse over a route
// it will expand the width of the route to make it more visible to the user.
// </summary>
// <param name="e">The event args</param>
// <returns></returns>
function MouseOverShape(e) {
    if (e.elementID != null) {
        HighlightRoute(e.elementID);
    }
}

// MouseOverRouteShape
// <summary>
// Calls a pagemethod to get bus stop information on when the next bus will
// get to the stop
// </summary>
// <param name="e">The event args of the shape we're hovering over</param>
// <returns></returns>
function MouseOverRouteShape(e) {
    if (e.elementID) {
        shape = map.GetShapeByID(e.elementID);
        if (m_oPatternPointShapeDict[shape.GetID()] != null) {
            // !shape.GetDescription() && 
            shape.SetDescription('<span id="' + shape.GetID() + '_Content"><img src="img/progress_loading.gif" alt="" align="absmiddle" /> Loading information...</span>');
            var l_szShapeId = shape.GetID().substr(0, 18);
            var l_szKey = m_oPatternPointShapeDict[l_szShapeId];
            var l_sListOfVehicles = "";

            if (window.ActiveXObject) {
                l_sListOfVehicles = m_oCurrentVehicleListXML.xml;
            }
            else {
                l_sListOfVehicles = (new XMLSerializer()).serializeToString(m_oCurrentVehicleListXML);
            }

            PageMethods.GetNextBusTimesForStop(shape.GetID(), m_oPreviousTimePointPatternPointKeyDict[l_szKey], m_sCurrentRouteKey, m_oPatternPointDTFDict[l_szKey], l_sListOfVehicles, OnGetNextBusComplete, OnGetNextBusError, null);
        }

    }
}


// OnGetNextBusComplete
// <summary>
// Handles the call back from the MouseOverRouteShape PageMethod call
// Assigns the values in the passed in XML into the shape object on the map
// </summary>
// <param name="l_oXmlResults">An XML representing the next bus arrival time</param>
// <returns></returns>
function OnGetNextBusComplete(l_oXmlResults) {
    var l_oRootNode = l_oXmlResults.getElementsByTagName("results").item(0);
    var l_oTimeNode = l_oRootNode.getElementsByTagName("time").item(0);
    var l_oShapeNode = l_oRootNode.getElementsByTagName("shape").item(0);

    var l_oShapeContent = document.getElementById(l_oShapeNode.firstChild.data + '_Content');

    if (l_oShapeContent != null) {
        document.getElementById(l_oShapeNode.firstChild.data + '_Content').innerHTML = l_oTimeNode.firstChild.data;
        shape = map.GetShapeByID(l_oShapeNode.firstChild.data);
        shape.SetDescription(l_oTimeNode.firstChild.data);
    }
}


// OnGetNextBusError
// <summary>
// If there is an error with the pagemethod call, we don't need to do anything
// </summary>
// <returns></returns>
function OnGetNextBusError(error, msg, line) {
    // do nothing    
}


// MouseClick
// <summary>
// Handles the click event of a mouse clicking on a route on the map.
// Redirects the user to the Route.aspx page passing the routekey as
// a query string parameter.
// </summary>
// <param name="e">The event args</param>
// <returns></returns>  
function MouseClick(e) {

    // disable the geo community
    // makes sure click events on the map objects are not followed (like panning)
    map.vemapcontrol.EnableGeoCommunity(false);

    if (e.elementID != null) {
        // get the route key and redirect the user        
        var l_szRouteKey = e.elementID.substring(12, e.elementID.length - 6);

        var l_oRouteDD = document.getElementById("ctl00_ddlRoute");

        var l_bLoop = false;

        for (i = 0; i < l_oRouteDD.options.length; i++) {
            if (l_oRouteDD.options[i].value.indexOf(l_szRouteKey) > -1) {
                if (l_oRouteDD.options[i].value.indexOf('LOOP') > -1) {
                    l_bLoop = true;
                }
            }
        }

        if (l_szRouteKey.length > 0) {
            // force the client to redirect
            window.location = "Route.aspx?route=" + l_szRouteKey + "&loop=" + l_bLoop;
        }

    }
}


// MouseUp
// <summary>
// Handles the mouseup event and disables the
// geocommunity to interact with shapes on the map
// </summary>
// <param name="e">The event args</param>
// <returns></returns>  
function MouseUp(e) {
    map.vemapcontrol.EnableGeoCommunity(false);
}


// ReSelectStops
// <summary>
// Called externally to figure out if we need a postback or not
// when a user selects the Go button. If no postback is needed,
// the client will reselect the stops based off the users selection.
// </summary>
function ReSelectStops() {
    // validate the stop selection first  
    var l_oRouteDD = document.getElementById("ctl00_ddlRoute");
    var l_oStopSource = new Object;
    l_oStopSource.controltovalidate = "ctl00_ddlStop";
    var arguments = new Object;

    ClientStopValidate(l_oStopSource, arguments);

    if (arguments.IsValid && l_oRouteDD.value.replace("LOOP", "") != "") {
        if (m_sCurrentRouteKey == null)
            return true;

        if (m_sCurrentRouteKey != l_oRouteDD.value.replace("LOOP", ""))
            return true;

        // select new cells
        SetupStartStopVars();

        OnGetVehiclesOnRouteComplete(m_oCurrentVehicleListXML);
    }
    // we don't need to do a postback since we didn't change the route
    return false;
}

// END REGION EVENT HANDLERS

// REGION HELPERS

// HighlightRoute
// <summary>
// Highlights the route on the map.
//
// Global vars: Assumes m_oIconContainer has already been defined.
// </summary>
// <param name="szRouteKey">The given route key</param>
// <returns></returns>  
function HighlightRoute(szRouteKey) {

    if (map != null && (m_oPreviousRouteShapeID == null || m_oPreviousRouteShapeID != szShapeID)) {

        var szShapeID = null;

        // get the VE shape ID of the route we are hovering over           
        if (m_oRouteShapeDict[szRouteKey] != null) {
            szShapeID = m_oRouteShapeDict[szRouteKey];

        }
        else {
            szShapeID = szRouteKey;

            // Change the leftnav selection background color (they are labled LHRoute + RouteKey)
            var l_sActualRouteKey = szRouteKey.substring(12, 48);
            var l_oLeftHandRouteListItem = document.getElementById("LHRoute" + l_sActualRouteKey);
            if (l_oLeftHandRouteListItem != null)
                l_oLeftHandRouteListItem.style.background = "#466883";
        }

        // get the shape that we are hovering over
        var shape = map.GetShapeByID(szShapeID);

        if (shape != null) {
            // set the width of the shape
            shape.SetLineWidth(ROUTE_LINE_WIDTH_HOVER);
            var color = shape.GetLineColor();
            color.A = ROUTE_LINE_TRANSPARENCY_HOVER;
            shape.SetLineColor(color);
            shape.Show();

            // show the icon container of the route in the top VE bar
            var l_oCustomIcon = shape.GetCustomIcon();
            m_oIconContainer.innerHTML = l_oCustomIcon;
            /*m_oIconContainer.style.display = "block";          */

            // unhighlight the previous highlighted shape
            if (m_oPreviousRouteShapeID != null && m_oPreviousRouteShapeID != szShapeID) {
                UnHighlightRoute(m_oPreviousRouteShapeID);
            }

            // remember this shape so we can unhighlight it later
            m_oPreviousRouteShapeID = szShapeID;


        }
    }
}

// UnHighlightRoute   
// <summary>
// Unhighlights the route on the map
//
// </summary>
// <param name="szShapeID">The given VE shape ID</param>
// <returns></returns>  
function UnHighlightRoute(szShapeID) {
    // get the shape that we are finished with
    var shape = map.GetShapeByID(szShapeID);

    if (shape != null) {
        // Change the leftnav selection background color (they are labled LHRoute + RouteKey)
        var l_sActualRouteKey = szShapeID.substring(12, 48);
        var l_oLeftHandRouteListItem = document.getElementById("LHRoute" + l_sActualRouteKey);
        if (l_oLeftHandRouteListItem != null)
            l_oLeftHandRouteListItem.style.background = "#658292";

        // set the width of the shape
        shape.SetLineWidth(ROUTE_LINE_WIDTH);
        var color = shape.GetLineColor();
        color.A = ROUTE_LINE_TRANSPARENCY;
        shape.SetLineColor(color);
        shape.HideIcon(); // Remove the route name from the VE bar
    }
}
// END REGION HELPERS

// GetMapAllRoutes
// <summary>
// GetMap does the most important thing to the virtual earth map. It loads its data!
// It also sets up events that are needed for mouse controls.
//
// This method assumes that m_nInitialLatitude, m_nInitialLongitude,
// m_nInitialZoomLevel are already defined.
// </summary>
// <returns></returns>  
function GetMapAllRoutes() {

    // get the map element from the page and load it
    map = new VEMap('myMap');
    map.LoadMap(new VELatLong(m_nInitialLatitude, m_nInitialLongitude), m_nInitialZoomLevel, VEMapStyle.Shaded, false, null, false);

    // Hide the BirdsEye notification
    document.getElementById('MSVE_obliqueNotification').innerHTML = "";

    // Load the Icon container that will contain the route name when we highlight over
    LoadIconContainer();



    m_bLoadingAllRoutes = true;

    // Add a loading control to the map so the user will know something is happening              
    //   if (m_oLoadingControl == null) {
    //      var l_oCenterOfMap = map.LatLongToPixel(map.GetCenter());
    //      m_oLoadingControl = document.createElement("div");
    //      m_oLoadingControl.id = "Loader";
    //      m_oLoadingControl.style.top = l_oCenterOfMap.y - 50 + "px";
    //      m_oLoadingControl.style.left = l_oCenterOfMap.x - 50 + "px";
    //      m_oLoadingControl.style.border = "2px solid black";
    //      m_oLoadingControl.style.background = "White";
    //      m_oLoadingControl.innerHTML = m_sLoaderIcon;
    //      map.AddControl(m_oLoadingControl);
    //      addShim(m_oLoadingControl);      
    //      }

    // Attach all mouse events   
    map.AttachEvent("onmouseover", MouseOverShape);
    map.AttachEvent("onmousedown", MouseClick);
    map.AttachEvent("onmouseup", MouseUp);

    // Load the XML for the routes to be displayed on the map.
    //LoadRoutes(); // This is the old way (parse schedule.xml)



    // New way to load all routes (load already parsed JS files)
    LoadRoutesXML();

}

function SendAsyncRequest(url) {
    //dbg("Requesting "+url);
    scriptTag = document.createElement("script");
    scriptTag.src = url;
    scriptTag.type = "text/javascript";
    document.body.appendChild(scriptTag);



}

// LoadIconContainer
// <summary>
// Loads a placeholder into the VirtualEarth control where we can put the route ID when we
// hover over.
// </summary>
// <returns></returns>  
function LoadIconContainer() {
    var l_oVEContainer = document.getElementById('MSVE_navAction_topBar');
    var l_oGlyph = document.getElementById('MSVE_navAction_toggleGlyphInner');
    m_oIconContainer = document.createElement("div");
    m_oIconContainer.className = "iconContainer";
    m_oIconContainer.style.color = "White";
    m_oIconContainer.innerHTML = "";
    m_oIconContainer.title = "";
    l_oVEContainer.appendChild(m_oIconContainer);
}

// GetMapRoute(szRouteName)
// <summary>
// This control loads a particular route into the map.
//
// This method assumes that m_nInitialLatitude, m_nInitialLongitude,
// m_nInitialZoomLevel are already defined.
// 
// This method checks if there is a querystring for "route" containing
// the routeKey, otherwise it will use the dddlRoute dropdown value.
// </summary>
// <returns></returns>  
function GetMapRoute() {

    // Get the map element from the page and load it
    map = new VEMap('myMap');
    map.LoadMap(new VELatLong(m_nInitialLatitude, m_nInitialLongitude), m_nInitialZoomLevel, VEMapStyle.Shaded, false, null, false);

    // Hide the BirdsEye notification
    document.getElementById('MSVE_obliqueNotification').innerHTML = "";

    // Load the icon container for showing the route name in the VE bar
    LoadIconContainer();

    var l_szRouteName = queryString("route");

    if ($get('ctl00_ddlRoute').value.replace("LOOP", "").length > 0) {
        l_szRouteName = $get('ctl00_ddlRoute').value.replace("LOOP", "");
    }

    // remember the current route that we are loading
    m_sCurrentRouteKey = l_szRouteName;
    m_bLoadingAllRoutes = false;

    map.AttachEvent("onmouseover", MouseOverRouteShape);

    if (l_szRouteName != null && l_szRouteName.length > 0 && l_szRouteName != "false") {
        LoadRoute(l_szRouteName);
    }
}


// AddShim   
// <summary>
// Add a new control (shim) to surround a control that is to be embedded on top of the
// map data.
//
// </summary>
// <param name="el">The element to add to the loading container</param>
// <returns></returns>  
function addShim(el) {
    var shim = document.createElement("iframe");
    shim.id = "myShim";
    shim.frameBorder = "0";
    shim.style.position = "absolute";
    shim.style.zIndex = "1";
    shim.style.top = el.offsetTop;
    shim.style.left = el.offsetLeft;
    shim.width = el.offsetWidth;
    shim.height = el.offsetHeight;
    el.shimElement = shim;
    el.parentNode.insertBefore(shim, el);
}

// RemoveLoadingControl   
// <summary>
// Removes the loading control from the map
// Assumes the m_oLoadingControl has been initialized
// </summary>
// <returns></returns>    
function RemoveLoadingControl() {
    if (m_oLoadingControl != null) {
        map.DeleteControl(m_oLoadingControl);
        m_oLoadingControl = null;
    }
}


// LoadRoute   
// <summary>
// Loads a particular route into our global route XML Document 
//
// </summary>
// <param name="l_szRouteKey">The routeKey to load</param>
// <returns></returns>  
function LoadRoute(l_szRouteKey) {
    // loading route
    importXML(l_szRouteKey);
}

// LoadRoutes  
// <summary>
// Loads all routes into our global route XML Document    
//
// </summary>
// <returns></returns>  
function LoadRoutes() {
    importXML("");
}

// LoadRouteMapData  
// <summary>
// Loads a particular route timetable into the virtual earth Map.
//
// <param name="l_szRouteKey">The routeKey to load</param>
// </summary>
// <returns></returns> 
function LoadRouteMapData(l_szRouteKey) {
    try {
        LoadTimeTableXML(l_szRouteKey);
    }
    catch (err) {
        alert("There was an error " + err);
    }
}


// GetVehiclePosition  
// <summary>
// Gets the latest location of all vehicles on a particular route. When complete, the 
// OnGetVehiclesOnRouteComplete event handler is fired.  
//
// <param name="l_szRouteKey">The routeKey to check for which vehicles are on route</param>
// </summary>
// <returns></returns> 
function GetVehiclePosition(l_szRouteKey) {
    if (l_szRouteKey == m_sCurrentRouteKey) {
        // Call the C# GetVehiclesOnRoute function to do the webservice call
        // On complete, execute OnGetVehiclesOnRouteComplete
        // On error, execute OnGetVehiclesOnRouteError
        PageMethods.GetVehiclesOnRoute(l_szRouteKey, OnGetVehiclesOnRouteComplete, OnGetVehiclesOnRouteError, null);

        // Set up this method to be called again in the TIMER_INTERVAL duration
        m_nTimerID = setTimeout("GetVehiclePosition('" + l_szRouteKey + "')", TIMER_INTERVAL);
    }
}

// OnGetVehiclesOnRouteComplete
// <summary>
// Processes the results of the routes latest vehicle locations
// Finds the closest vehicle to the users selected stops, plus pots the vehicles on the map
//
// Assumes a VE map has been loaded, plus that a pickup and drop off stop has been selected.
// 
// <param name="l_oVehiclesOnRouteXML">The XML document representing all the vehicles that are on route</param>
// </summary>
// <returns></returns> 
function OnGetVehiclesOnRouteComplete(l_oVehiclesOnRouteXML) {

    // remember these vehicles
    m_oCurrentVehicleListXML = l_oVehiclesOnRouteXML;

    // Reset the cell colors in the Timetable back to original
    ResetTimeTableCells();

    m_oVehiclesOnTrip = new oDictionary();

    // get the vehicle layer
    if (m_oVehicleLayer == null) {
        m_oVehicleLayer = new VEShapeLayer();
        m_oVehicleLayer.SetTitle("Vehicle Layer");
        map.AddShapeLayer(m_oVehicleLayer);
    }
    else {
        // Erase current vehicles on the map and then redraw them
        m_oVehicleLayer.DeleteAllShapes();
    }

    var l_oClosestVehicle = new Object;
    var l_oNoteForLoop = document.getElementById('NoteForLoop');

    try {
        var l_oResultsNode = l_oVehiclesOnRouteXML.getElementsByTagName("results").item(0);
        var l_oMethodCallNode = l_oVehiclesOnRouteXML.getElementsByTagName("methodCall").item(0);
        var l_dDateExecuted = l_oMethodCallNode.getElementsByTagName("DateExecuted").item(0).firstChild.data;
        if (l_oResultsNode != null || l_oResultsNode.childNodes.length > 0)
        // if this isn't true, the Streets server is probably down
        {

            // Start looping through all the vehicles in the result
            var l_oAgencyVehiclesNode = l_oResultsNode.getElementsByTagName("agencyVehicles").item(0);
            var l_nNumOfAVs = l_oAgencyVehiclesNode.getElementsByTagName("agencyVehicle").length;
            for (var i = 0; i < l_nNumOfAVs; i++) {
                var l_AgencyVehicleNode = l_oAgencyVehiclesNode.getElementsByTagName("agencyVehicle").item(i);

                // Add Vehicle to map
                var l_szAVId = l_AgencyVehicleNode.getElementsByTagName("id").item(0).firstChild.data;

                var l_fPointLat = Number(l_AgencyVehicleNode.getElementsByTagName("lat").item(0).firstChild.data);
                var l_fPointLong = Number(l_AgencyVehicleNode.getElementsByTagName("long").item(0).firstChild.data);

                var l_szLastTimePointTripPointKey = "";
                if (l_AgencyVehicleNode.getElementsByTagName("lastTimePointTripPointKey").item(0).firstChild != null) {
                    l_szLastTimePointTripPointKey = l_AgencyVehicleNode.getElementsByTagName("lastTimePointTripPointKey").item(0).firstChild.data;
                }

                else {
                    continue;
                }

                var l_szLastLocation = l_AgencyVehicleNode.getElementsByTagName("lastReported").item(0).firstChild.data;
                var l_oVehicleShape = new VEShape(VEShapeType.Pushpin, new VELatLong(l_fPointLat, l_fPointLong));
                var l_oScheduleDeltaAdherence = l_AgencyVehicleNode.getElementsByTagName("scheduleAdherenceDelta").item(0).firstChild.data;
                l_oVehicleShape.SetCustomIcon(m_sVehicleIcon);
                l_oVehicleShape.SetZIndex(1002); // Put the vehicle on top of everything else
                l_oVehicleShape.Title = "Bus Information";

                // Set up the description for the bus shape
                var l_sDescription = "<b>Last reported</b><br />" + l_szLastLocation + " ago<br />";
                if (l_szLastTimePointTripPointKey != null && l_szLastTimePointTripPointKey.length > 0) {
                    // Write the schedule adherence for this vehicle
                    l_sDescription += "<br /><b>Status</b><br />" + CalculateScheduleAdherence(Number(l_oScheduleDeltaAdherence)) + "<br />";

                    // Get the last stop name & time
                    var l_oTimePointCell = document.getElementById(l_szLastTimePointTripPointKey);
                    if (l_oTimePointCell != null && l_oTimePointCell.innerText.length > 1) {
                        l_sDescription += "<br /><b>Previous time point</b><br />" + l_oTimePointCell.parentElement.parentElement.parentElement.parentElement.parentElement.parentElement.childNodes[1].childNodes[0].childNodes[0].rows[l_oTimePointCell.parentElement.rowIndex].childNodes["1"].innerText + " - " + CalculateTime(l_oTimePointCell.innerText) + "<br />";

                        // Get the next stop (it should be the next cell below this one)
                        var l_oTempNextCell = l_oTimePointCell.offsetParent.childNodes[0].childNodes[l_oTimePointCell.parentElement.rowIndex + 1];
                        // make sure that this stop actually exists (it could be that it is at the end)
                        if (l_oTempNextCell != null) {
                            var l_oNextStopCell = l_oTempNextCell.childNodes[l_oTimePointCell.cellIndex];
                            if (l_oNextStopCell != null && l_oNextStopCell.innerText.length > 1) {

                                l_sDescription += "<br /><b>Next time point</b><br />" + l_oTimePointCell.parentElement.parentElement.parentElement.parentElement.parentElement.parentElement.childNodes[1].childNodes[0].childNodes[0].rows[l_oNextStopCell.parentElement.rowIndex].childNodes["1"].innerText + " - " + CalculateTime(l_oNextStopCell.innerText) + "<br />";
                            }
                        }
                    }
                }
                l_oVehicleShape.SetDescription(l_sDescription);
                m_oVehicleLayer.AddShape(l_oVehicleShape);

                // see if this bus is closest to their pickup stop
                if (l_szLastTimePointTripPointKey != null && l_szLastTimePointTripPointKey.length > 0) {
                    var l_oCell = document.getElementById(l_szLastTimePointTripPointKey);

                    if (l_oCell != null) {

                        // figure out which table this cell is in
                        var l_sId = l_oCell.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.id;

                        var l_nTripCellIndex = l_oCell.cellIndex;

                        // figure out which trip this cell is on
                        // it's the ID of the first cell of the column             
                        var l_sTripKey = l_oCell.parentNode.parentNode.rows[0].childNodes[l_nTripCellIndex].id;
                        m_oVehiclesOnTrip.Add(l_sTripKey, l_szAVId);

                        // find the rank of this trippointkey
                        var l_nRank = Number(l_oCell.parentNode.rowIndex);

                        // compare this with the pick up stop rank
                        if (m_oPickUpStop.Rank != null && m_oDropOffStop.Rank != null) {
                            if (l_nRank < m_oPickUpStop.Rank) {
                                var l_oRankDiff = Number(m_oPickUpStop.Rank - l_nRank);

                                var l_oCheckRHPickupCell = document.getElementById(l_sId + m_oPickUpStop.Key);
                                var l_oCheckRHDropoffCell = document.getElementById(l_sId + m_oDropOffStop.Key);

                                var l_oDropOffTripCell = l_oCheckRHDropoffCell.parentElement.parentElement.parentElement.parentElement.parentElement.childNodes[2].childNodes[0].childNodes[0].rows[Number(m_oDropOffStop.Rank)].childNodes[l_nTripCellIndex];

                                //l_oCheckRHDropoffCell.parentNode.childNodes[l_nTripCellIndex];   

                                l_oTripPointCell = l_oCheckRHPickupCell.parentElement.parentElement.parentElement.parentElement.parentElement.childNodes[2].childNodes[0].childNodes[0].rows[Number(m_oPickUpStop.Rank)].childNodes[l_nTripCellIndex];
                                //l_oCheckRHPickupCell.parentNode.childNodes[l_nTripCellIndex];                         

                                // make sure we found a trip point cell and that it has a time in it (not blank)
                                // it also needs to be closer to the point then the previous closest vehicle
                                if (l_oTripPointCell != null && l_oDropOffTripCell != null &&
                        l_oDropOffTripCell.innerText.length > 1 && l_oTripPointCell.innerText.length > 1 &&
                        (l_oClosestVehicle.Id == null || l_oClosestVehicle.RankDifferential > l_oRankDiff)) {

                                    // Highlight the cell
                                    //TimeTableHighlightCells(l_oTripPointCell, "TimeTableCellHighlight", "TimeTableRowHighlight");

                                    l_oClosestVehicle.Id = l_szAVId;
                                    l_oClosestVehicle.RankDifferential = l_oRankDiff;
                                    l_oClosestVehicle.LastTimePointTripPointKey = l_szLastTimePointTripPointKey;
                                    l_oClosestVehicle.ScheduleDeltaAdherence = l_oScheduleDeltaAdherence;

                                    // we need to check the pickup stop to find the time this closest bus will arrive

                                    var l_oPickUpStopRHCell = document.getElementById(l_sId + m_oPickUpStop.Key);
                                    //l_oTripPointCell = l_oPickUpStopRHCell.parentNode.childNodes[l_nTripCellIndex]; 
                                    l_oTripPointCell = l_oPickUpStopRHCell.parentElement.parentElement.parentElement.parentElement.parentElement.childNodes[2].childNodes[0].childNodes[0].rows[Number(m_oPickUpStop.Rank)].childNodes[l_nTripCellIndex];
                                    //TimeTableHighlightCells(l_oTripPointCell, "TimeTableCellHighlight", "TimeTableRowHighlight");
                                    l_oClosestVehicle.PickUpStopTime = String(l_oTripPointCell.innerText);
                                    l_oClosestVehicle.PickUpCell = l_oTripPointCell;
                                    var l_oPickUpStopCellIndex = l_oTripPointCell.cellIndex;

                                    // drop off stop time

                                    if (m_bCurrentRouteIsLoop == false) {
                                        var l_oDropOffStopRHCell = document.getElementById(l_sId + m_oDropOffStop.Key);
                                        l_oTripPointCell = l_oDropOffStopRHCell.parentElement.parentElement.parentElement.parentElement.parentElement.childNodes[2].childNodes[0].childNodes[0].rows[Number(m_oDropOffStop.Rank)].childNodes[l_nTripCellIndex];
                                        //l_oDropOffStopRHCell.parentNode.childNodes[l_nTripCellIndex];           
                                        l_oNoteForLoop.style.display = "none";
                                    }
                                    else {
                                        // first make sure the drop off stop is not before the pickup stop
                                        // if so, then we are looping to another trip
                                        // if this is a loop, it also could be in the next table.... 
                                        if (m_oDropOffStop.Rank < m_oPickUpStop.Rank) {
                                            // get last timepoint of the current trip (it should be in the last cell of this column)

                                            var l_oParentTable = l_oCheckRHPickupCell.parentNode.parentNode.parentNode.parentNode.nextSibling.firstChild.firstChild.firstChild;
                                            var l_oRowsCount = l_oParentTable.rows.length;
                                            var l_oLastTimePointOfPickUpTrip = l_oParentTable.rows[l_oRowsCount - 1].childNodes[l_oPickUpStopCellIndex];

                                            // get the first time point of the next trip
                                            var l_oFirstTimePointOfNextTrip;
                                            var l_bFoundTime = false;

                                            while (l_bFoundTime == false) {


                                                l_oFirstTimePointOfNextTrip = l_oParentTable.parentNode.rows[1].childNodes[l_oPickUpStopCellIndex];


                                                // do the compare of times, if the FirstTimePointOfNextTrip is > LastTimePointOfPickUpTrip
                                                // then we can use this trip

                                                // TODO: Make function for this
                                                //if (l_oLastTimePointOfPickUpTrip.innerText < l_oFirstTimePointOfNextTrip.innerText)
                                                var l_dLastDate = new Date('01/01/1980 ' + l_oLastTimePointOfPickUpTrip.innerText);
                                                var l_dFirstDate;
                                                if (l_oFirstTimePointOfNextTrip.attributes.getNamedItem('overhours').value == "true") {
                                                    l_dFirstDate = new Date('01/02/1980 ' + l_oFirstTimePointOfNextTrip.innerText);
                                                }
                                                else {
                                                    l_dFirstDate = new Date('01/01/1980 ' + l_oFirstTimePointOfNextTrip.innerText);
                                                }

                                                if (l_dFirstDate > l_dLastDate) {
                                                    l_nTripCellIndex = l_oFirstTimePointOfNextTrip.cellIndex;
                                                    l_bFoundTime = true;
                                                    l_oNoteForLoop.style.display = "block";
                                                }
                                                else // continue to the next time point
                                                {
                                                    l_oPickUpStopCellIndex++;
                                                }

                                            }


                                        }
                                        else {
                                            l_oNoteForLoop.style.display = "none";
                                        }

                                        var l_oDropOffStopRHCell = document.getElementById(l_sId + m_oDropOffStop.Key);
                                        l_oTripPointCell = l_oDropOffStopRHCell.parentNode.parentNode.parentNode.parentNode.nextSibling.firstChild.firstChild.firstChild.rows[m_oDropOffStop.Rank].childNodes[l_nTripCellIndex];
                                        // = l_oDropOffStopRHCell.parentNode.childNodes[l_nTripCellIndex];                          
                                    }

                                    //TimeTableHighlightCells(l_oTripPointCell, "TimeTableCellHighlight", "TimeTableRowHighlight");                  
                                    l_oClosestVehicle.DropOffStopTime = String(l_oTripPointCell.innerText);
                                    l_oClosestVehicle.DropOffCell = l_oTripPointCell;
                                }
                            }
                        }

                        // Change the color in the time table cell associated to this time point
                        // This is just for debugging purposes
                        //l_oCell.className = "TimeTableCellVehicle";           
                    }

                }
            }

        }

        // If we still haven't found a closest vehicle, then we'll need to select a newly starting trip
        if ((l_oClosestVehicle.Id == null && m_oPickUpStop.Rank != null && m_oDropOffStop.Rank != null) ||
         (l_oClosestVehicle.DropOffStopTime.length <= 1 || l_oClosestVehicle.PickUpStopTime.length <= 1)) {
            l_oNoteForLoop.style.display = "none";

            // check current time against all trips
            GetNewlyStartingTrips(l_dDateExecuted);
        }
        else {
            // We found a cloest vehicle, so lets load the bus estimation table with its appropriate values
            TimeTableHighlightCells(l_oClosestVehicle.PickUpCell, "TimeTableCellHighlight", "TimeTableRowHighlight");
            TimeTableHighlightCells(l_oClosestVehicle.DropOffCell, "TimeTableCellHighlight", "TimeTableRowHighlight");

            var l_oEstimationDiv = document.getElementById('BusEstimation');
            l_oEstimationDiv.style.visibility = "visible";
            LoadBusEstimation(l_oClosestVehicle);
        }

    }
    catch (err) {
        // If we have an error, we pass the last closest vehicle, if this is still null
        // the BusEstimation will just mark that there are no vehicles available
        // This could be due to the Streets Server being down, or an error with the data.
        LoadBusEstimation(l_oClosestVehicle);
    }
}


// GetNewlyStartingTrips
// <summary>
// This function figures out if there is a trip that has no busses
// on it yet, i.e. it is newly starting, that the user will be able
// to ride on soon.
// <param name="oCurrentDate">The current date to look for the next starting trip</param>
// </summary>
// <returns></returns> 
function GetNewlyStartingTrips(oCurrentDate) {
    var l_oCurDateFormatted = new Date(oCurrentDate);
    var l_oPickUpStopCell = null;
    var l_oDropOffStopCell = null;
    var l_oClosestVehicle = new Object;
    var l_oCompareDateDiff = null;
    var l_oNoteForLoop = document.getElementById('NoteForLoop');
    var l_sDayTablePrefix = GetDayTypeTablePrefix();

    var l_oTimeTable = document.getElementById(l_sDayTablePrefix).lastChild.childNodes[0].childNodes[0];
    if (l_oTimeTable != null) {
        var l_oRowsArray = l_oTimeTable.childNodes[0].rows;

        // check all columns of the timetable
        for (var x = 0; x < l_oRowsArray[0].childNodes.length; x++) {
            var l_sTripKey = l_oRowsArray[0].childNodes[x].id;

            // see if there was a vehicle on this trip
            // if there isn't then we can check this trip
            if (m_oVehiclesOnTrip[l_sTripKey] == null &&
                    l_sTripKey.length > 0) {

                var l_nDayOfWeek = l_oCurDateFormatted.getDay();

                var l_oPickRank = m_oPickUpStop.Rank;
                var l_oStopRank = m_oDropOffStop.Rank;
                var l_oTempPickUpCell = l_oRowsArray[l_oPickRank].childNodes[x];
                var l_oTempStopCell = l_oRowsArray[l_oStopRank].childNodes[x];

                if (l_oTempPickUpCell.innerText.length > 1 && l_oTempStopCell.innerText.length > 1) {
                    // we also need to account for time after midnight       
                    if (l_oTempPickUpCell.attributes.getNamedItem('overhours') != null) {
                        var l_sOverHours = l_oTempPickUpCell.attributes.getNamedItem('overhours').value;

                        var l_oDay = null;
                        if (l_sOverHours == "true") {
                            // minus one day
                            l_oDay = l_oCurDateFormatted.getDate();
                            if (l_nDayOfWeek != 0) // it's any other day then sunday
                                l_nDayOfWeek = l_nDayOfWeek - 1;
                            else // it's sunday, so make it saturday
                                l_nDayOfWeek = 6;
                        }
                        else
                            l_oDay = l_oCurDateFormatted.getDate();

                        var l_oCompareDate = new Date(
                                l_oCurDateFormatted.getMonth() + 1 + "/" +
                                l_oDay + "/" +
                                l_oCurDateFormatted.getFullYear() + " " +
                                l_oTempPickUpCell.innerText);

                        // we just need to make sure this time is greater then the current date                                                              
                        if (l_oCompareDate >= l_oCurDateFormatted) {
                            if (l_oCompareDateDiff == null || (l_oCompareDate - l_oCurDateFormatted) < l_oCompareDateDiff) {
                                l_oCompareDateDiff = l_oCompareDate - l_oCurDateFormatted;
                                // if it is, then use this one
                                l_oPickUpStopCell = l_oTempPickUpCell;

                                if (m_bCurrentRouteIsLoop == true && l_oStopRank < l_oPickRank) {

                                    l_oNoteForLoop.style.display = "block";


                                    var l_oPickUpStopCellIndex = l_oPickUpStopCell.cellIndex;

                                    // need to check stop cell if it is a loop
                                    var l_oParentTable = l_oTempPickUpCell.parentNode.parentNode;
                                    var l_oRowsCount = l_oParentTable.rows.length;
                                    var l_oLastTimePointOfPickUpTrip = l_oParentTable.rows[l_oRowsCount - 1].childNodes[l_oPickUpStopCellIndex];

                                    // get the first time point of the next trip
                                    var l_oFirstTimePointOfNextTrip;
                                    var l_bFoundTime = false;

                                    while (l_bFoundTime == false) {

                                        if (l_oPickUpStopCellIndex <= TRIP_BREAKER + 1) {
                                            l_oFirstTimePointOfNextTrip = l_oParentTable.rows[1].childNodes[l_oPickUpStopCellIndex];
                                        }
                                        else {
                                            l_oPickUpStopCellIndex = 2; // go to the first timepoint of the next table


                                            if (document.getElementById(l_sId + m_oPickUpStop.Key) != null) {
                                                l_oParentTable = document.getElementById(l_sId + m_oPickUpStop.Key).parentNode.parentNode;
                                                l_oFirstTimePointOfNextTrip = l_oParentTable.rows[1].childNodes[l_oPickUpStopCellIndex];
                                            }
                                            else {
                                                l_oFirstTimePointOfNextTrip = "";
                                            }
                                        }

                                        // do the compare of times, if the FirstTimePointOfNextTrip is > LastTimePointOfPickUpTrip
                                        // then we can use this trip

                                        // TODO: Make function for this
                                        //if (l_oLastTimePointOfPickUpTrip.innerText < l_oFirstTimePointOfNextTrip.innerText)
                                        var l_dLastDate = new Date('01/01/1980 ' + l_oLastTimePointOfPickUpTrip.innerText);
                                        var l_dFirstDate;
                                        if (l_oFirstTimePointOfNextTrip.attributes.getNamedItem('overhours').value == "true") {
                                            l_dFirstDate = new Date('01/02/1980 ' + l_oFirstTimePointOfNextTrip.innerText);
                                        }
                                        else {
                                            l_dFirstDate = new Date('01/01/1980 ' + l_oFirstTimePointOfNextTrip.innerText);
                                        }
                                        if (l_dFirstDate > l_dLastDate) {
                                            l_nTripCellIndex = l_oFirstTimePointOfNextTrip.cellIndex;
                                            l_oDropOffStopCell = l_oParentTable.childNodes[l_oStopRank].childNodes[l_nTripCellIndex];
                                            l_bFoundTime = true;
                                        }
                                        else // continue to the next time point
                                        {
                                            l_oPickUpStopCellIndex++;
                                        }

                                    }

                                }
                                else {
                                    l_oNoteForLoop.style.display = "none";
                                    l_oDropOffStopCell = l_oTempStopCell;
                                }
                            } 
                        }

                    }
                }
            }

        }
    }


    if (l_oPickUpStopCell != null && l_oDropOffStopCell != null) {
        // set the pickup stop cells and set the correct bus stop estimated time
        l_oClosestVehicle.Id = "Unknown";
        l_oClosestVehicle.LastTimePointTripPointKey = "";
        l_oClosestVehicle.ScheduleDeltaAdherence = 0;
        l_oClosestVehicle.PickUpStopTime = String(l_oPickUpStopCell.innerText);
        l_oClosestVehicle.DropOffStopTime = String(l_oDropOffStopCell.innerText);

        // Highlight the cells that were found
        TimeTableHighlightCells(l_oPickUpStopCell, "TimeTableCellHighlight", "TimeTableRowHighlight");
        TimeTableHighlightCells(l_oDropOffStopCell, "TimeTableCellHighlight", "TimeTableRowHighlight");

        LoadBusEstimation(l_oClosestVehicle);
    }
    else {
        // we'll only get here if there are no trips that are available
        // so pass a empty closest vehicle to tell the user there are no busses available
        LoadBusEstimation(new Object);
    }

}

// OnGetVehiclesOnRouteError
// <summary>
// If there was an error with the PageMethod call, i.e. Streets Server is down, we'll load
// the bus estimation with a empty closest vehicle to tell the user there are not busses
// available.
// <param name="error">The error object</param>
// <param name="msg">The error message</param>
// <param name="line">The line number that the error is on</param>
// </summary>
// <returns></returns>    
function OnGetVehiclesOnRouteError(error, msg, line) {
    LoadBusEstimation(new Object);
}


// TimeTableHighlightCells
// <summary>
// Mark particular cells in our timetable with a specified ClassName
//
// <param name="l_oCell">The TD object cell of the time point to highlight</param>
// <param name="l_sCellClassName">The class name to mark on the l_oCell</param>
// <param name="l_sNeighborClassName">The class name to highlight all of the neighbor cells</param>
// </summary>
// <returns></returns>  
function TimeTableHighlightCells(l_oCell, l_sCellClassName, l_sNeighborClassName) {
    // Highlight all rows for the pickup and drop off stop

    var l_oDayPrefix = GetDayTypeTablePrefix();
    var l_oPickUpCellRH = document.getElementById(l_oDayPrefix + m_oPickUpStop.Key);
    var l_oDropOffCellRH = document.getElementById(l_oDayPrefix + m_oDropOffStop.Key);


    if (l_oPickUpCellRH != null && l_oDropOffCellRH != null) {
        for (var j = 0; j < l_oPickUpCellRH.parentNode.childNodes.length; j++) {
            var l_oNeighborPickUpCell = l_oPickUpCellRH.parentNode.childNodes.item(j);
            var l_oNeighborDropOffCell = l_oDropOffCellRH.parentNode.childNodes.item(j);

            if (l_oNeighborPickUpCell.className != "TimeTableEmptyCell" &&
                l_oNeighborPickUpCell.className != l_sCellClassName)
                l_oNeighborPickUpCell.className = l_sNeighborClassName;

            if (l_oNeighborDropOffCell.className != "TimeTableEmptyCell" &&
                l_oNeighborDropOffCell.className != l_sCellClassName)
                l_oNeighborDropOffCell.className = l_sNeighborClassName;
        }
    }

    // TODO: Refactor and put some of this repeated code into a function
    for (var j = 0; j < l_oCell.parentNode.childNodes.length; j++) {

        var l_oNeighborPickUpCell = l_oCell.parentNode.childNodes.item(j);
        var l_oNeighborDropOffCell = l_oCell.parentNode.childNodes.item(j);

        if (l_oNeighborPickUpCell.className != "TimeTableEmptyCell" &&
            l_oNeighborPickUpCell.className != l_sCellClassName)
            l_oNeighborPickUpCell.className = l_sNeighborClassName;

        if (l_oNeighborDropOffCell.className != "TimeTableEmptyCell" &&
            l_oNeighborDropOffCell.className != l_sCellClassName)
            l_oNeighborDropOffCell.className = l_sNeighborClassName;
    }

    // Highlight the cell in a white background
    l_oCell.className = l_sCellClassName;

    // bring the cell into view
    l_oCell.focus();

}

// ResetTimeTableCells
// <summary>
// This function resets the color of all cells in the timetable so they
// be changed to something new
//
// </summary>
// <returns></returns> 
function ResetTimeTableCells() {
    var l_oPrimaryTables = document.getElementById('PrimaryTable').childNodes.item(0);

    var l_sDayOfWeekPrefix = GetDayTypeTablePrefix();

    var l_bfirstRowCell = false;

    var l_oTimeTable;
    var l_oTimeTableHeader;

    if (l_sDayOfWeekPrefix != null && l_sDayOfWeekPrefix != "") {
        l_oTimeTable = document.getElementById(l_sDayOfWeekPrefix).lastChild.childNodes[0].childNodes[0];
        l_oTimeTableHeader = document.getElementById(l_sDayOfWeekPrefix).childNodes[1].childNodes[0];
    }
    else {
        l_oTimeTable = null;
        l_oTimeTableHeader = "Undefined";
    }

    if (l_oTimeTable != null) {
        var l_oRows = l_oTimeTable.childNodes[0].rows;
        var l_oHeaderRows = l_oTimeTableHeader.rows;
        for (var i = 0; i < l_oRows.length; i++) {
            var l_oRow = l_oRows.item(i);
            var l_oHeaderRow = l_oHeaderRows.item(i);

            // Handle the cells                    

            for (var j = 0; j < l_oRow.childNodes.length; j++) {
                if (l_oRow.childNodes.item(j).className != "TimeTableEmptyCell") {
                    if (l_bfirstRowCell)
                        l_oRow.childNodes.item(j).className = "darkRow";
                    else
                        l_oRow.childNodes.item(j).className = "lightRow";
                }
            }

            if (l_bfirstRowCell) {
                l_oHeaderRow.childNodes.item(0).className = "darkRow";
                l_oHeaderRow.childNodes.item(1).className = "darkRow";
            }
            else {
                l_oHeaderRow.childNodes.item(0).className = "lightRow";
                l_oHeaderRow.childNodes.item(1).className = "lightRow";
            }

            // flip the flag
            l_bfirstRowCell = !l_bfirstRowCell;
        }
    }

}


// LoadBusEstimation
// <summary>
// Populates the bus information based off the closest vehicle.
//
// <param name="oClosestVehicle">The closest vehicle object to use to 
//                               calculate the estimation.</param>
// </summary>
// <returns></returns> 
function LoadBusEstimation(oClosestVehicle) {
    var l_oBusLastTimePoint = document.getElementById('BusLastTimePoint');
    var l_oBusLastTimePointLeftStopAdherence = document.getElementById('BusLastTimePointLeftStopAdherence');
    var l_oBusPickUpStopTime = document.getElementById('BusPickUpStopTime');
    var l_oBusDropOffStopTime = document.getElementById('BusDropOffStopTime');
    var l_oBusEstimationLastStopTable = document.getElementById('BusEstimationLastStopTable');
    var l_oNoBus = document.getElementById('NoBusesError');

    // If we have a closest vehicle we can calculate the result
    if (oClosestVehicle.Id != null &&
         oClosestVehicle.Id.length > 0) {
        l_oNoBus.style.display = "none";
        l_oBusEstimationLastStopTable.style.visibility = "visible";

        // assign the last stop the vehicle was atast
        var l_oCell = document.getElementById(oClosestVehicle.LastTimePointTripPointKey);

        if (l_oCell != null) {
            var l_oRowIndex = l_oCell.parentNode.rowIndex;
            l_oBusLastTimePoint.innerHTML = l_oCell.parentElement.parentElement.parentElement.parentElement.parentElement.parentElement.childNodes[1].childNodes[0].rows[l_oRowIndex].childNodes[1].innerText;
        }
        else {
            l_oBusLastTimePoint.innerHTML = NOTAVAIL;
        }

        // figure out how late/early this vehicle was 
        l_oBusLastTimePointLeftStopAdherence.innerHTML = CalculateScheduleAdherence(Number(oClosestVehicle.ScheduleDeltaAdherence)); //Number(oClosestVehicle.ScheduleDeltaAdherence);

        // enter the time estimation in the table
        l_oBusPickUpStopTime.innerHTML = CalculateTime(oClosestVehicle.PickUpStopTime);
        l_oBusDropOffStopTime.innerHTML = CalculateTime(oClosestVehicle.DropOffStopTime);
    }
    else // we don't have one so we should show that there are no busses available
    {
        l_oNoBus.style.display = "block";
        l_oBusLastTimePoint.innerHTML = NOTAVAIL;
        l_oBusLastTimePointLeftStopAdherence.innerHTML = NOTAVAIL;
        l_oBusPickUpStopTime.innerHTML = NOTAVAIL;
        l_oBusDropOffStopTime.innerHTML = NOTAVAIL;
    }

}


// CalculateScheduleAdherence
// <summary>
// Figures out how early or late the closest Vehicle is
//
// <param name="l_oScheduleDeltaAdherence">Figures out how early or late the closest vehicle</param>
// </summary>
// <returns>An estimated calculation of how early or late the vehicle is</returns> 
function CalculateScheduleAdherence(l_oScheduleDeltaAdherence) {
    var l_nMinutes = Math.ceil(Math.abs(Number(l_oScheduleDeltaAdherence)) / 60);
    var l_bEarly = (Number(l_oScheduleDeltaAdherence) < 0);

    var l_sEarlyLate = null;
    if (l_bEarly) {
        l_sEarlyLate = "EARLY";
        m_nLateEarlyMins = Number(l_nMinutes * -1); // make it negative         
    }
    else if (l_nMinutes == 0) {
        l_sEarlyLate = "ON TIME";
        m_nLateEarlyMins = l_nMinutes;
    }
    else {
        l_sEarlyLate = "LATE";
        m_nLateEarlyMins = l_nMinutes;
    }

    return String(l_nMinutes + " min. " + l_sEarlyLate + " (estimated)");
}


// CalculateTime
// <summary>
// Returns the properly formatted time with AM/PM for a given time
//
// Assumse the m_nLateEarlyMins has been set to how early or late the vehicle.
// 
// <param name="sTime">Figures out the time the bus will reach the given time</param>
// </summary>
// <returns>A string containing the estimated time the bus will reach the stop</returns>
function CalculateTime(sTime) {
    var l_oDate = new Date('01/01/1980 ' + sTime);

    if (m_nLateEarlyMins != null) {
        l_oDate = minutesFromDate(l_oDate, m_nLateEarlyMins);

        var l_nHour = null;
        var l_sAMPMString = null;
        var l_sMinutes = null;
        if (l_oDate.getHours() > 12) {
            l_nHour = Number(l_oDate.getHours() - 12);
            l_sAMPMString = "PM";
        }
        else if (l_oDate.getHours() == 12) {
            l_nHour = 12;
            l_sAMPMString = "PM";
        }
        else {
            l_nHour = l_oDate.getHours();
            l_sAMPMString = "AM";
        }

        // append a 0 in front of a single digit time 1-9
        if (l_oDate.getMinutes() < 10)
            l_sMinutes = "0" + l_oDate.getMinutes();
        else
            l_sMinutes = l_oDate.getMinutes();

        return String(l_nHour + ":" + l_sMinutes + " " + l_sAMPMString) + " (estimated)";
    }

    return l_oTimeToCheck;
}


// MinutesFromDate
// <summary>
// Returns a date time that is minutes from or before the given date

// <param name="oOrigDate">The original date</param>
// <param name="nn">Integer of n minutes</param>
// </summary>
// <returns>A date time object that is nn minutes from the original date</returns>
function minutesFromDate(oOrigDate, nn) {
    var dt2 = new Date(oOrigDate.valueOf() + (nn * 60000));
    return dt2;
}


// LoadFullScheduleMapData
// <summary>
// Load map Data converts the loaded XML into virtual earth shape objects that can be placed on the map
//
// </summary>
// <returns></returns>
function LoadFullSchedleMapData() {
    var l_oXmlDocument = null;
    l_oXmlDocument = m_oXmlDoc.documentElement;

    try {
        // lets start looping through the results
        var l_oResultsNode = l_oXmlDocument.getElementsByTagName("results").item(0);
        m_oRoutesNode = l_oResultsNode.getElementsByTagName("routes").item(0);
        var l_nTotal = m_oRoutesNode.getElementsByTagName("route").length;
        setTimeout("loadRoute(" + 0 + "," + l_nTotal + ")", 0);
    }
    catch (err) {
        // We don't have a schedule, they need to run the setup page.
        RemoveLoadingControl();
    }
}


// loadRoute
// <summary>
// Sets up the asynchronous loading of all the routes on the map 
// </summary>
// <param name="i">An index of which route we are currently loading</param>
// <param name="t">the total count of routes</param>
// <returns></returns> 
function loadRoute(i, t) {
    if (i < t) {
        var l_oRouteNode = m_oRoutesNode.getElementsByTagName("route").item(i);
        LoadRouteIntoMap(l_oRouteNode, false);
        i++;
        setTimeout("loadRoute(" + i + "," + t + ")", 0);
    }
    else {
        // Remove the loading control
        RemoveLoadingControl();
    }
}


// LoadRouteIntoMap
// <summary>
// Loads a particular route into the virtual earth map    
// </summary>
// <param name="l_oRouteNode">The XMl Route node that we are loading</param>
// <param name="l_bShowStops">true/false whether or not to show bus stops when loading routes</param>
// <returns></returns> 
function LoadRouteIntoMap(l_oRouteNode, l_bShowStops) {

    // Clear our dictionary for time points
    m_oTimePointDict = new oDictionary();

    var l_oPatternNode = l_oRouteNode.getElementsByTagName("pattern").item(0);
    // set up variables from web service
    var l_szRouteID = l_oRouteNode.attributes.getNamedItem("id").value
    var l_szRouteName = l_oRouteNode.attributes.getNamedItem("name").value
    var l_szRouteKey = l_oRouteNode.attributes.getNamedItem("key").value;

    var l_oRouteNameDiv = document.getElementById("RouteName");

    if (l_oRouteNameDiv != null) {
        if (l_szRouteName != null && l_szRouteName.length > 1)
            l_oRouteNameDiv.innerHTML = "<h2>" + l_szRouteName + "</h2>";
        else
            l_oRouteNameDiv.innerHTML = "<h2>" + l_szRouteID + "</h2>";
    }

    var l_szLineRGB = l_oPatternNode.attributes.getNamedItem("lineRGB").value;
    var l_szLineColor = l_oPatternNode.attributes.getNamedItem("lineColor").value;
    var l_nLineSize = Number(l_oPatternNode.attributes.getNamedItem("lineSize").value);
    var l_szLineType = l_oPatternNode.attributes.getNamedItem("lineType").value;
    var l_szStopRGB = l_oPatternNode.attributes.getNamedItem("stopRGB").value;
    var l_szStopColor = l_oPatternNode.attributes.getNamedItem("stopColor").value;

    if (l_oPatternNode.attributes.getNamedItem("loop") != null) {
        var l_szPatternIsLoop = l_oPatternNode.attributes.getNamedItem("loop").value;
    }

    // check if it's a loop pattern
    if (l_szPatternIsLoop != null && l_szPatternIsLoop.toLowerCase() == "true") {
        m_bCurrentRouteIsLoop = true;
    }
    else {
        m_bCurrentRouteIsLoop = false;
    }

    // Mark the stop color in the legend
    var l_oLegendStopImage = document.getElementById("LegendStopImage");

    if (l_oLegendStopImage != null) {
        l_oLegendStopImage.style.backgroundColor = l_szStopColor;
    }

    var l_szStopSize = l_oPatternNode.attributes.getNamedItem("stopSize").value;
    var l_szStopSymbol = l_oPatternNode.attributes.getNamedItem("stopSymbol").value;
    var l_oPatternPointsNode = l_oPatternNode.getElementsByTagName("patternPoints").item(0);
    m_oShapePoints = new Array();
    var l_nTimePointRank = 1;
    var l_szPreviousTimePointPatternPointKey;
    for (var j = 0; j < l_oPatternPointsNode.getElementsByTagName("pp").length; j++) {
        var l_oPatternPointNode = l_oPatternPointsNode.getElementsByTagName("pp").item(j);
        var l_szPatternPointKey = l_oPatternPointNode.getAttribute("key");
        var l_szPointRank = l_oPatternPointNode.attributes.getNamedItem("rank").value;
        var l_szPointId = l_oPatternPointNode.attributes.getNamedItem("id").value;
        var l_fPointLong = Number(l_oPatternPointNode.attributes.getNamedItem("long").value);
        var l_fPointLat = Number(l_oPatternPointNode.attributes.getNamedItem("lat").value);
        var l_szPointType = l_oPatternPointNode.attributes.getNamedItem("type").value;
        var l_nDTF = l_oPatternPointNode.attributes.getNamedItem("dtf").value;

        m_oPatternPointDTFDict.Add(l_szPatternPointKey, l_nDTF);

        // Put the bus stops on the map - note: this is not a good idea as it slows down
        // the map if you have lots of routes loaded.
        if (l_bShowStops && (l_szPointType == POINTTYPE_BUS || l_szPointType == POINTTYPE_TIMEPOINT)) {
            var l_oPatternPointShape = new VEShape(VEShapeType.Pushpin, new VELatLong(l_fPointLat, l_fPointLong));


            if (l_szPointId.toLowerCase() != "new point") {
                l_oPatternPointShape.SetTitle(l_szPointId);
            }
            if (l_szPointType == POINTTYPE_TIMEPOINT && l_nDTF != 0 && l_szPointId.toLowerCase() != "new point") {

                l_oPatternPointShape.SetCustomIcon(format(m_sStopIcon, l_szStopColor));
                l_oPatternPointShape.SetIconAnchor(new VELatLong(l_fPointLat, l_fPointLong));
                map.AddShape(l_oPatternPointShape);
            }
            else if (l_szPointType == POINTTYPE_TIMEPOINT && m_oTimePointPatternPoint.Lookup(l_szPatternPointKey) != null) {
                // we'll add these timepoint shapes to a dictionary that we can access later when we
                // load the timetable as they may not be in the correct order with the routeheaders
                l_szPreviousTimePointPatternPointKey = l_szPatternPointKey;

                var l_oPoint = new Object;
                l_oPoint.Title = l_szPointId;
                l_oPoint.Shape = new VEShape(VEShapeType.Pushpin, new VELatLong(l_fPointLat, l_fPointLong));
                l_oPoint.Latitude = l_fPointLat;
                l_oPoint.Longitude = l_fPointLong;
                m_oTimePointDict.Add(l_szPatternPointKey, l_oPoint);

                l_oPatternPointShape.SetCustomIcon(format(m_sStopIcon, l_szStopColor));
                l_oPatternPointShape.SetIconAnchor(new VELatLong(l_fPointLat, l_fPointLong));
                map.AddShape(l_oPatternPointShape);
            }


            m_oPreviousTimePointPatternPointKeyDict.Add(l_szPatternPointKey, l_szPreviousTimePointPatternPointKey);
            m_oPatternPointShapeDict.Add(l_oPatternPointShape.GetID(), l_szPatternPointKey);
        }

        // save the long/lat so we can create our polyline   
        m_oShapePoints[j] = new VELatLong(l_fPointLat, l_fPointLong);
    }
    // end of patternPoints loop
    // add the shape points to the map
    m_oRouteShape = new VEShape(VEShapeType.Polyline, m_oShapePoints);
    var l_oLineColor = new VEColor(Number(l_szLineRGB.split(",")[0]), Number(l_szLineRGB.split(",")[1]), Number(l_szLineRGB.split(",")[2]), ROUTE_LINE_TRANSPARENCY);
    m_oRouteShape.SetLineColor(l_oLineColor);
    m_oRouteShape.SetLineWidth(ROUTE_LINE_WIDTH);
    m_oRouteShape.SetCustomIcon(format(m_sRouteIcon, l_szLineColor, l_szRouteID, GetForeGroundColor(l_szLineRGB)));
    m_oRouteShape.SetId(l_szRouteKey);

    // hide the square box icon
    m_oRouteShape.HideIcon();

    // add it to the map
    map.AddShape(m_oRouteShape);

    // Remeber this route shape in our dictionary
    m_oRouteShapeDict.Add(l_szRouteKey, m_oRouteShape.GetID());

    return m_oRouteShape;
}


// ImportXML   
// <summary>
// Load our schedule.xml or a specifed route into our global Xml object
// </summary>
// <param name="szRoute">If specified, we'll load this routeKey file</param>
// <returns></returns> 
function importXML(szRoute) {
    var l_szXmlSchedulePath;

    // if a route key is specified, load it, otherwise load the full schedule
    if (szRoute.length > 0) l_szXmlSchedulePath = "Schedule/" + szRoute + "-Route.xml";
    else l_szXmlSchedulePath = "Schedule/schedule.xml";

    // if Internet Explorer
    if (window.ActiveXObject) {
        m_oXMLHTTP = new ActiveXObject("Microsoft.XMLHTTP");
    }
    else if (window.XMLHttpRequest) // not IE
    {
        m_oXMLHTTP = new XMLHttpRequest();
    }

    if (m_oXMLHTTP != null) {
        m_oXMLHTTP.onreadystatechange = function(evt) {
            RouteXMLLoaded(szRoute);
        }

        m_oXMLHTTP.open("GET", l_szXmlSchedulePath, true);
        m_oXMLHTTP.send(null);
    }
    else {
        alert("Your browser does not support XMLHTTP.");
    }

}

// RouteXMLLoaded   
// <summary>
// Once the XML file is loaded, this method is called
// </summary>
// <param name="szRoute">The route that was loaded</param>
// <returns></returns> 
function RouteXMLLoaded(szRouteKey) {
    var l_oXmlDocument = null;
    if (checkReadyState(m_oXMLHTTP)) {
        m_oXmlDoc = m_oXMLHTTP.responseXML; //.documentElement;

        // Either load the full schedule or the specified route
        if (szRouteKey.length == 0)
            LoadFullSchedleMapData();
        else
            LoadRouteMapData(szRouteKey);
    }
}


// Page_Unload   
// <summary>
// Clean up all objects
// </summary>
// <returns></returns> 
function Page_Unload() {
    if (map != null) {
        map.Dispose();
        map = null;
    }
}

// OnCallError   
// <summary>
// If there is a page error we'll display it   
// </summary>
// <returns></returns> 
function OnCallError(result, txtresult, methodName) {
    alert("error " + methodName + txtresult);
}

// AJAX Methods

// SetupScheduleData   
// <summary>
// Gets a list of all routes belonging to a particular schedule
// using a C# call to GetAllRoutes
// </summary>
// <returns></returns> 
function SetupScheduleData() {
    var l_btnLoad = $get("btnLoadSchedule");
    var l_oStatus = document.getElementById('Status');
    l_btnLoad.disabled = true;
    l_btnLoad.value = "Loading data...";
    l_oStatus.innerHTML += '<p>Loading...</p>';
    var l_szScheduleKey = $get('ctl00_ContentPlaceHolderMain_m_ScheduleDropDown').value;

    if (l_szScheduleKey.length > 0)
        PageMethods.GetAllRoutes(l_szScheduleKey, OnSetupComplete, OnSetupError, null);
    else {
        l_btnLoad.disabled = false;
        l_btnLoad.value = "Load Schedule";
        l_oStatus.innerHTML = '<p>Please select an option before continuing.</p>';
    }
}

// OnSetupComplete   
// <summary>
// When the setup file has completed, inform the user that it is finished
// </summary>
// <returns></returns>    
function OnSetupComplete(e) {
    var l_btnLoad = $get("btnLoadSchedule");
    var l_oStatus = document.getElementById('Status');
    l_btnLoad.disabled = false;
    l_btnLoad.value = "Load Schedule";
    l_oStatus.innerHTML = '<p>Loading complete!</p>';
}

// OnSetupError   
// <summary>
// If the setup has an error, inform the user.
// </summary>
// <param name="error">The error object</param>
// <param name="userContext">The context of the error</param>
// <param name="methodName">The method that was being called</param>
// <returns></returns>      
function OnSetupError(error, userContext, methodName) {
    var l_btnLoad = $get("btnLoadSchedule");
    var l_oStatus = document.getElementById('Status');
    l_btnLoad.disabled = false;
    l_btnLoad.value = "Load Schedule";
    l_oStatus.innerHTML = '<p>Loading error!</p><p>' + error.get_message() + '</p>';
}

// Time table methods

// LoadTimeTable   
// <summary>
// Processes the timetable xml and adds the timePoints to the map
// </summary>
// <param name="l_szRouteKey">The routeKey that we are currently loading</param>
// <param name="l_oXmlDocument">The XML document that we are loading</param>
// <returns></returns>  
function LoadTimeTable(l_szRouteKey, l_oXmlDocument) {
    var l_oResultsNode = l_oXmlDocument.getElementsByTagName("results").item(0);

    var l_oRouteHeadersNode = l_oResultsNode.getElementsByTagName("routeHeaders").item(0);
    var l_oTripHeaderNodes = l_oRouteHeadersNode.getElementsByTagName("TripHeaders").item(0);
    var l_nTotalRouteHeaders = l_oRouteHeadersNode.getElementsByTagName("routeHeader").length;

    for (var i = 0; i < l_nTotalRouteHeaders; i++) {
        var l_oRouteHeaderNode = l_oRouteHeadersNode.getElementsByTagName("routeHeader").item(i);

        var l_oPatternPointNode = l_oRouteHeaderNode.getElementsByTagName("pp").item(0);

        var l_szRank = Number(l_oRouteHeaderNode.getAttribute("rank")) + 1;
        var l_szRHKey = l_oRouteHeaderNode.getAttribute("key");
        var l_szDesc = l_oRouteHeaderNode.getAttribute("description");

        if (l_oPatternPointNode != null) {
            var l_szPatternPointKey = l_oPatternPointNode.getAttribute("key");
            var l_szPointRank = l_oPatternPointNode.getAttribute("rank");
            var l_szPointId = l_oPatternPointNode.getAttribute("id");
            var l_fPointLong = Number(l_oPatternPointNode.getAttribute("long"));
            var l_fPointLat = Number(l_oPatternPointNode.getAttribute("lat"));
            var l_szPointType = l_oPatternPointNode.getAttribute("type");
            // Put the time point on the map

            var l_oPatternPointShape = new VEShape(VEShapeType.Pushpin, new VELatLong(l_fPointLat, l_fPointLong));

            l_oPatternPointShape.Title = l_szPointId;
            l_oPatternPointShape.SetCustomIcon("<div class='pinStyle'><div class='text'>" + l_szRank + "</div></div>");
            l_oPatternPointShape.SetIconAnchor(new VELatLong(l_fPointLat, l_fPointLong));
            l_oPatternPointShape.SetZIndex(1001); // put this over top all other icons

            map.AddShape(l_oPatternPointShape);

            m_oPatternPointShapeDict.Add(l_oPatternPointShape.GetID(), l_szPatternPointKey);
            m_oTimePointPatternPoint.Add(l_szPatternPointKey, l_oPatternPointShape.GetID());
        }
    }

    // Do all the rest of stuff that has to be done after all points are loaded on the map

    // setup the start/stop values from what we have loaded
    SetupStartStopVars();

    // continue loading the map
    var l_oXmlDocument = m_oXmlDoc.documentElement;
    var l_oRouteShape = LoadRouteIntoMap(l_oXmlDocument, true);
    map.SetMapView(l_oRouteShape.GetPoints());
    HighlightRoute(l_oRouteShape.GetID());

    PageMethods.GetVehiclesOnRoute(l_szRouteKey, OnGetVehiclesOnRouteComplete, OnGetVehiclesOnRouteError, null);
    m_nTimerID = setTimeout("GetVehiclePosition('" + l_szRouteKey + "')", TIMER_INTERVAL);

}


// SetupStartStopVars   
// <summary>
// This function configures our global variables that represent the start
// and stop values.
//
// Sets the m_oPickUpStop and m_oDropOffStop objects with values based on the query string
// or dropdown values.
// </summary>
// <returns></returns>  
function SetupStartStopVars() {
    ResetTimeTableCells();

    var l_sStartQueryString = queryString("start");
    var l_sStartDropDownString = $get('ctl00_ddlStart').value;

    var l_sStopQueryString = queryString("stop");
    var l_sStopDropDownString = $get('ctl00_ddlStop').value;

    if ((l_sStartQueryString.length > 0 && l_sStartQueryString != "false") && l_sStartDropDownString.length == 0)
        m_oPickUpStop.Key = l_sStartQueryString;
    else if (l_sStartDropDownString.length > 0)
        m_oPickUpStop.Key = l_sStartDropDownString;
    else {
        m_oPickUpStop = new Object;
    }

    if ((l_sStopQueryString.length > 0 && l_sStopQueryString != "false") && l_sStopDropDownString.length == 0)
        m_oDropOffStop.Key = l_sStopQueryString;
    else if (l_sStopDropDownString.length > 0)
        m_oDropOffStop.Key = l_sStopDropDownString;
    else {
        m_oDropOffStop = new Object;
    }

    var l_sDayType = GetDayTypeTablePrefix();

    if (m_oPickUpStop.Key != null && m_oPickUpStop.Key.length > 0 && m_oPickUpStop.Key != "false") {
        var l_oCell = document.getElementById(l_sDayType + m_oPickUpStop.Key);
        if (l_oCell != null) {
            m_oPickUpStop.Name = l_oCell.innerText;
            m_oPickUpStop.Rank = Number(l_oCell.parentNode.firstChild.innerText);
        }
    }
    if (m_oDropOffStop.Key != null && m_oDropOffStop.Key.length > 0 && m_oDropOffStop.Key != "false") {
        var l_oCell = document.getElementById(l_sDayType + m_oDropOffStop.Key);
        if (l_oCell != null) {
            m_oDropOffStop.Name = l_oCell.innerText;
            m_oDropOffStop.Rank = Number(l_oCell.parentNode.firstChild.innerText);
        }
    }
}

function GetDayTypeTablePrefix() {
    var l_oPrimaryTableCell = document.getElementById("PrimaryTable");
    var l_oTablesCell = l_oPrimaryTableCell.childNodes[0];
    var l_oToday = new Date();
    var l_oDayOfWeek = l_oToday.getDay();

    for (var i = 0; i <= l_oTablesCell.childNodes.length; i++) {
        var l_oCell = l_oTablesCell.childNodes[i];
        if (l_oCell != null &&
            l_oCell.childNodes.length > 0) {
            var l_sDayString = l_oCell.attributes.getNamedItem('dayflagstring').value;
            if (l_sDayString.split(",")[l_oDayOfWeek] == "1") {
                return l_oCell.id;
            }

        }

    }
}

// LoadTimeTableXML   
// <summary>
// Loads the time table xml into memory
// </summary>
// <param name="l_szRouteKey">The routeKey that we are currently loading</param>
// <returns></returns> 
function LoadTimeTableXML(l_szRouteKey) {

    var l_szXmlSchedulePath;
    var l_oXmlTimeTableNode = null;
    if (l_szRouteKey.length > 0) {
        l_szXmlSchedulePath = "Schedule/" + l_szRouteKey + "-RouteTimetable.xml";

        if (window.ActiveXObject) {
            m_oTimeXMLHTTP = new ActiveXObject("Microsoft.XMLHTTP");
        }
        else if (window.XMLHttpRequest) {
            m_oTimeXMLHTTP = new XMLHttpRequest();
        }

        if (m_oTimeXMLHTTP != null) {
            m_oTimeXMLHTTP.onreadystatechange = function(evt) {
                RouteTimeTableXMLLoaded(l_szRouteKey);
            }
            m_oTimeXMLHTTP.open("GET", l_szXmlSchedulePath, true);
            m_oTimeXMLHTTP.send(null);
        }
        else {
            alert("Your browser does not support XMLHTTP.");
        }

    }
}

// LoadTimeTableXML   
// <summary>
// Loads the time table xml into memory
// </summary>
// <param name="l_szRouteKey">The routeKey that we are currently loading</param>
// <returns></returns> 
function RouteTimeTableXMLLoaded(szRouteKey) {
    if (checkReadyState(m_oTimeXMLHTTP)) {
        var l_oXmlTimeTableNode = m_oTimeXMLHTTP.responseXML; //.documentElement;

        LoadTimeTable(szRouteKey, l_oXmlTimeTableNode);
    }
}


// LoadRoutesXML
// <summary>
// Loads the simplified routes.xml
// </summary>
// <returns></returns> 
function LoadRoutesXML() {

    var l_szXmlRoutesPath = "Schedule/routes.xml";
    var l_oXmlRoutesNode = null;

    if (window.ActiveXObject) {
        m_oRoutesXMLHTTP = new ActiveXObject("Microsoft.XMLHTTP");
    }
    else if (window.XMLHttpRequest) {
        m_oRoutesXMLHTTP = new XMLHttpRequest();
    }

    if (m_oRoutesXMLHTTP != null) {
        m_oRoutesXMLHTTP.onreadystatechange = function(evt) {
            RoutesXMLLoaded();
        }
        m_oRoutesXMLHTTP.open("GET", l_szXmlRoutesPath, true);
        m_oRoutesXMLHTTP.send(null);
    }
    else {
        alert("Your browser does not support XMLHTTP.");
    }

}


// RoutesXMLLoaded   
// <summary>
// Loads the routes xml file into memory
// </summary>
// <returns></returns> 
function RoutesXMLLoaded() {
    if (checkReadyState(m_oRoutesXMLHTTP)) {
        var l_oXmlRoutesNode = m_oRoutesXMLHTTP.responseXML; //.documentElement;

        LoadAllRoutes(l_oXmlRoutesNode);
    }
}

// LoadAllRoutes
// <summary>
// Parse the simplified routes node
// </summary>
// <returns></returns> 
function LoadAllRoutes(l_oXmlDocument) {
    // lets start looping through the results
    var l_oRoutesNode = l_oXmlDocument.getElementsByTagName("routes").item(0);
    var l_nTotalRoutes = l_oRoutesNode.getElementsByTagName("route").length;

    for (var i = 0; i < l_nTotalRoutes; i++) {
        var l_oRouteNode = l_oRoutesNode.getElementsByTagName("route").item(i);

        var l_szRouteKey = l_oRouteNode.getAttribute("key");

        SendAsyncRequest("Schedule/" + l_szRouteKey + ".js");
    }
}

