var bounds;
  
function loadData(filename) {
  $('#loading .check').hide();
  $('#blackout').fadeIn();
  $('#loading').fadeIn();
  $.ajax({
    url: filename,
    success: dataLoaded,
    error: dataError
  });
   
  $('#loading-mp .check').show();
}

function dataError() {
  $('#loading').hide();
  $('#notify-internal').html('<p>There seems to have been an error loading this data trace.</p>');
  $('#blackout').fadeIn();
  $('#notify').fadeIn();
}

function dataLoaded(data) {
  $('#loading-dl .check').show();
  
  var rowStrings = String(data).split("\n");
  var liBlob = [];

  // Extract all the rmc data in one go.
  $.each(rowStrings, function(i, rowString) {
    var fields = rowString.split(',');
    if (fields[0] == '$GPRMC') {
      var data = {
        latlng: new google.maps.LatLng(parseLL(fields[3]), -parseLL(fields[5])),
        speed: fields[7],
        bearing: fields[8],
        time: Time.parse(fields[1]).offsetHours(HOUR_OFFSET),
        flag: null
      };
      
      if (data.speed < STALL_SPEED) {
        data.stopped = true;
      }

      allPoints.push(data);
      liBlob.push('<li id="i' + (allPoints.length - 1) + '"></li>');
    }
  });
  
  // Create all the time li elements at once. WAY faster this way.
  $('#timeline-internal ol').html(
    '<li class="bookend" id="bookend-start"></li>' + liBlob.join('') +
    '<li class="bookend" id="bookend-finish"></li>');
  
  if (allPoints.length < 5) {
    dataError();
  } else {
    setTimeout(function() { $('#loading-dp .check').show(); }, 0);
    processPoint(0);
  }
}

function processPoint(i) {
  var pointData = allPoints[i];
  var li = $('#i' + i);
  
  if (!bounds) {
    bounds = new google.maps.LatLngBounds(pointData.latlng);
  } else {
    bounds.extend(pointData.latlng);
  }
  
  setTimelineEntry(i, li, pointData);
  
  if (++i >= allPoints.length) {
    allPointsProcessed();
  } else {
    // Process the next one.
    if (i % 500 == 0) {
      $('#data-percent-done').html(Math.round(i / allPoints.length * 100));
      setTimeout(function() { processPoint(i); }, 0); 
    } else {
      processPoint(i);
    }
  }
}

function setTimelineEntry(index, li, pointData)  {
  var c = gradient.getColorAt(pointData.speed / MAX_SPEED);
  li.css({ 'background': c.css(), 'border-color': c.lighten(0.7).css() });
  
  $.each([ 2, 4, 8, 16 ], function(i, val) {
    if (index % val == 0) { li.addClass('z' + val); }
  });

  if (pointData.time.seconds == 0) {
    li.append('<span class="time-tick ' + (pointData.time.minutes % 5 == 0 ? 'min5' : '') + ' ' + (pointData.time.minutes % 30 == 0? 'min30' : '') + '">' +
        '<span class="time-tick-label">' + pointData.time.render() + '</span></span>');
  } else if (pointData.time.seconds % 10 == 0) {
    li.append('<span class="time-tick-10s"></span>');  
  }
}

function allPointsProcessed() {
  // We're done.
  $('#loading-di .check').show();
  $('#data-percent-done').html(100);

  var polypoints = [];
  $.each(allPoints, function(i, pointData) {
    if (!pointData.stopped) {
      polypoints.push(pointData.latlng);
    }
  });

  var bigPolyline = new BigPolyline(polypoints);
  map.addOverlay(bigPolyline);
  $('#loading-pl .check').show();

  map.setCenter(bounds.getCenter(), map.getBoundsZoomLevel(bounds));
    
  setTimeout(function() {
    $('#timeline-internal').fadeIn();
    $('#blackout').fadeOut();
    $('#loading').fadeOut();
    setTimelineZoom(timelineZoom);
  }, 100);
}

