    // This file handles the GPS data and plotting
    // Globals

    var do_short_run = 0;
    var short_run_length = 200;

    var g_tracks = new Array;
    var g_tracks_index = new Array;

    // ********************************************************************
    // Plot Object Constructor
    // ********************************************************************
    function  ObjGPSPlot(lat, lon, strTime)
    {
      this.lat = lat;
      this.lon = lon;
      this.tim = strTime;
      this.secs = convTime(strTime);
    }

    // ********************************************************************
    // XY Object Constructor
    // ********************************************************************
    function  ObjXY(x,y)
    {
      this.x = x;
      this.y = y;
    }


    // ********************************************************************
    // Transformation between Lat/Lon and Map X/Y requires
    // some mapping constants
    // These are only calculated once
    // ********************************************************************
    function CalcTransformsConstant()
    {
      var tmp;
      // The limits of the map are defined by MapMin to MapMax, expressed in Lat & Lon numerical units


      // Now make sure its going to be the right way up
      // Check the bounds
      if(MapMin.x > MapMin.x)
      {
        debug("CalcTransformsConstant() : Map x co-ords inverted : fixing ....");
        tmp = MapMax.x;
        MapMax.x = MapMin.x;
        MapMin.x = tmp;
      }

      if(MapMin.y > MapMin.y)
      {
        debug("CalcTransformsConstant() : Map y co-ords inverted : fixing ....");
        tmp = MapMax.y;
        MapMax.x = MapMin.y;
        MapMin.y = tmp;
      }
    
      // The limits of the plotting area are defined by PlotMin to PlotMax, express in Lat & Lon numerical units
      PlotMin = new ObjXY(0,0);
      PlotMax = new ObjXY(400,400);
      PlotMax.x = 1036;
      PlotMax.y = 837;

      // The scaling is calculated for both X and Y
      Pos2PlotScale = new ObjXY(((PlotMax.x-PlotMin.x)/(MapMax.x-MapMin.x)),((PlotMax.y-PlotMin.y)/(MapMax.y-MapMin.y))); 

      // Now adust the scale factor for aspect ratio, which means choose a projection
      // Normalise to the latitude, i.e. 1 minute = 1nm
      tmp = Math.cos(((MapMax.y+MapMin.y)/2)* Math.PI/180);
      // For the plotting ratio to be correct, scale X = tmp * Scale Y
      // Adjust the plotting ratio
      if (Pos2PlotScale.x < Pos2PlotScale.y * tmp)
      {
        // Adjust X scale factor
        Pos2PlotScale.x = Pos2PlotScale.y * tmp; 
        debug("CalcTransformsConstant() :  Normalising X");
      }
      else
      {
        // Adjust Y scale factor
        Pos2PlotScale.y = Pos2PlotScale.x / tmp; 
        debug("CalcTransformsConstant() :  Normalising Y");
      }

      debug("CalcTransformsConstant() : Scale X : " + Pos2PlotScale.x + "  Y : " + Pos2PlotScale.y + "  Angle factor : " + tmp);
    }

    // ********************************************************************
    // Transform a Lat, Lon to a pixel plot position
    // ********************************************************************
    function mapLatLonToPx(LatLon)
    {
      var pixel = new ObjXY(0,0);
      pixel.x = (LatLon.lon-MapMin.x) * Pos2PlotScale.x;
      debug("mapLatLonToPx() : MapMin : " + MapMin.x + "  MapMax : " + MapMax.x +"  Lon : " + LatLon.lon + " --> Pixel.x : " + pixel.x);
      // Note, the plotting needs to invert the vertical axis
      // This routine takes care of it
      pixel.y = (MapMax.y-LatLon.lat) * Pos2PlotScale.y;
      return pixel;      
    }


    // ********************************************************************
    // ********************************************************************
    function getTracks()
    {
      var t;
      var i, j, k;    // Keep these local
      var lat, lon;
      var strTime;
      debug("getTracks() : Entry");

      // Get each track in the file
      var xmltracks = xmlDoc.getElementsByTagName('trk');
      debug("getTracks() : No. of tracks = " + xmltracks.length);

      // For each track
      for(i=0; i<xmltracks.length; i++)
      {
        // Get the track segments in the track
        var trackseg = xmltracks[i].getElementsByTagName('trkseg');
        var y = trackseg.length;
        debug("getTracks() : Track " + i + " contains " + y + " track segments");

        // Create a new track and on the global tracks array
        // We add the position points later
        g_tracks[i] = new Array;

        // For each track segment, get the track points
        for(j=0; j<trackseg.length; j++)
        {
          var gpsTrack = new Array
          var trackpts = trackseg[j].getElementsByTagName('trkpt');

          // For each track point, get the time and co-ordinates
          debug("getTracks() : Track : " + i + "  Segment : " + j);

          kLimit = trackpts.length;
          if (do_short_run) kLimit = Math.min(short_run_length, kLimit);  // Trim for development, but not beyond the end

          debug("getTracks() : Looping on " + kLimit + " elements in track segment")

          for (k=0; k<kLimit; k++)
          {
            // Lat and Lon are attributes
            lat = trackpts[k].attributes.getNamedItem("lat").value;
            lon = trackpts[k].attributes.getNamedItem("lon").value;

            // Time is a tag
            t = trackpts[k].getElementsByTagName('time');
            strTime = t[0].firstChild.nodeValue;

            // Add this point to the curent track list
            g_tracks[i].push(new ObjGPSPlot(lat, lon, strTime));

          } // End of trackpoints iteration
          debug("getTracks() : Track point iteration of segment finished")

        }// End of track segments iteration
        debug("getTracks() : Iteration of segments in track finished")
      }// End of track iteration
      debug("getTracks() : Iteration of tracks finished")

      debug("getTracks() complete");
    }


    // Draw track segment
    function drawTrack(positions)
    {
      var xList = new Array;
      var yList = new Array;
      var gpsPoint = new ObjGPSPlot(0,0,0);
      var plotPoint = new ObjXY(0,0);
      var pLimit;
      var i;

      // Determine how far to iterate
      pLimit = positions.length;

      debug("drawTrack() : No of points in array = " + positions.length);

      if (do_short_run) // for debugging
      {
        pLimit = Math.min(short_run_length, pLimit);
      }
      debug("drawTrack() : Looping on first " + pLimit + " elements of array")

      // Now iterate the array of positions
      for(i=0; i<pLimit; i++)
      {
      	gpsPoint = positions[i];	          // Get this position

        debug("drawTrack() : " + i + "   " + positions[i].tim + "  Positions lon, lat, y = " + positions[i].lon + ",  " + positions[i].lat);
        convTime(positions[i].tim);

        plotPoint = mapLatLonToPx(gpsPoint);	  // Transform co-ords to plotting
        xList[i] = plotPoint.x;
	      yList[i] = plotPoint.y;
        debug("drawTrack() : plotPoint X,Y = " + plotPoint.x + ", " + plotPoint.y + "  xList : " + xList[i] + "  yList : " + yList[i]);
      }

      // Now plot the track
      plotArea.setStroke(2);                    // Drawing width
      plotArea.drawPolyline(xList, yList);
      plotArea.paint();
      debug("drawTrack() : Exit");
    }


    //
    // This functions draws tracks
    // in the g_tracks array,
    // up to the time indicated by g_trackTimer
    // This function returns non zero to terminate the animation
    //
    function drawTracks()
    {
      var i, k;
      var upto;
      var mGPSPlot = new ObjGPSPlot(0,0,0);
      var retVal = 1;   // Default to stopping, but if a track is still running we will clear this value
    
      debug("drawTracks() : call drawTrack() now for each track in g_tracks");
      for(i=0; i<g_tracks.length; i++)
      {

        // For the current track,
        // find how much of it (index k) we need
        // to get to the current time marker, g_trackTimer
        // Obviously, we can't go past the end of the track!
        
        k=g_tracks_index[i];
        exitLoop = false;
        while (!exitLoop)
        {
          k++;
          if (k < g_tracks[i].length)
          {
            // Still in the track
            mGPSPlot = g_tracks[i][k];
            if (g_trackTimer < mGPSPlot.secs) // Found the current time in the track?
            {
              retVal = 0; // Indicate that we've haven't reached the end of the track yet
              exitLoop = true;
              debug("drawTracks() : Next time index found at : " + k);
            }
          }
          else
          {
            // Overrun the end of the track
            k = g_tracks[i].length;
            exitLoop = true;
            debug("drawTracks() : Reached end of track at " + k);
            k--;  // Gone too far by one, so mend it
            // We've reached the end of the track
          }

        }
        debug("drawTracks() : Track : " + i + "  Index : " + k + "  Target time : " + g_trackTimer + "  Track time : " + mGPSPlot.secs);

        // Set the colour
        plotArea.setColor(colour[i%colour.length]);

        // And now draw the segment
        drawTrack(g_tracks[i].slice(g_tracks_index[i], k));

        // Update the pointer
        g_tracks_index[i] = k-1; // Go back one so that we don't get gaps between segments
      }
      return retVal;
    }

    // This function, processData() is called when the XML file is loaded
    function processData()
    {
      var i, j;

      controlState(1);  // Go to the pre-processing stage

      // Define the plotting area;
      plotArea = new jsGraphics("plot_area");

      // Prepare some mapping constants
      CalcTransformsConstant();

      debug("processData() : About to process the tracks");
      getTracks();    // Pre-process the tracks

      debug("processData() : g_Tracks length : " + g_tracks.length);
      for(i=0; i<g_tracks.length; i++) debug("processData() : g_Tracks length[" + i + "] : " + g_tracks[i].length);

      resetTrackTimer();  // Initialise the timer value
      controlState(2);    // ready to run

      debug("processData() : Finished");
    }

    // Display appropriate messages to the user will loading, preparing, etc.
    function controlState(state)
    {
      debug("controlState() : Entry : state = " + state);
      switch (state)
      {
      case 0:   // Go from loading to preparing
        controlsLoading.style.visibility = "visible";  // Show the loading message
        controlsLoading.style.display = "block";       

        controlsPrep.style.visibility = "hidden";     // Hide the Preprocessing message
        controlsPrep.style.display = "none";          // Claim back the space

        controlsReady.style.visibility = "hidden";    // Controls are hidden
        controlsReady.style.display = "none";         // Claim back the space
        break;

      case 1:   // Go from loading to preparing
        controlsLoading.style.visibility = "hidden";  // Hide the loading message
        controlsLoading.style.display = "none";       // Claim back the space

        controlsPrep.style.visibility = "visible";    // Show the Preprocessing message
        controlsPrep.style.display = "block";         // Show this one

        controlsReady.style.visibility = "hidden";    // Controls are hidden
        controlsReady.style.display = "none";         // claim back the space
        break;

      case 2:
        controlsLoading.style.visibility = "hidden";  // Hide the loading message
        controlsLoading.style.display = "none";       // Claim back the space

        controlsPrep.style.visibility = "hidden";     // Hide the preprocessing message
        controlsPrep.style.display = "none";          // Claim back the space

        controlsReady.style.visibility = "visible";   // Show the Controls
        controlsReady.style.display = "block";
        break;

      default:
        break;
      }

    }


    // Convert time from the XML string format into a numerical format
    function convTime(GPSTime)
    {
      var year = 0;
      var month = 0;
      var day = 0;
      var hours = 0;
      var mins = 0;
      var secs = 0;
      var seconds;

//      debug("convTime() : Entry");

      if(GPSTime.length == 20)
      {
        year = Number(GPSTime.slice(0,4));
        month = Number(GPSTime.slice(5,7));
        day = Number(GPSTime.slice(8,10));
        hours = Number(GPSTime.slice(11,13));
        mins = Number(GPSTime.slice(14,16));
        secs = Number(GPSTime.slice(17,19));
      }

      seconds = (hours * 3600) + (mins * 60) + secs;

//      debug("convTime() : Date : " + year + "/" + month + "/" + day + "  " + hours + ":" + mins + ":" + secs + " Seconds =" + seconds);

      return seconds;
    }

    function reportTime(t,status)
    {
      var rS = t%60;
      var rM = ((t%3600) - rS)/60;
      var rH = Math.floor(t/3600) + 1; // BST adjustment
      document.getElementById("time").innerHTML = "Time : " + (rH<10?"0"+rH:rH) +":" + (rM<10?"0"+rM:rM) + ":" + (rS<10?"0"+rS:rS) + status;
    }



    // ********************************************************************
    // ********************************************************************
    function demoPlot()
    {
      // Graphics functions

      // Next line is already done elsewhere
      // It defines which DIV we are drawing in
      //plotArea = new jsGraphics("plot_area");

      plotArea.setColor("maroon");
      plotArea.fillEllipse(450, -5, 40, 70); 
      plotArea.setStroke(5);                    // Drawing width
      plotArea.setColor("#ff6666");
      plotArea.drawPolyline(new Array(90, 640, 90, 100), new Array(0, 25, 90, 300));
    
      plotArea.setColor("green");
      plotArea.drawRect(100,40,200,18);
  
      plotArea.setColor("blue");
      plotArea.setStroke(Stroke.DOTTED); 
      plotArea.drawRect(-20,0,32,50);
      plotArea.drawEllipse(250,10,100,100);
      plotArea.paint();
    }



    // ********************************************************************
    // ********************************************************************
    function MappingTest()
    {
      debug("Report the constants");
      debug("MapMin X:"+ MapMin.x + "  Y:" + MapMin.y);

      // Now do a quick test
      var myMapPoint = new ObjXY(0,0);
      var myPlotPoint = new ObjXY(0,0);

      myMapPoint = MapMin;
      myPlotPoint = mapLatLonToPx(myMapPoint);
      debug("MapMin plotted at X:" + myPlotPoint.x + "  Y:" + myPlotPoint.y);
      myMapPoint = MapMax;
      myPlotPoint = mapLatLonToPx(myMapPoint);
      debug("MapMax plotted at X:" + myPlotPoint.x + "  Y:" + myPlotPoint.y);
    }


