 /***********************************************************************************************
 * Copyright (c) 2007 InIT, ZHAW Zürcher Hochschule für angewandte Wissenschaften
 * All rights reserved.
 * 
 * AIR TRAFFIC v1.8 - Visualisierung von Transponder-Daten mittels Mashup
 *
 * --- DIPLOMARBEIT 2007 ---
 * 
 * Authors:  Daniel Kramarz <kramadan@students.zhaw.ch>
 *           Andreas Loeber <loberand@students.zhaw.ch>
 *
 * Tutor:    Dr. Karl Rege <karl.rege@zhaw.ch>
 *
 * modified: 7.12.2007 Andreas Loeber
 *           getAirplanesInRange() und getAirportsInRange() auf gerundete Koordinaten geaendert,
 *           damit Caching besser greift.
 *
 ***********************************************************************************************/
 
//*****************************************************************************
// GLOBAL VARIABLES
//*****************************************************************************
var map;                      //Google Map
var airplanes = new Array();  //holder of all airplane objects
var airports = new Array();   //holder of all airport objects
var selectedAirplane = false; //reference to the airplane object that's selected
var tracking = false;         //reference to the airplane object that's being tracked
var timestamp = 0;            //timestamp of the last newest tracking point (only get new data)
var periodicalUpdater;        //periodical updater that call requestAirplanesInRange() periodically
var ajaxRequest;              //current ajax Request (needed to cancel the running request)
var ajaxTimeoutTimer;         //ajax timeout timer. On timeout display message of connectivity error
var ajaxTimedOut = false;     //flag set to true when ajax request timed out 
var appStarted = false;       //flag set to true when app is startd (to show startup message)
var boundSW;
var boundNE;
var allowedBounds;
var boundMargin;

//*****************************************************************************
// OPTIONS (loading with default values from the config file)
//*****************************************************************************
var language = defaultLanguage;
var units = defaultUnits;
var colorBar = defaultColorBar;
var mapType = defaultMapType;
var isTrackingEnabled = defaultTrackingEnabled;
var isShadowEnabled = defaultShadowEnabled;
var mapZoom = defaultMapZoom;
var mapCenterLatitude = defaultMapCenterLatitude;
var mapCenterLongitude = defaultMapCenterLongitude;
var withoutTimeout = false;
var kioskMode = false;
var trackICAO = false;
  
//*****************************************************************************
// MAIN INITIALISATION
//*****************************************************************************
google.load('maps', '2');             //load Google Maps JS library version 2 
google.setOnLoadCallback(initialize); //on page load, call the function initialize()

function initialize()
{
  //Disable shadows on older browsers (like IE6) due to half-transparent PNGs
  if (isOlderBrowser()) isShadowEnabled = false;
  
  //Load options
  loadPropertiesFromCookie();

  //Preload images
  preloadImages();
  
  //insert additional HTML elements
  new Insertion.After('map', '<div id="statusBar"></div>');
  new Insertion.After('map', '<div id="infoBar" style="width:240px; display:none"></div>');
  new Insertion.After('map', '<img id="colorBar" src="images/colorbar_' + (colorBar != 'automatic' ? colorBar : getDefaultColorBarForMapType(mapType)) + '_' + (units == 'aviatic' ? 'ft' : 'm') + '.png" alt="" title="" />');
  new Insertion.After('map', '<img id="loader" src="images/ajax-loader.gif" alt="loading..." title="loading..." />');
  
  if (!kioskMode) 
  {
    new Insertion.After('map', '<img id="zoomIn" src="images/zoom_in.png" alt="' + texts[language].zoomIn + '" title="' + texts[language].zoomIn + '" onclick="zoomInMap()" />');
    new Insertion.After('map', '<img id="zoomOut" src="images/zoom_out.png" alt="' + texts[language].zoomOut + '" title="' + texts[language].zoomOut + '" onclick="zoomOutMap()" />');
  } else {
    new Insertion.After('map', '<img id="zoomInKiosk" src="images/zoom_in_kiosk.gif" alt="' + texts[language].zoomIn + '" title="' + texts[language].zoomIn + '" onclick="zoomInMap()" />');
    new Insertion.After('map', '<img id="zoomOutKiosk" src="images/zoom_out_kiosk.gif" alt="' + texts[language].zoomOut + '" title="' + texts[language].zoomOut + '" onclick="zoomOutMap()" />');
  }
  new Insertion.After('map', '<div id="xTabs"></div>');
  new Insertion.After('map', '<div id="transparentBackground"><div id="message"></div></div>');
  new Insertion.After('map', '<div id="timeDisplay" style="display:none">...</div>');
  new Insertion.After('map', '<div id="liveIcon"><img src="images/live.png" alt="" /></div>');
  
  //update XTabs (scrollable tabs on the bottom of the page)
  if (!kioskMode) 
  {
    updateXTabs();
  } else {
    // Google-Logo und Nutzungsbedingungen mit transparentem div überdecken
    new Insertion.After('map', '<div id="skyguide" style="position:absolute; bottom:20px; left:3px; z-index:10"><img src="images/skyguide.gif" width="70px" height="35px" alt="" /></div>');
    new Insertion.After('map', '<div id="skyguide" style="position:absolute; bottom:20px; right:2px; z-index:10"><img src="images/skyguide.gif" width="110px" height="20px" alt="" /></div>');

    // ZHAW-Logo
    new Insertion.After('map', '<div id="skyguide" style="position:absolute; bottom:25px; left:75px; z-index:10"><img src="images/zhaw.gif" width="31px" height="32px" alt="ZHAW-Logo" /></div>');
  }

  //adjust window size
  adjustWindow();
  
  //Test browser to be compatible with Google maps
  if (!GBrowserIsCompatible())
  {
    showMessage('Your browser isn\'t compatible with Google maps!');
    return;
  }
  
  //show startup message
  showMessage('Loading air traffic information... Please wait.');
  
  //initialize Google map
  initializeGoogleMaps();
  
  //request airplanes in range for the first time (because periodicalUpdater starts after first timeout)
  requestAirplanesInRange();
  
  //check if there is live-data available
  new Ajax.Request(airtrafficService + '/isRunning', {
    method: 'get',
    onSuccess: function(transport)
    {
      //evaluate JSON object
      var isRunning = transport.responseText.evalJSON();
      if (!isRunning)
      {
        showMessage('Currently no live-data available!<br />Come back later...');
      }
      else
      {
        //setup AJAX loader image and AJAX timeout timer
        Ajax.Responders.register({
          onCreate: function() //on create AJAX request...
          {
            $('loader').style.display = 'block';
            if (!ajaxTimeoutTimer) ajaxTimeoutTimer = setTimeout('ajaxConnectionFailed()', ajaxConnectionFailedTimeout*1000);
          },
          onComplete: function(transport) //on complete AJAX request...
          {
            if (Ajax.activeRequestCount == 0 && !ajaxTimedOut)
            {
              clearTimeout(ajaxTimeoutTimer);
              ajaxTimeoutTimer = null;
              $('statusBar').innerHTML = 'Update done.';
              $('liveIcon').style.display = 'block';
              $('loader').style.display = 'none';
              
              if (!appStarted)
              {
                appStarted = true;
                hideMessage();
                
                //Preload airplane images
                //preloadAirplaneImages();
              }
            }
          }
        });

        //initialize interval check
        intervalCheckTimer = new PeriodicalExecuter(checkInterval, intervalCheckInterval);

        //initialize version check
        versionCheckTimer = new PeriodicalExecuter(checkVersion, versionCheckInterval);

        //initialize inactivity timeout check
        if(!withoutTimeout)
        {
          timeoutCheckTimer = new PeriodicalExecuter(inactivityCheck, inactivityCheckInterval);
          GEvent.addListener(map, "mousemove", function( pt) {
            lastMouseMoveEventTimestamp = new Date().getTime();
          } );
        }

        //initialize AJAX periodical updater (calls function updateAirplaneData)
        periodicalUpdater = new PeriodicalExecuter(requestAirplanesInRange, updateTimeout);
  
        //request airports in range (afterwards this method is called after moving the map or after zooming)
        requestAirportsInRange();
      }
    }
  });
}

// ------------- INTERVAL ------------

function checkInterval() {
  ajaxRequest =
  new Ajax.Request('/var/interval.txt', {
    method: 'get',
    onSuccess: function(transport)
    {
      var result = transport.responseText.replace (/^\s+/, '').replace (/\s+$/, '');
      updateTimeout = result;
      periodicalUpdater.stop();
      periodicalUpdater = new PeriodicalExecuter(requestAirplanesInRange, updateTimeout);
    }
  });
}

// ------------- VERSION CHECK FUNCTIONS ------------

function checkVersion() {
  ajaxRequest =
  new Ajax.Request('/var/version.txt', {
    method: 'get',
    onSuccess: function(transport)
    {
      var result = transport.responseText.replace (/^\s+/, '').replace (/\s+$/, '');
      if(result != currentScriptVersion) {
	document.location.reload();
      }
    }
  });
}

// ------------- INACTIVITY FUNCTIONS ----------------

function inactivityCheck()
{
  var now = new Date().getTime();
  if((now - lastMouseMoveEventTimestamp)/1000 > inactivityTimeoutTime) {
    timeoutCheckTimer.stop();
    inactivityStartTimestamp = new Date().getTime();
    timoutWarningTimer = new PeriodicalExecuter(inactivityWarning, 1);
  }
}
function mouseMoved() {
  lastMouseMoveEventTime = new Date().getTime();
}
function inactivityWarning() {
  var now = new Date().getTime();
  var delta = Math.round((now - inactivityStartTimestamp)/1000);
  if(delta < inactivityWarningTime) {
    var deltaRev = inactivityWarningTime - delta;
    showMessage('It seems that you are away. Click to continue!' + '<p />(Waiting for ' + deltaRev + ' seconds)<p /><input type="button" value="Continue" onclick="clearInactivity()" />');
  } else {
    timoutWarningTimer.stop();
    intervalCheckTimer.stop();
    periodicalUpdater.stop();              //stop periodical updater
    versionCheckTimer.stop();
    ajaxTimedOut = true;
    $('loader').style.display = 'none';
    ajaxRequest.transport.abort();         //stop running AJAX request
    showMessage('Inactivity timeout. Click to restart.<p /><input type="button" value="Restart" onclick="document.location.reload()" />');
  }
}
function clearInactivity() {
  hideMessage();
  timoutWarningTimer.stop();
  lastMouseMoveEventTimestamp = new Date().getTime();
  timeoutCheckTimer = new PeriodicalExecuter(inactivityCheck, inactivityCheckInterval);

}


//PRELOAD IMAGES
//(http://elouai.com/javascript-preload-images.php)
function preloadImages()
{
  if (document.images)
  {
    var image = new Image();
    for(var angle = 0; angle < 360; angle += 10)
    {
      image.src = 'images/airplanes/airplane' + angle + '.png';
      image.src = 'images/airplanes/shadow_airplane' + angle + '.png';
      image.src = 'images/airplanes/r_airplane' + angle + '.png';
      image.src = 'images/airplanes/y_airplane' + angle + '.png';
      image.src = 'images/airplanes/g_airplane' + angle + '.png';
      
      image.src = 'images/airplanes/zeppelin' + angle + '.png';
      image.src = 'images/airplanes/shadow_zeppelin' + angle + '.png';
      image.src = 'images/airplanes/r_zeppelin' + angle + '.png';
      image.src = 'images/airplanes/y_zeppelin' + angle + '.png';
      image.src = 'images/airplanes/g_zeppelin' + angle + '.png';
    }
    image.src = 'images/tab.png';
  }
}



//*****************************************************************************
// GOOGLE MAPS STUFF
//*****************************************************************************

//INITIALIZE GOOGLE MAPS
function initializeGoogleMaps()
{
  //instanciate new Google Map and define some controls
  map = new GMap2(document.getElementById('map'));
  map.setCenter(new GLatLng(mapCenterLatitude, mapCenterLongitude), mapZoom);
  map.setMapType(map.getMapTypes()[mapType]);
  map.disableScrollWheelZoom(); //we make our own scroll wheel zoomer but with zoom level restriction
  map.disableDoubleClickZoom();
  map.enableDragging();
  map.disableInfoWindow();
  
  //register click event on the map
  GEvent.addListener(map, 'click', function(obj) {
    if (!obj && selectedAirplane) deselectAirplane(true);
  });
  
  //register scroll wheel event to zoom the map
  if (window.addEventListener) window.addEventListener('DOMMouseScroll', wheel, false); //Mozilla
  window.onmousewheel = document.onmousewheel = wheel; //IE/Opera.
  
  //Hide zoom in/out button if necessary
  if (map.getZoom() == minMapZoomLevel) $('zoomOut').style.display = 'none';
  if (map.getZoom() == maxMapZoomLevel) $('zoomIn').style.display = 'none';
  
  //request airports after dragging the map the a new view rectangle or after zooming
  GEvent.addListener(map, 'moveend', function(obj) { requestAirportsInRange(); savePropertiesToCookie(); });
  GEvent.addListener(map, 'zoomend', function(obj) { requestAirportsInRange(); savePropertiesToCookie(); });
  
  // check map bounds when Skyguide Kisok-Modus is active
  if (kioskMode) 
  {
    boundSW = new GLatLng(45.7193861, 5.87115833);  //switzerland near SW
    boundNE = new GLatLng(48.7913194, 9.8623278);      //switzerland near NE
    allowedBounds = new GLatLngBounds(boundSW, boundNE);
    boundMargin = 0.2;
    GEvent.addListener(map, 'move', function() { checkSkyguideBounds(); });
  }
  
  //update colorBar
  updateColorBar(colorBar);
  
  //initialize LabelOverlay class
  initializeLabelOverlay();
  
  //initialize AirplaneShadowOverlay class
  initializeAirplaneShadowOverlay();
  initializeZeppelinShadowOverlay();
  
  //initialize AirportOverlay class
  initializeAirportOverlay();
}

// Boudn function for the Skyguide Kiosk-Modus
function checkSkyguideBounds()
{
  // Perform the check and return if OK
  var currentBounds = map.getBounds();
  var cSpan = currentBounds.toSpan();          // width and height of the bounds
  var offsetX = cSpan.lng() / (2+boundMargin); // we need a little border
  var offsetY = cSpan.lat() / (2+boundMargin);
  var C = map.getCenter();                     // current center coords
  var X = C.lng();
  var Y = C.lat();

  // now check if the current rectangle in the allowed area
  var checkSW = new GLatLng(C.lat()-offsetY,C.lng()-offsetX);
  var checkNE = new GLatLng(C.lat()+offsetY,C.lng()+offsetX);
				
  if (allowedBounds.containsLatLng(checkSW) && allowedBounds.containsLatLng(checkNE)) {
    return;
  }

  var AmaxX = allowedBounds.getNorthEast().lng();
  var AmaxY = allowedBounds.getNorthEast().lat();
  var AminX = allowedBounds.getSouthWest().lng();
  var AminY = allowedBounds.getSouthWest().lat();

  if (X < (AminX+offsetX)) {X = AminX + offsetX;}
  if (X > (AmaxX-offsetX)) {X = AmaxX - offsetX;}
  if (Y < (AminY+offsetY)) {Y = AminY + offsetY;}
  if (Y > (AmaxY-offsetY)) {Y = AmaxY - offsetY;}

  map.setCenter(new GLatLng(Y,X));
  return;
}

//CLASS LabelOverlay extends GOverlay
//(http://www.google.com/apis/maps/documentation/overlays.html#Custom_Overlays)
function LabelOverlay(point, label, color, pane)
{
  this.point = point;
  this.label = label;
  this.color = color ? color : 'black';
  this.pane = pane ? pane : G_MAP_MAP_PANE;
}
function initializeLabelOverlay()
{
  LabelOverlay.prototype = new GOverlay();
  LabelOverlay.prototype.initialize = function(map)
  {
    //create label div
    this.div = document.createElement('div');
    this.div.style.display = 'none';  //don't show the label before positioning in method redraw()
    this.div.style.position = 'absolute';
    this.div.style.backgroundImage = 'url(/images/white_transparent.png)';
    this.div.style.color = this.color;
    this.div.style.whiteSpace = 'nowrap'; //no line-breaks in label
    this.div.innerHTML = this.label;
    map.getPane(this.pane).appendChild(this.div); //add label to Google map
  }

  LabelOverlay.prototype.remove = function()
  {  
    this.div.parentNode.removeChild(this.div);
    //this.div.outerHTML = '';  // remove memory leak in IE (http://groups.google.de/group/Google-Maps-API/browse_thread/thread/10deaf2bc90ddc24/fb492295e94a8e42?lnk=raot) 
    //this.div = null;
  }

  LabelOverlay.prototype.copy = function()
  {
    return new LabelOverlay(this.point, this.label);
  }

  LabelOverlay.prototype.redraw = function(force)
  {
    var p = map.fromLatLngToDivPixel(this.point);
    this.div.style.left = p.x + 16 + 'px';  
    this.div.style.top = p.y - 15 + 'px';
    this.div.style.display = 'block';  //make the label visible after positioning
  }
}

//CLASS AirplaneShadowOverlay extends GOverlay
//(http://www.google.com/apis/maps/documentation/overlays.html#Custom_Overlays)
function AirplaneShadowOverlay(airplane)
{
  this.airplane = airplane;
}
function initializeAirplaneShadowOverlay()
{
  AirplaneShadowOverlay.prototype = new GOverlay();
  AirplaneShadowOverlay.prototype.initialize = function(map)
  {
    this.div = document.createElement('div');
    this.div.style.display = 'none';  //don't show the airplane shadow before positioning in method redraw()
    this.div.style.position = 'absolute';
    this.shadowImage = document.createElement('img');
    var attr = document.createAttribute('src');
    if (this.airplane.trackingPoint.heading) attr.nodeValue = 'images/airplanes/shadow_airplane' + ((Math.round(this.airplane.trackingPoint.heading/10)*10)%360) + '.png';
    this.shadowImage.setAttributeNode(attr);
    this.div.appendChild(this.shadowImage);
    map.getPane(G_MAP_MAP_PANE).appendChild(this.div); //add shadow overlay to Google map
  }

  AirplaneShadowOverlay.prototype.remove = function()
  {
    this.div.parentNode.removeChild(this.div);
    //this.div.outerHTML = '';  // remove memory leak in IE (http://groups.google.de/group/Google-Maps-API/browse_thread/thread/10deaf2bc90ddc24/fb492295e94a8e42?lnk=raot) 
    //this.div = null;
  }

  AirplaneShadowOverlay.prototype.copy = function()
  {
    return new AirplaneShadowOverlay(this.airplane);
  }

  AirplaneShadowOverlay.prototype.redraw = function(force)
  {
    var displacement = (airplaneShadowIconSizeAltitudeShrinkFactor * (this.airplane.trackingPoint.altitude/1000));
    var iWidth = airplaneIconSizeWidth - displacement;
    var iHeight = airplaneIconSizeHeight - displacement;
    if (iWidth <= 0) iWidth = 1;
    if (iHeight <= 0) iHeight = 1;
    var attr = document.createAttribute('src');
    attr.nodeValue = 'images/airplanes/shadow_airplane' + ((Math.round(this.airplane.trackingPoint.heading/10)*10)%360) + '.png';
    this.shadowImage.setAttributeNode(attr);
    attr = document.createAttribute('width');
    attr.nodeValue = iWidth;
    this.shadowImage.setAttributeNode(attr);
    attr = document.createAttribute('height');
    attr.nodeValue = iHeight;
    this.shadowImage.setAttributeNode(attr);
    this.shadowImage.style.position = 'absolute';
    this.shadowImage.style.left = airplaneShadowIconMinDisplacementX + Math.round(this.airplane.trackingPoint.altitude/(airplaneShadowIconDisplacementXFactor*1000)) + 'px';
    this.shadowImage.style.top = airplaneShadowIconMinDisplacementY + Math.round(this.airplane.trackingPoint.altitude/(airplaneShadowIconDisplacementYFactor*1000)) + 'px';
    var point = map.fromLatLngToDivPixel(new GLatLng(this.airplane.trackingPoint.latitude, this.airplane.trackingPoint.longitude));
    this.div.style.left = point.x - Math.round(iWidth/2) + 'px';
    this.div.style.top = point.y - Math.round(iHeight/2) + 'px';
    this.div.style.display = 'block';  //make the shadow visible after positioning
  }
}

//CLASS ZeppelinShadowOverlay extends GOverlay
//(http://www.google.com/apis/maps/documentation/overlays.html#Custom_Overlays)
function ZeppelinShadowOverlay(airplane)
{
  this.airplane = airplane;
}
function initializeZeppelinShadowOverlay()
{
  ZeppelinShadowOverlay.prototype = new GOverlay();
  ZeppelinShadowOverlay.prototype.initialize = function(map)
  {
    this.div = document.createElement('div');
    this.div.style.display = 'none';  //don't show the airplane shadow before positioning in method redraw()
    this.div.style.position = 'absolute';
    this.shadowImage = document.createElement('img');
    var attr = document.createAttribute('src');
    if (this.airplane.trackingPoint.heading) attr.nodeValue = 'images/airplanes/shadow_zeppelin' + ((Math.round(this.airplane.trackingPoint.heading/10)*10)%360) + '.png';
    this.shadowImage.setAttributeNode(attr);
    this.div.appendChild(this.shadowImage);
    map.getPane(G_MAP_MAP_PANE).appendChild(this.div); //add shadow overlay to Google map
  }

  ZeppelinShadowOverlay.prototype.remove = function()
  {
    this.div.parentNode.removeChild(this.div);
    //this.div.outerHTML = '';  // remove memory leak in IE (http://groups.google.de/group/Google-Maps-API/browse_thread/thread/10deaf2bc90ddc24/fb492295e94a8e42?lnk=raot) 
    //this.div = null;
  }

  ZeppelinShadowOverlay.prototype.copy = function()
  {
    return new ZeppelinShadowOverlay(this.airplane);
  }

  ZeppelinShadowOverlay.prototype.redraw = function(force)
  {
    var displacement = (airplaneShadowIconSizeAltitudeShrinkFactor * (this.airplane.trackingPoint.altitude/1000));
    var iWidth = airplaneIconSizeWidth - displacement;
    var iHeight = airplaneIconSizeHeight - displacement;
    if (iWidth <= 0) iWidth = 1;
    if (iHeight <= 0) iHeight = 1;
    var attr = document.createAttribute('src');
    attr.nodeValue = 'images/airplanes/shadow_zeppelin' + ((Math.round(this.airplane.trackingPoint.heading/10)*10)%360) + '.png';
    this.shadowImage.setAttributeNode(attr);
    attr = document.createAttribute('width');
    attr.nodeValue = iWidth;
    this.shadowImage.setAttributeNode(attr);
    attr = document.createAttribute('height');
    attr.nodeValue = iHeight;
    this.shadowImage.setAttributeNode(attr);
    this.shadowImage.style.position = 'absolute';
    this.shadowImage.style.left = airplaneShadowIconMinDisplacementX + Math.round(this.airplane.trackingPoint.altitude/(airplaneShadowIconDisplacementXFactor*1000)) + 'px';
    this.shadowImage.style.top = airplaneShadowIconMinDisplacementY + Math.round(this.airplane.trackingPoint.altitude/(airplaneShadowIconDisplacementYFactor*1000)) + 'px';
    var point = map.fromLatLngToDivPixel(new GLatLng(this.airplane.trackingPoint.latitude, this.airplane.trackingPoint.longitude));
    this.div.style.left = point.x - Math.round(iWidth/2) + 'px';
    this.div.style.top = point.y - Math.round(iHeight/2) + 'px';
    this.div.style.display = 'block';  //make the shadow visible after positioning
  }
}

//CLASS AirportOverlay extends GOverlay
//(http://www.google.com/apis/maps/documentation/overlays.html#Custom_Overlays)
function AirportOverlay(airport)
{
  this.airport = airport;
}
function initializeAirportOverlay()
{
  AirportOverlay.prototype = new GOverlay();
  AirportOverlay.prototype.initialize = function(map)
  {
    this.div = document.createElement('div');
    this.div.style.display = 'none';  //don't show the airport before positioning in method redraw()
    this.div.style.position = 'absolute';
    this.airportImage = document.createElement('img');
    var attr = document.createAttribute('src');
    attr.nodeValue = airportIconImage;
    this.airportImage.setAttributeNode(attr);
    attr = document.createAttribute('width');
    attr.nodeValue = airportIconSizeWidth;
    this.airportImage.setAttributeNode(attr);
    attr = document.createAttribute('height');
    attr.nodeValue = airportIconSizeHeight;
    this.airportImage.setAttributeNode(attr);
    this.div.appendChild(this.airportImage);
    map.getPane(G_MAP_MAP_PANE).appendChild(this.div); //add airport overlay to Google map
  }

  AirportOverlay.prototype.remove = function()
  {
    this.div.parentNode.removeChild(this.div);
    //this.div.outerHTML = '';  // remove memory leak in IE (http://groups.google.de/group/Google-Maps-API/browse_thread/thread/10deaf2bc90ddc24/fb492295e94a8e42?lnk=raot) 
    //this.div = null;
  }

  AirportOverlay.prototype.copy = function()
  {
    return new AirportOverlay(this.airport);
  }

  AirportOverlay.prototype.redraw = function(force)
  {
    var point = map.fromLatLngToDivPixel(new GLatLng(this.airport.latitude, this.airport.longitude));
    this.div.style.left = point.x - Math.round(airportIconSizeWidth/2) + 'px';
    this.div.style.top = point.y - Math.round(airportIconSizeHeight/2) + 'px';
    this.div.style.display = 'block';  //make the airport overlay visible after positioning
  }
}

//UPDATE (OR CREATE) AIRPLANE MARKER
function updateAirplaneMarker(airplane)
{
  //decide which image file to paint
  var angle = ((Math.round(airplane.trackingPoint.heading/10)*10+360)%360);
  var prefix = '';
  if (showExtrapolatedPositionsGray && airplane.trackingPoint.pointType == 'EXTRAPOLATED')
  {
    prefix = "g_";
  }
  else if (airplane == selectedAirplane)
  {
    if (mapType == 0) prefix = selectedAirplaneIconPrefixOnStreetMap;
    else if (mapType == 1) prefix = selectedAirplaneIconPrefixOnSatelliteMap;
    else if (mapType == 2) prefix = selectedAirplaneIconPrefixOnHybridMap;
  }
  else
  {
    if (mapType == 0) prefix = airplaneIconPrefixOnStreetMap;
    else if (mapType == 1) prefix = airplaneIconPrefixOnSatelliteMap;
    else if (mapType == 2) prefix = airplaneIconPrefixOnHybridMap;
  }

  if(zeppelinICAO[airplane.airplaneICAO] != null) 
  {
	  var airplaneImage = 'images/airplanes/' + prefix + 'zeppelin' + angle + '.png';
  }
  else
  {
    var airplaneImage = 'images/airplanes/' + prefix + 'airplane' + angle + '.png';
  }
  
  //for older browsers (like IE 6), remove airplane marker and create a new one
  if (isOlderBrowser())
  {
    if (airplane.marker) removeAirplaneMarker(airplane);
    airplane.marker = null;
  }
  
  if (!airplane.marker)
  {
    //create new airplane marker
    var airplaneIcon = new GIcon();
    airplaneIcon.image = airplaneImage;
    airplaneIcon.iconSize = new GSize(airplaneIconSizeWidth, airplaneIconSizeHeight);
    airplaneIcon.iconAnchor = new GPoint(Math.round(airplaneIconSizeWidth/2), Math.round(airplaneIconSizeHeight/2));
    var options = {draggable: false, icon: airplaneIcon};
    airplane.marker = new GMarker(new GLatLng(airplane.trackingPoint.latitude, airplane.trackingPoint.longitude), options);
    GEvent.addListener(airplane.marker, "click", function() {  selectAirplane(airplane); });
    GEvent.addListener(airplane.marker, "mousedown", function() {  selectAirplane(airplane); });
    map.addOverlay(airplane.marker);
  }
  else
  {
    //update airplane marker
    airplane.marker.setImage(airplaneImage);
    airplane.marker.setPoint(new GLatLng(airplane.trackingPoint.latitude, airplane.trackingPoint.longitude));
  }
  
  //create airplane label and shadow
  if (airplane.shadow) map.removeOverlay(airplane.shadow);
  if (airplane.label) map.removeOverlay(airplane.label);
  if (isShadowEnabled)
  {
    if(zeppelinICAO[airplane.airplaneICAO] != null)
	{
	  airplane.shadow = new ZeppelinShadowOverlay(airplane);
	}
	else
  	{
      airplane.shadow = new AirplaneShadowOverlay(airplane);
	}
    map.addOverlay(airplane.shadow);
  }
  if (airplane.flight)
  {
    airplane.label = new LabelOverlay(new GLatLng(airplane.trackingPoint.latitude, airplane.trackingPoint.longitude), '<strong>' + airplane.flight.flightIdentification + '</strong>', 'red', G_MAP_MARKER_PANE);
    map.addOverlay(airplane.label);
  }
  
  //track the airplane which is set by cookie
  if(airplane.airplaneICAO == trackICAO)
  {
    if (selectedAirplane != airplane)
    {
      if (selectedAirplane) deselectAirplane();
      selectedAirplane = airplane;
      showAirplaneInfo(airplane);
    }  	      	
  }
}


//UPDATE ALL AIRPLANE MARKERS
function updateAllAirplaneMarkers()
{
  for(var j = 0; j < airplanes.length; j++) updateAirplaneMarker(airplanes[j]);
}


//REMOVE AIRPLANE MARKER
function removeAirplaneMarker(airplane)
{
  if (airplane.label) map.removeOverlay(airplane.label);
  if (airplane.shadow) map.removeOverlay(airplane.shadow);
  map.removeOverlay(airplane.marker);
}


//CREATE AIRPORT MARKER
function createAirportMarker(airport)
{
  airport.label = new LabelOverlay(new GLatLng(airport.latitude, airport.longitude), airport.name, 'black', G_MAP_MAP_PANE);
  map.addOverlay(airport.label);
  airport.marker = new AirportOverlay(airport);
  map.addOverlay(airport.marker);
}


//REMOVE AIRPORT MARKER
function removeAirportMarker(airport)
{
  map.removeOverlay(airport.marker);
  map.removeOverlay(airport.label);
}


//REMOVE ALL AIRPORTS
//useTTL = true: decrement TimeToLive (TTL) of each airport and remove the airports only if TTL = 0
//useTTL = false: remove all airports in any case 
function removeAirports(useTTL)
{
  for(var j = 0; j < airports.length; j++) //for each airport in airports array...
  {
    //if useTTL = true: decrement TimeToLive (TTL) of each airport and remove the airport only if TTL = 0
    if (!useTTL || (useTTL && airports[j].TTL-- == 0)) 
    {    
      //remove airport marker
      removeAirportMarker(airports[j]);
          
      //remove airport from the airports array
      airports.splice(j--, 1);
    }
  }
}


//UPDATE POLYLINES OF AN AIRPLANE
//createNewPolyline = true: create a new polyline (don't replace old one)
function updatePolylines(airplane, createNewPolyline)
{
  if (!airplane.lastTrackingPoint)
  {
    airplane.lastFixedTrackingPoint = airplane.trackingPoint;
    airplane.lastFixedExactTrackingPoint = airplane.trackingPoint;
    airplane.polylines = new Array();
    airplane.trackLength = 0;
    return;
  }
  var polyline;
  if (createNewPolyline ||
      (Math.abs(airplane.lastFixedTrackingPoint.heading - airplane.trackingPoint.heading) > polylineAngleDifferential ||
       Math.abs(airplane.lastFixedTrackingPoint.altitude - airplane.trackingPoint.altitude) > polylineAltitudeDifferential ||
       new GLatLng(airplane.lastFixedTrackingPoint.latitude, airplane.lastFixedTrackingPoint.longitude).distanceFrom(new GLatLng(airplane.trackingPoint.latitude, airplane.trackingPoint.longitude)) > (polylineDistanceDifferential * 1000))
     )
  {
    //new polyline
    airplane.lastFixedTrackingPoint = airplane.lastTrackingPoint;
    if (airplane.lastTrackingPoint.pointType != "EXTRAPOLATED") airplane.lastFixedExactTrackingPoint = airplane.lastTrackingPoint;
    polyline = new GPolyline([new GLatLng(airplane.lastTrackingPoint.latitude, airplane.lastTrackingPoint.longitude), new GLatLng(airplane.trackingPoint.latitude, airplane.trackingPoint.longitude)], getAltitudeColor(airplane.trackingPoint.altitude), 2, 0.8);
    airplane.polylines.unshift(polyline);
  }
  else
  {
    //replace current polyline
    if (airplane.polylines.length > 0)
    {
      airplane.trackLength -= airplane.polylines[0].getLength();
      map.removeOverlay(airplane.polylines[0]);
    }
    polyline = new GPolyline([new GLatLng(airplane.lastFixedTrackingPoint.latitude, airplane.lastFixedTrackingPoint.longitude), new GLatLng(airplane.trackingPoint.latitude, airplane.trackingPoint.longitude)], getAltitudeColor(airplane.trackingPoint.altitude), 2, 0.8);
    airplane.polylines[0] = polyline;
  }
  map.addOverlay(polyline);
  airplane.trackLength += polyline.getLength();

  if (airplane.trackLength > maxTrackLength && airplane.trackLength-airplane.polylines[airplane.polylines.length-1].getLength() >= minTrackLength)
  {
    var index = airplane.polylines.length-1;
    fadePolyline(airplane.polylines[index], 0.7);
    airplane.trackLength = airplane.trackLength - airplane.polylines[index].getLength();
    airplane.polylines.splice(index, 1);
  }
}


//FADES A POLYLINE OUT
//needed by updatePolylines()
function fadePolyline(polyline, alpha)
{
  map.removeOverlay(polyline);
  if (alpha <= 0.1) return;
  var p = new GPolyline([polyline.getVertex(0),polyline.getVertex(1)], polyline.color, 2, alpha);
  polyline = null;
  map.addOverlay(p);
  setTimeout(function () { fadePolyline(p, alpha-0.1); }, polylineFadeTimout);
}


//GET COLOR FOR POLYLINE DEPENDING ON THE ALTITUDE
function getAltitudeColor(altitude)
{
  var red = 0, green = 0, blue = 0;
  if (altitude < 0) altitude = 0;
  if (altitude > 45000) altitude = 45000;
  if (colorBar == '3colors' || (colorBar == 'automatic' && getDefaultColorBarForMapType(mapType) == '3colors'))
  {
    var altitudeMeter = altitude / 3.2808;
    if(altitudeMeter < 6000)
    {
      red = 255 - Math.round(altitudeMeter/24);
      green = Math.round(altitudeMeter/24);
    }
    else if(altitudeMeter < 12000 && altitudeMeter >= 6000)
    {
      var green = 255 - Math.round((altitudeMeter-6000)/24);
      var blue = Math.round((altitudeMeter-6000)/24);
    }
    else if(altitudeMeter >= 12000) blue = 255;
  } 
  else
  {
    altitude = 45000 - altitude; //turn the colors up-side-down
    if (altitude <= 45000 && altitude >= 35000)
    {
      red = Math.round((45000-altitude)*0.0255);
    }
    else if (altitude < 35000 && altitude >= 25000)
    {
      red = 255;
      green = Math.round((35000-altitude)*0.0255);
    }
    else if (altitude < 25000 && altitude >= 15000)
    {
      red = Math.round((altitude-15000)*0.0255);
      green = 255;
      blue = Math.round((25000-altitude)*0.01275);
    }
    else if (altitude < 15000 && altitude >= 5000)
    {
      green = Math.round((altitude-5000)*0.0255);
      blue = Math.round((25000-altitude)*0.01275);
    }
    else
    {
      blue = Math.round((altitude+5000)*0.0255);
    }
  }
  var redHex = red.toString(16);
  if(redHex.length == 1) redHex = '0' + redHex;
  var greenHex = green.toString(16);
  if(greenHex.length == 1) greenHex = '0' + greenHex;
  var blueHex = blue.toString(16);
  if(blueHex.length == 1) blueHex = '0' + blueHex;
  return '#' + redHex + greenHex + blueHex;
}


//REMOVE ALL POLYLINES OF AN AIRPLANE
function removePolylines(airplane)
{
  if (airplane.polylines)
  {
    for(var p = 0; p < airplane.polylines.length; p++) map.removeOverlay(airplane.polylines[p]);
    airplane.polylines.clear();
    airplane.trackLength = 0;
  }
}


//REPLACE POLYLINES OF ALL AIRPLANES
//needed by updateColorBar()
function replacePolylinesOfAllAirplanes()
{
  ajaxRequest.transport.abort(); //stop running AJAX request
  for(var j = 0; j < airplanes.length; j++) //for each airplane in airplanes array...
  {
    //remove all polylines
    removePolylines(airplanes[j]);

    airplanes[j].trackingPoint = false;
    airplanes[j].lastTrackingPoint = false;
    airplanes[j].lastFixedTrackingPoint = false;
    airplanes[j].lastExactFixedTrackingPoint = false;
     
    //request past tracking points of this airplane
    requestAirplaneTrackingPoints(airplanes[j], 0, timestamp);
  }
}


//REMOVE ALL AIRPLANES
//useTTL = true: decrement TimeToLive (TTL) of each airplane and remove the airplane only if TTL = 0
//useTTL = false: remove all airplanes in any case 
function removeAllAirplanes(useTTL)
{
  for(var j = 0; j < airplanes.length; j++) //for each airplane in airplanes array...
  {
    //useTTL = true: decrement TimeToLive (TTL) of each airplane and remove the airplane only if TTL = 0
    if (!useTTL || (useTTL && airplanes[j].TTL-- == 0)) 
    {
      //if airplane that should be removed is selected, deselect it first
      if (selectedAirplane == airplanes[j]) deselectAirplane(true);
          
      //remove all polylines of this airplane
      removePolylines(airplanes[j]);
          
      //remove airplane marker
      removeAirplaneMarker(airplanes[j]);
          
      //remove airplane from the airplanes array
      airplanes.splice(j--, 1);
    }
  }
}


//SELECT AIRPLANE
//Track the airplane in case that the option 'tracking' is activated
function selectAirplane(airplane)
{
  if (selectedAirplane != airplane)
  {
    if (selectedAirplane) deselectAirplane();
    selectedAirplane = airplane;
    updateAirplaneMarker(airplane);
    showAirplaneInfo(airplane);
  }
  else updateAirplaneInfo(airplane);
  if (isTrackingEnabled) panToAirplane(airplane);
}


//DESELECT AIRPLANE
function deselectAirplane(hideInfoBar)
{
  var airplane = selectedAirplane;
  selectedAirplane = false;
  updateAirplaneMarker(airplane);
  if (hideInfoBar) new Effect.DropOut('infoBar');
}


//PAN TO AIRPLANE
function panToAirplane(airplane)
{
  map.panTo(new GLatLng(airplane.trackingPoint.latitude, airplane.trackingPoint.longitude))
}


//ZOOM IN MAP
function zoomInMap()
{
  if (maxMapZoomLevel > map.getZoom())
  {
    map.zoomIn();
    $('zoomOut').style.display = 'block';
    if (map.getZoom() == maxMapZoomLevel) $('zoomIn').style.display = 'none';
  }
}


//ZOOM OUT MAP
function zoomOutMap()
{
  if (minMapZoomLevel < map.getZoom())
  {
    map.zoomOut();
    $('zoomIn').style.display = 'block';
    if (map.getZoom() == minMapZoomLevel) $('zoomOut').style.display = 'none';
  }
}




//*****************************************************************************
// AJAX REQUESTS / DATA UPDATING
//*****************************************************************************

//CALLBACK FUNCTION FOR FAILED AJAX CONNECTION
function ajaxConnectionFailed()
{
  //ajaxTimedOut = true;
  $('loader').style.display = 'none';
  //periodicalUpdater.stop();              //stop periodical updater
  ajaxRequest.transport.abort();         //stop running AJAX request
  //showMessage('Connection to server ' + (airplanes.length == 0 ? 'failed' : 'lost') + '! Reload the page to ' + (airplanes.length == 0 ? 'retry obtaining a connection.' : 'obtain the connection again.') + '<p /><input type="button" value="Reload" onclick="document.location.reload()" />');
}


//REQUEST AIRPLANES IN RANGE FROM THE SERVER
//This function is called by the PeriodicalUpdater
function requestAirplanesInRange()
{
  //only proceed if there's no AJAX request running
  if (Ajax.activeRequestCount > 0) return;
    
  $('liveIcon').style.display = 'none';
  $('statusBar').innerHTML = 'Updating air traffic information...';

  //only get airplanes in the view area of the map
  //and a little bit more (-> expLat, expLng)
  var bounds = map.getBounds();
  var minLat = bounds.getSouthWest().lat();
  var minLng = bounds.getSouthWest().lng();
  var maxLat = bounds.getNorthEast().lat();
  var maxLng = bounds.getNorthEast().lng();
  var expLat = (maxLat-minLat)/10;
  var expLng = (maxLng-minLng)/10;
  var rndMinLat = Math.floor(minLat-expLat);
  var rndMinLng = Math.floor(minLng-expLng);
  var rndMaxLat = Math.ceil(maxLat+expLat);
  var rndMaxLng = Math.ceil(maxLng+expLng);

  //request airplanes in range
  //an array of tracking points is received sorted first by airplane, secondary by the timestamp in ascending order
  ajaxRequest =
  new Ajax.Request(airtrafficService + '/getAirplanesInRange/' + rndMinLat + '/' + rndMinLng + '/' + rndMaxLat + '/' + rndMaxLng + '/' + (timestamp == 0 ? ('0' + '/' + ((new Date()).getTime())) : timestamp), {
    method: 'get',
    onSuccess: function(transport)
    {
      //evaluate JSON object
      //result[i][0] = airplaneICAO
      //result[i][1] = trackingPoint
      var result = transport.responseText.evalJSON();
      
      //keep the newest timestamp of the request
      //next time use this timestamp to request data newer than this timestamp
      if (result.length > 0) timestamp = result[result.length-1][1].timestamp;
      else if (timestamp != 0) timestamp = timestamp + 1; //Bugfix: Caching verhindern, wenn Antwort eine leere Liste ist.
      
      var interpolated = false; //flag for interpolation request
      for(var i = 0; i < result.length; i++) //for each tracking point received by the request...
      {
        if (hideOnGroundAirplanes && result[i][1].onGround == true) //do not treat any onground airplanes
        {
            continue;
        }

        if (result[i][1].latitude >= (minLat-expLat) &&
            result[i][1].longitude >= (minLng-expLng) &&
            result[i][1].latitude <= (maxLat+expLat) &&
            result[i][1].longitude <= (maxLng+expLat)
           )
        {

        //if there were interpolated tracking points requested (flag interpolated=true)
        //then don't proceed with this airplane
        if (i > 0 && result[i-1][0] == result[i][0] && interpolated) continue;
        else interpolated = false;
        
        //check if there are more (newer) tracking points of this airplane
        //keep this information in the boolean variable moreTPsOfThisAirplane
        var moreTPsOfThisAirplane = i+1 < result.length && result[i+1][0] == result[i][0] ? true : false;
         
        //check if airplane already exists in airplanes array
        var j = 0;
        while (j < airplanes.length)
        {
          if (result[i][0] == airplanes[j].airplaneICAO) break; //airplane found in array -> exit while loop
          j++;
        }
        
        //new airplane
        if (j == airplanes.length) 
        {
          if (moreTPsOfThisAirplane) continue; //only create ONE new airplane
          
          //create new airplane and request further information about airplane
          requestAirplane(result[i][0]);
          continue;
        }
        
        //update existing airplane
        else
        {
          //replace tracking point information
          airplanes[j].lastTrackingPoint = airplanes[j].trackingPoint; //remember last tracking point
          airplanes[j].trackingPoint = result[i][1];                   //new tracking point of the request
          if(zeppelinICAO[airplanes[j].airplaneICAO] != null) 
          {
            airplanes[j].TTL = zeppelinTTL;                            //set TimeToLive (TTL) to its original value 
          }
          else
          {
            airplanes[j].TTL = airplaneTTL;                            //set TimeToLive (TTL) to its original value 
          }

          //check if interpolated tracking points have to be requested
          if (airplanes[j].trackingPoint.pointType != "EXTRAPOLATED" &&
              airplanes[j].lastTrackingPoint.pointType == "EXTRAPOLATED")
          {
            //remove dispensable polylines
            while(airplanes[j].polylines && airplanes[j].polylines.length > 0)
            {
              if (airplanes[j].polylines[0].getVertex(1).lat() == airplanes[j].lastFixedExactTrackingPoint.latitude &&
                  airplanes[j].polylines[0].getVertex(1).lng() == airplanes[j].lastFixedExactTrackingPoint.longitude) break;
              airplanes[j].trackLength -= airplanes[j].polylines[0].getLength();
              map.removeOverlay(airplanes[j].polylines[0]);
              airplanes[j].polylines.splice(0, 1);
            }
            airplanes[j].trackingPoint = airplanes[j].lastFixedExactTrackingPoint;
            requestAirplaneTrackingPoints(airplanes[j], airplanes[j].trackingPoint.timestamp+1, timestamp, true); //request interpolated tracking points
            interpolated = true; //mark that further tracking points of this airplane shouldn't be processed 
            continue;
          }

          //paint airplane on Google map
          repaintAirplane(airplanes[j]);

          //select/track airplane
          if (selectedAirplane == airplanes[j] && !moreTPsOfThisAirplane) selectAirplane(selectedAirplane);
        }
        } //end of area check

	    // Capture server time to display the effective time on the map
	    serverTime = result[i][1].timestamp;
      }

      //show time
      var d = new Date();
      d.setTime(serverTime);
      if (d.getFullYear() > 1970)
      {
        $('timeDisplay').innerHTML = (d.getDate() < 10 ? '0' : '') + d.getDate() + '.' + (d.getMonth()+1 < 10 ? '0' : '') + (d.getMonth()+1) + '.' + d.getFullYear() + ' ' + (d.getHours() < 10 ? '0' : '') + d.getHours() + ':' + (d.getMinutes() < 10 ? '0' : '') + d.getMinutes() + ':' + (d.getSeconds() < 10 ? '0' : '') + d.getSeconds();
        if (showTimeDisplay) $('timeDisplay').style.display = 'block';
      }
      
      if (trackICAO != false)  requestAirplaneByICAO();

      //remove invisible or non-more-updated airplanes and their tracking points
      removeAllAirplanes(true);
    }
  });
}


//REQUEST AIRPLANES BY ICAO FROM THE SERVER
function requestAirplaneByICAO()
{
  //request airplane by ICAO
  //an array of tracking points is received sorted first by airplane, secondary by the timestamp in ascending order
  ajaxRequest =
  new Ajax.Request(airtrafficService + '/getAirplaneByICAO/' + trackICAO + '/' + (timestamp == 0 ? ('0' + '/' + ((new Date()).getTime())) : timestamp), {
    method: 'get',
    onSuccess: function(transport)
    {
      //evaluate JSON object
      //result[i][0] = airplaneICAO
      //result[i][1] = trackingPoint
      var result = transport.responseText.evalJSON();
            
      var interpolated = false; //flag for interpolation request
      for(var i = 0; i < result.length; i++) //for each tracking point received by the request...
      {
        if (hideOnGroundAirplanes && result[i][1].onGround == true) //do not treat any onground airplanes
        {
            continue;
        }

        //if there were interpolated tracking points requested (flag interpolated=true)
        //then don't proceed with this airplane
        if (i > 0 && result[i-1][0] == result[i][0] && interpolated) continue;
        else interpolated = false;
        
        //check if there are more (newer) tracking points of this airplane
        //keep this information in the boolean variable moreTPsOfThisAirplane
        var moreTPsOfThisAirplane = i+1 < result.length && result[i+1][0] == result[i][0] ? true : false;
         
        //check if airplane already exists in airplanes array
        var j = 0;
        while (j < airplanes.length)
        {
          if (result[i][0] == airplanes[j].airplaneICAO) break; //airplane found in array -> exit while loop
          j++;
        }
        
        //new airplane
        if (j == airplanes.length) 
        {
          if (moreTPsOfThisAirplane) continue; //only create ONE new airplane
          
          //create new airplane and request further information about airplane
          requestAirplane(result[i][0]);
          continue;
        }
        
        //update existing airplane
        else
        {
          //replace tracking point information
          airplanes[j].lastTrackingPoint = airplanes[j].trackingPoint; //remember last tracking point
          airplanes[j].trackingPoint = result[i][1];                   //new tracking point of the request
          if(zeppelinICAO[airplanes[j].airplaneICAO] != null) 
          {
            airplanes[j].TTL = zeppelinTTL;                            //set TimeToLive (TTL) to its original value 
          }
          else
          {
            airplanes[j].TTL = airplaneTTL;                            //set TimeToLive (TTL) to its original value 
          }
                    
          //check if interpolated tracking points have to be requested
          if (airplanes[j].trackingPoint.pointType != "EXTRAPOLATED" &&
              airplanes[j].lastTrackingPoint.pointType == "EXTRAPOLATED")
          {
            //remove dispensable polylines
            while(airplanes[j].polylines && airplanes[j].polylines.length > 0)
            {
              if (airplanes[j].polylines[0].getVertex(1).lat() == airplanes[j].lastFixedExactTrackingPoint.latitude &&
                  airplanes[j].polylines[0].getVertex(1).lng() == airplanes[j].lastFixedExactTrackingPoint.longitude) break;
              airplanes[j].trackLength -= airplanes[j].polylines[0].getLength();
              map.removeOverlay(airplanes[j].polylines[0]);
              airplanes[j].polylines.splice(0, 1);
            }
            airplanes[j].trackingPoint = airplanes[j].lastFixedExactTrackingPoint;
            requestAirplaneTrackingPoints(airplanes[j], airplanes[j].trackingPoint.timestamp+1, timestamp, true); //request interpolated tracking points
            interpolated = true; //mark that further tracking points of this airplane shouldn't be processed 
            continue;
          }

          //paint airplane on Google map
          repaintAirplane(airplanes[j]);

          //select/track airplane
          if (selectedAirplane == airplanes[j] && !moreTPsOfThisAirplane) selectAirplane(selectedAirplane);
        }
	  }
    }
  });
}

function repaintAirplane(airplane)
{
  //update airplane marker
  updateAirplaneMarker(airplane);
  
  //update polylines
  updatePolylines(airplane);
}


//REQUEST TRACKING POINTS OF AN AIRPLANE FROM THE SERVER
//used on loading and on reloading interpolated tracking points
function requestAirplaneTrackingPoints(airplane, timestampFrom, timestampTo, interpolated)
{
  //request tracking points of a specific airplane
  //an array of tracking points is received sorted by the timestamp in ascending order
  new Ajax.Request(airtrafficService + '/getAirplaneTrackingPoints/' + airplane.airplaneICAO + '/' + timestampFrom + '/' + timestampTo, {
    method: 'get',
    onSuccess: function(transport)
    {
      //evaluate JSON object
      //result[i] = trackingPoint
      var result = transport.responseText.evalJSON();
      
      for(var i = 0; i < result.length; i++) //for each tracking point received...
      {
        //remember last tracking point
        if (airplane.trackingPoint) airplane.lastTrackingPoint = airplane.trackingPoint;
        
        //new tracking point of the request
        airplane.trackingPoint = result[i];
        
        //update airplane marker
        updateAirplaneMarker(airplane);
        
        //update polylines
        updatePolylines(airplane, i == 0 && interpolated ? true : false);
        
        //select/track airplane
        if (selectedAirplane == airplane && i == result.length - 1) selectAirplane(selectedAirplane);
      }
    }
  });
}


//REQUEST AIRPLANE OBJECT BY AIRPLANE ICAO FROM THE SERVER
//This object includes all information about the airplane, the flight, the airline, ...
function requestAirplane(airplaneICAO)
{
  //request airplane and all its information (aircraft, flight, ...)
  new Ajax.Request(airtrafficService + '/getAirplane/' + airplaneICAO, {
    method: 'get',
    onSuccess: function(transport)
    {
      //evaluate JSON object
      var airplane = transport.responseText.evalJSON();
      
      //set TimeToLive (TTL) to its original value
      if(zeppelinICAO[airplaneICAO] != null) 
      {
        airplane.TTL = zeppelinTTL;
      }
      else
      {
        airplane.TTL = airplaneTTL;
      }
            
      //add new airplane to the airplanes array
      airplanes[airplanes.length] = airplane;
      
      //request past tracking points of this airplane
      requestAirplaneTrackingPoints(airplane, 0, timestamp);
    }
  });
}


//REQUEST AIRPORTS IN RANGE FROM THE SERVER
function requestAirportsInRange()
{
  //only get airports in the view area of the map
  var bounds = map.getBounds();
  var minLat = bounds.getSouthWest().lat();
  var minLng = bounds.getSouthWest().lng();
  var maxLat = bounds.getNorthEast().lat();
  var maxLng = bounds.getNorthEast().lng();

  var rndMinLat = Math.floor(minLat);
  var rndMinLng = Math.floor(minLng);
  var rndMaxLat = Math.ceil(maxLat);
  var rndMaxLng = Math.ceil(maxLng);
  
  //request airports in range
  new Ajax.Request(airtrafficService + '/getAirportsInRange/' + rndMinLat + '/' + rndMinLng + '/' + rndMaxLat + '/' + rndMaxLng, {
    method: 'get',
    onSuccess: function(transport)
    {
      //evaluate JSON object
      //result[i] = airport
      var result = transport.responseText.evalJSON();

      for(var i = 0; i < result.length; i++) //for each airport received...
      {
        if (result[i].latitude >= minLat &&
            result[i].longitude >= minLng &&
            result[i].latitude <= maxLat &&
            result[i].longitude <= maxLng
           )
        {

        //check if airport already exists in airports array
        var j = 0;
        while (j < airports.length)
        {
          if (result[i].airportIATA == airports[j].airportIATA) break; //airport found in array -> exit while loop
          j++;
        }
        
        //new airport
        if (j == airports.length)
        {
          airports[j] = result[i];
          airports[j].TTL = 3; //set TimeToLive (TTL) to its original value
          createAirportMarker(airports[j]);
        }
        
        //update existing airport
        else airports[j].TTL = 3; //set TimeToLive (TTL) to its original value
        }

      }
      
      //remove invisible or non-more-updated airports
      removeAirports(true);
    }
  });
}




//*****************************************************************************
// WINDOW CONTROL FUNCTION adjustWindow()
// (This function is hardly browser dependent!)
//*****************************************************************************
function adjustWindow()
{
  var xTabs = $('xTabs').getElementsByClassName('XTab');
  if (window.innerHeight) //Firefox, Netscape, ...
  {
    $('map').style.height = (window.innerHeight-23) + 'px';
    $('map').style.width = (window.innerWidth-2) + 'px';
    $('statusBar').style.width = (window.innerWidth-8) + 'px';
    $('infoBar').style.left = (window.innerWidth-6-$('infoBar').style.width.substring(0, $('infoBar').style.width.length-2)) + 'px';
    for(var i=0; i<xTabs.length; i++)
    {
      var h1 = xTabs[i].getElementsBySelector('h1');
      h1[0].style.top = navigator.userAgent.indexOf('Chrome/') == -1 ? '-25px' : '-24px';
      h1[0].style.marginLeft = (154 * i + 80) + 'px';
      xTabs[i].style.bottom = xListShown[xTabs[i].id] ? Element.getHeight('statusBar')-15 : (Element.getHeight('statusBar') - Element.getHeight(xTabs[i].id)) + 'px';
    }
  }
  else //Internet Explorer
  {
    $('map').style.height = (document.documentElement.clientHeight-23) + 'px'; 
    $('map').style.width = (document.documentElement.clientWidth-2) + 'px';
    $('statusBar').style.width = (document.documentElement.clientWidth-8) + 'px';
    $('infoBar').style.left = (document.documentElement.clientWidth-6-$('infoBar').style.width.substring(0, $('infoBar').style.width.length-2)) + 'px';
    var xTabs = $('xTabs').getElementsByClassName('XTab');
    for(var i=0; i<xTabs.length; i++)
    {
      var h1 = xTabs[i].getElementsBySelector('h1');
      h1[0].style.top = '-18px';
      h1[0].style.marginLeft = (154 * i + 80) + 'px';
      xTabs[i].style.bottom = xListShown[xTabs[i].id] ? Element.getHeight('statusBar')-15 : (Element.getHeight('statusBar') - Element.getHeight(xTabs[i].id)) + 'px';
    }
  }
}




//*****************************************************************************
// OPTIONS SETTER AND UPDATE METHODS
//*****************************************************************************

//UPDATE UNITS
function updateUnits(_units)
{
  if (_units == units) return;
  if (_units) units = _units;
  if (selectedAirplane) updateAirplaneInfo(selectedAirplane);
  updateColorBar(colorBar, true);
}


//UPDATE MAP TYPE
function updateMapType(_mapType)
{
  if (_mapType == mapType) return;
  if (colorBar == 'automatic')
  {
    if (getDefaultColorBarForMapType(_mapType) != getDefaultColorBarForMapType(mapType))
    {
      mapType = _mapType; //must assign before calling updateColorBar()
      updateColorBar(colorBar);
    }
  }
  mapType = _mapType;
  map.setMapType(map.getMapTypes()[mapType]);
  updateAllAirplaneMarkers();
  savePropertiesToCookie();
}


//UPDATE COLOR BAR (AUTOMATIC, 3 COLORS OR 5 COLORS)
function updateColorBar(_colorBar, keepInAnyCase)
{
  if (_colorBar == 'automatic')
  {
    if (appStarted && !keepInAnyCase && getDefaultColorBarForMapType(mapType) != colorBar) replacePolylinesOfAllAirplanes();
    $('colorBar').src = 'images/colorbar_' + getDefaultColorBarForMapType(mapType) + '_' + (units == 'aviatic' ? 'ft' : 'm') + '.png';
  }
  else
  {
    if (appStarted && !keepInAnyCase && (colorBar != 'automatic' && _colorBar != colorBar) || (colorBar == 'automatic' && getDefaultColorBarForMapType(mapType) != _colorBar)) replacePolylinesOfAllAirplanes();
    $('colorBar').src = 'images/colorbar_' + _colorBar + '_' + (units == 'aviatic' ? 'ft' : 'm') + '.png';
  }
  colorBar = _colorBar;
  savePropertiesToCookie();
}


//UPDATE LANGUAGE
function updateLanguage(_language)
{
  if (_language == language) return;
  language = _language;
  $('zoomIn').alt = texts[language].zoomIn;
  $('zoomIn').title = texts[language].zoomIn;
  $('zoomOut').alt = texts[language].zoomOut;
  $('zoomOut').title = texts[language].zoomOut;
  updateXTabs();
  showXTab('options', true);
  //xListShown['options'] = false;
  adjustWindow();
  if (selectedAirplane) showAirplaneInfo(selectedAirplane);
  savePropertiesToCookie();
}


//ENABLE TRACKING
function enableTracking()
{
  if (isTrackingEnabled) return;
  isTrackingEnabled = true;
  if (selectedAirplane) panToAirplane(selectedAirplane);
  savePropertiesToCookie();
}


//DISABLE TRACKING
function disableTracking()
{
  isTrackingEnabled = false;
  savePropertiesToCookie();
}


//ENABLE AIRPLANE SHADOW
function enableShadow()
{
  if (isShadowEnabled) return;
  isShadowEnabled = true;
  updateAllAirplaneMarkers();
  savePropertiesToCookie();
}


//DISABLE AIRPLANE SHADOW
function disableShadow()
{
  isShadowEnabled = false;
  updateAllAirplaneMarkers();
  savePropertiesToCookie();
}


//GET DEFAULT COLOR BAR FOR MAP TYPE
//(as defined in config file, used by updateMapType() and getAltitudeColor())
function getDefaultColorBarForMapType(mapType)
{
  if (mapType == 0) return defaultColorBarOnStreetMap;
  if (mapType == 1) return defaultColorBarOnSatelliteMap;
  if (mapType == 2) return defaultColorBarOnHybridMap;
}




//*****************************************************************************
// HELPER FUNCTIONS
//*****************************************************************************

//DETECT OLD BROWSER (LIKE IE6)
//(http://blog.t8d.de/2007/01/09/ie7-und-javascript-sicher-erkennen)
function isOlderBrowser()
{
  if (window.XMLHttpRequest) return false;
  else return true;
}


//RETURNS AIRPLANE POSITION IN SI UNITS
function getPositionInSIUnit(airplane)
{
  var position = (airplane.trackingPoint.latitude < 0 ? getDegrees(airplane.trackingPoint.latitude * (-1)) + ' S' : getDegrees(airplane.trackingPoint.latitude) + ' N');
  position += '<br />' + (airplane.trackingPoint.longitude < 0 ? getDegrees(airplane.trackingPoint.longitude * (-1)) + ' W' : getDegrees(airplane.trackingPoint.longitude) + ' E');
  return position;
}
function getDegrees(val) //used by getPositionInSIUnit()
{
  var deg = Math.floor(val);
  var temp = (val-deg)*60;
  var min = Math.floor(temp);
  var sec = Math.floor((temp-min)*60);
  var retVal = (deg < 10 ? '0' : '') + deg + '&deg;' + (min < 10 ? '0' : '') + min + '\'' + (sec < 10 ? '0' : '') + sec + '"';
  return retVal;
}


//FORMAT DATE
//
// Field        | Full Form          | Short Form
// -------------+--------------------+-----------------------
// Year         | yyyy (4 digits)    | yy (2 digits), y (2 or 4 digits)
// Month        |                    | MM (2 digits), M (1 or 2 digits)
// Day of Month | dd (2 digits)      | d (1 or 2 digits)
// Hour (1-12)  | hh (2 digits)      | h (1 or 2 digits)
// Hour (0-23)  | HH (2 digits)      | H (1 or 2 digits)
// Hour (0-11)  | KK (2 digits)      | K (1 or 2 digits)
// Hour (1-24)  | kk (2 digits)      | k (1 or 2 digits)
// Minute       | mm (2 digits)      | m (1 or 2 digits)
// Second       | ss (2 digits)      | s (1 or 2 digits)
// AM/PM        | a                  |
//
//(Thanks to http://www.mattkruse.com/javascript/date/)
function formatDate(date, format)
{
  if (!date) return texts[language].notAvailable;
  if (!(date instanceof Date)) date = new Date(date);
  if (!format) format = 'dd.MM.y';
  var y = date.getYear() + '';
  var M = date.getMonth() + 1;
  var d = date.getDate();
  var H = date.getHours();
  var m = date.getMinutes();
  var s = date.getSeconds();
  var value = new Object();
  if (y.length < 4) y = '' + (y - 0 + 1900);
  value['y'] = y;
  value['yyyy'] = y;
  value['yy'] = y.substring(2,4);
  value['M'] = M;
  value['MM'] = LZ(M);
  value['d'] = d;
  value['dd'] = LZ(d);
  value['H'] = H;
  value['HH'] = LZ(H);
  if (H == 0) value['h'] = 12;
  else if (H > 12) value['h'] = H - 12;
  else value['h'] = H;
  value['hh'] = LZ(value['h']);
  if (H > 11) value['K'] = H - 12;
  else value['K'] = H;
  value['k'] = H + 1;
  value['KK'] = LZ(value['K']);
  value['kk'] = LZ(value['k']);
  if (H > 11) value['a'] = 'PM';
  else value['a'] = 'AM';
  value['m'] = m;
  value['mm'] = LZ(m);
  value['s'] = s;
  value['ss'] = LZ(s);
  var i_format = 0;
  var token = '';
  var result = '';
  var c = '';
  while (i_format < format.length)
  {
    c = format.charAt(i_format);
    token = '';
    while ((format.charAt(i_format) == c) && (i_format < format.length))
    {
      token += format.charAt(i_format++);
    }
    if (value[token] != null) result += value[token];
    else result += token;
  }
  return result;
}
function LZ(x) //used by formatDate() - fills all values between 1 and 9 with a perceding zero
{
  return (x < 0 || x > 9 ? '' : '0') + x;
}


//SHOW MESSAGE ON THE FULLSCREEN
function showMessage(msg)
{
  $('message').innerHTML = msg;
  $('transparentBackground').style.display = 'block';
}


//HIDE THE MESSAGE ON THE FULLSCREEN
function hideMessage()
{
  $('transparentBackground').style.display = 'none';
}




//*****************************************************************************
// INFO BAR
//*****************************************************************************
var selectedTab = 'Flight';
function showAirplaneInfo(airplane)
{
  $('infoBar').innerHTML =
  '<div id="tabsheader">' +
  ' <ul id="tabs">' +
  '  <li id="tabFlight"></li>' +
  '  <li id="tabAirplane"></li>' +
  ' </ul>' +
  '</div>' +
  '<div id="tabContent"></div>';
  showTab(selectedTab);
}


function showTab(tab)
{
  var airplane = selectedAirplane;
  if (tab == 'Airplane')
  {
    selectedTab = 'Airplane';
    $('tabFlight').innerHTML = '<a href="javascript:showTab(\'Flight\')" onmouseover="window.status = \'\'; return true">' + texts[language].flight + '</a></li>';
    $('tabAirplane').innerHTML = '<span>' + texts[language].airplane + '</span>';
    $('tabContent').innerHTML =
      '<table cellpadding="0" cellspacing="5" width="100%">' +
      '<tr><td colspan="2" style="padding-top:15px; border-bottom:1px solid black"><strong>' + texts[language].aircraftInformation + '</strong></td></tr>' +
      '<tr><td colspan="2" style="line-height:2px">&nbsp;</td></tr>' +  
      '<tr><td valign="top" width="80">ICAO:</td><td>' + airplane.airplaneICAO + ' (0x' + airplane.airplaneICAO.toString(16).toUpperCase() + ')' + '</td></tr>' +
      ((airplane.registration || airplane.selcal || airplane.manufacturer || airplane.model || airplane.type || airplane.buildYear || airplane.deliveryDate) ?
        (airplane.registration ? '<tr><td valign="top">' + texts[language].registration + ':</td><td>' + airplane.registration + '</td></tr>' : '') +
        (airplane.selcal ? '<tr><td valign="top">Selcal:</td><td>' + airplane.selcal + '</td></tr>' : '') +
        (airplane.manufacturer ? '<tr><td valign="top">' + texts[language].manufacturer + ':</td><td>' + airplane.manufacturer + '</td></tr>' : '') +
        (airplane.model ? '<tr><td valign="top">' + texts[language].model + ':</td><td>' + airplane.model + '</td></tr>' : '') +
        (airplane.type ? '<tr><td valign="top">' + texts[language].type + ':</td><td>' + airplane.type + '</td></tr>' : '') +
        (airplane.buildYear ? '<tr><td valign="top">' + texts[language].buildYear + ':</td><td>' + airplane.buildYear + '</td></tr>' : '') +
        (airplane.deliveryDate ? '<tr><td valign="top">' + texts[language].delivery + ':</td><td>' + formatDate(airplane.deliveryDate, 'MM.yyyy') + '</td></tr>' : '')
      :
        '') +
      (airplane.imageURL ? '<tr><td colspan="2" align="center" style="padding-top:5px"><img src="' + airplane.imageURL + '" alt="" title="" style="border:1px solid black" /></td></tr>' : '') +
      '</table>';
  }
  else if (tab == 'Flight')
  {
    if (!kioskMode) { 
      selectedTab = 'Flight';
      $('tabAirplane').innerHTML = '<a href="javascript:showTab(\'Airplane\')" onmouseover="window.status = \'\'; return true">' + texts[language].airplane + '</a></li>';
      $('tabFlight').innerHTML = '<span>' + texts[language].flight + '</span>';
      $('tabContent').innerHTML =
        '<table cellpadding="0" cellspacing="5" width="100%">' +
        '<tr><td colspan="2" style="padding-top:15px; border-bottom:1px solid black"><strong>' + texts[language].flightInformation + '</strong></td></tr>' +
        '<tr><td colspan="2" style="line-height:2px">&nbsp;</td></tr>' +
        '<tr><td width="80" valign="top">' + texts[language].flight + ':</td><td><strong>' + airplane.flight.flightIdentification + '</strong></td></tr>' +
        (airplane.flight.airline ? '<tr><td valign="top">' + texts[language].airline + ':</td><td>' + (airplane.flight.airline.website ? '<a href="' + airplane.flight.airline.website + '" target="_new">' + airplane.flight.airline.name + '</a>' : airplane.flight.airline.name) + '&nbsp;&nbsp;' + (airplane.flight.airline.country.flagURL ? '<img src="' + airplane.flight.airline.country.flagURL + '" alt="' + airplane.flight.airline.country.name + '" title="' + airplane.flight.airline.country.name + '" />' : '') + '</td></tr>' : '') +
        //'<tr><td valign="top">Squawk:</td><td id="squawk"></td></tr>' +
		(airplane.flight.departureAirport ?
          '<tr><td colspan="2" style="padding-top:15px; border-bottom:1px solid black"><strong>' + texts[language].departure + '</strong></td></tr>' +
          '<tr><td colspan="2" style="line-height:2px">&nbsp;</td></tr>' +
          '<tr><td valign="top">' + texts[language].airport + ':</td><td><strong><a href="' + airplane.flight.departureAirport.website + '" target="_new">' + airplane.flight.departureAirport.name + '</a></strong>&nbsp;&nbsp;' + (airplane.flight.departureAirport.country.flagURL ? '<img src="' + airplane.flight.departureAirport.country.flagURL + '" alt="' + airplane.flight.departureAirport.country.name + '" title="' + airplane.flight.departureAirport.country.name + '" />' : '') + '</td></tr>' +
          (airplane.flight.scheduledDepartureTime ? '<tr><td valign="top">' + texts[language].scheduledDepartureTime + ':</td><td>' + formatDate(airplane.flight.scheduledDepartureTime, 'dd.MM.yyyy HH:mm') + '</td></tr>' : '') +
          (airplane.flight.actualDepartureTime ? '<tr><td valign="top">' + texts[language].actualDepartureTime + ':</td><td>' + formatDate(airplane.flight.actualDepartureTime, 'dd.MM.yyyy HH:mm') + '</td></tr>' : '')
        :
          '') +
        (airplane.flight.arrivalAirport ?
          '<tr><td colspan="2" style="padding-top:15px; border-bottom:1px solid black"><strong>' + texts[language].arrival + '</strong></td></tr>' +
          '<tr><td colspan="2" style="line-height:2px">&nbsp;</td></tr>' +
          '<tr><td valign="top">' + texts[language].airport + ':</td><td><strong><a href="' + airplane.flight.arrivalAirport.website + '" target="_new">' + airplane.flight.arrivalAirport.name + '</a></strong>&nbsp;&nbsp;' + (airplane.flight.arrivalAirport.country.flagURL ? '<img src="' + airplane.flight.arrivalAirport.country.flagURL + '" alt="' + airplane.flight.arrivalAirport.country.name + '" title="' + airplane.flight.arrivalAirport.country.name + '" />' : '') + '</td></tr>' +
          (airplane.flight.scheduledArrivalTime ? '<tr><td valign="top">' + texts[language].scheduledArrivalTime + ':</td><td>' + formatDate(airplane.flight.scheduledArrivalTime, 'dd.MM.yyyy HH:mm') + '</td></tr>' : '') +
          (airplane.flight.estimatedArrivalTime ? '<tr><td valign="top">' + texts[language].estimatedArrivalTime + ':</td><td>' + formatDate(airplane.flight.estimatedArrivalTime, 'dd.MM.yyyy HH:mm') + '</td></tr>' : '')
        :
          '') +
        '<tr><td colspan="2" style="padding-top:15px; border-bottom:1px solid black"><strong>' + texts[language].trackingInformation + '</strong></td></tr>' +
        '<tr><td colspan="2" style="line-height:2px">&nbsp;</td></tr>' +
        '<tr><td>' + texts[language].speed + ':</td><td id="speed"></td></tr>' +
        '<tr><td>' + texts[language].altitude + ':</td><td id="altitude"></td></tr>' +
        '<tr><td valign="top">' + texts[language].position + ':</td><td id="position"></td></tr>' +
        '<tr><td>' + texts[language].verticalRate + ':</td><td id="verticalRate"></td></tr>' +
        '</table>';
    } else {
      selectedTab = 'Flight';
      $('tabAirplane').innerHTML = '<a href="javascript:showTab(\'Airplane\')" onmouseover="window.status = \'\'; return true">' + texts[language].airplane + '</a></li>';
      $('tabFlight').innerHTML = '<span>' + texts[language].flight + '</span>';
      $('tabContent').innerHTML =
        '<table cellpadding="0" cellspacing="5" width="100%">' +
        '<tr><td colspan="2" style="padding-top:15px; border-bottom:1px solid black"><strong>' + texts[language].flightInformation + '</strong></td></tr>' +
        '<tr><td colspan="2" style="line-height:2px">&nbsp;</td></tr>' +
        '<tr><td width="80" valign="top">' + texts[language].flight + ':</td><td><strong>' + airplane.flight.flightIdentification + '</strong></td></tr>' +
        (airplane.flight.airline ? '<tr><td valign="top">' + texts[language].airline + ':</td><td>' + airplane.flight.airline.name + '&nbsp;&nbsp;' + (airplane.flight.airline.country.flagURL ? '<img src="' + airplane.flight.airline.country.flagURL + '" alt="' + airplane.flight.airline.country.name + '" title="' + airplane.flight.airline.country.name + '" />' : '') + '</td></tr>' : '') +
    	//'<tr><td valign="top">Squawk:</td><td id="squawk"></td></tr>' +    
		(airplane.flight.departureAirport ?
          '<tr><td colspan="2" style="padding-top:15px; border-bottom:1px solid black"><strong>' + texts[language].departure + '</strong></td></tr>' +
          '<tr><td colspan="2" style="line-height:2px">&nbsp;</td></tr>' +
          '<tr><td valign="top">' + texts[language].airport + ':</td><td><strong>' + airplane.flight.departureAirport.name + '</strong>&nbsp;&nbsp;' + (airplane.flight.departureAirport.country.flagURL ? '<img src="' + airplane.flight.departureAirport.country.flagURL + '" alt="' + airplane.flight.departureAirport.country.name + '" title="' + airplane.flight.departureAirport.country.name + '" />' : '') + '</td></tr>' +
          (airplane.flight.scheduledDepartureTime ? '<tr><td valign="top">' + texts[language].scheduledDepartureTime + ':</td><td>' + formatDate(airplane.flight.scheduledDepartureTime, 'dd.MM.yyyy HH:mm') + '</td></tr>' : '') +
          (airplane.flight.actualDepartureTime ? '<tr><td valign="top">' + texts[language].actualDepartureTime + ':</td><td>' + formatDate(airplane.flight.actualDepartureTime, 'dd.MM.yyyy HH:mm') + '</td></tr>' : '')
        :
          '') +
        (airplane.flight.arrivalAirport ?
          '<tr><td colspan="2" style="padding-top:15px; border-bottom:1px solid black"><strong>' + texts[language].arrival + '</strong></td></tr>' +
          '<tr><td colspan="2" style="line-height:2px">&nbsp;</td></tr>' +
          '<tr><td valign="top">' + texts[language].airport + ':</td><td><strong>' + airplane.flight.arrivalAirport.name + '</strong>&nbsp;&nbsp;' + (airplane.flight.arrivalAirport.country.flagURL ? '<img src="' + airplane.flight.arrivalAirport.country.flagURL + '" alt="' + airplane.flight.arrivalAirport.country.name + '" title="' + airplane.flight.arrivalAirport.country.name + '" />' : '') + '</td></tr>' +
          (airplane.flight.scheduledArrivalTime ? '<tr><td valign="top">' + texts[language].scheduledArrivalTime + ':</td><td>' + formatDate(airplane.flight.scheduledArrivalTime, 'dd.MM.yyyy HH:mm') + '</td></tr>' : '') +
          (airplane.flight.estimatedArrivalTime ? '<tr><td valign="top">' + texts[language].estimatedArrivalTime + ':</td><td>' + formatDate(airplane.flight.estimatedArrivalTime, 'dd.MM.yyyy HH:mm') + '</td></tr>' : '')
        :
          '') +
        '<tr><td colspan="2" style="padding-top:15px; border-bottom:1px solid black"><strong>' + texts[language].trackingInformation + '</strong></td></tr>' +
        '<tr><td colspan="2" style="line-height:2px">&nbsp;</td></tr>' +
        '<tr><td>' + texts[language].speed + ':</td><td id="speed"></td></tr>' +
        '<tr><td>' + texts[language].altitude + ':</td><td id="altitude"></td></tr>' +
        '<tr><td valign="top">' + texts[language].position + ':</td><td id="position"></td></tr>' +
        '<tr><td>' + texts[language].verticalRate + ':</td><td id="verticalRate"></td></tr>' +
        '</table>';
    }
  }
  updateAirplaneInfo(airplane);
  new Effect.Appear('infoBar');
}


function updateAirplaneInfo(airplane)
{
  if (selectedTab == 'Flight')
  {
	//update squawk
    //???$('squawk').innerHTML = airplane.trackingPoint.squawk);
   
    //update speed
    if (units == 'aviatic')
      $('speed').innerHTML = Math.round(airplane.trackingPoint.speed) + ' kn';
    else if (units == 'metric')
      $('speed').innerHTML = Math.round(airplane.trackingPoint.speed*1.852) + ' km/h';
    
    //update altitude
    if (units == 'aviatic')
      $('altitude').innerHTML = Math.round(airplane.trackingPoint.altitude) + ' ft (AMSL)';
    else if (units == 'metric')
      $('altitude').innerHTML = Math.round(airplane.trackingPoint.altitude/3.2808) + ' m (AMSL)';
    
    //update position
    $('position').innerHTML = getPositionInSIUnit(airplane);
    
    //update verticalRate
    if (units == 'aviatic')
    {
      var verticalRate = Math.round(airplane.trackingPoint.verticalRate);
      $('verticalRate').innerHTML = '<img src="images/' + (verticalRate == 0 ? 'arrow_straight.png' : (verticalRate > 0 ? 'arrow_up.png' : 'arrow_down.png')) + '" alt="" title="" />&nbsp;' + (verticalRate > 0 ? '+' : '') + verticalRate + ' ft/min';
    }
    else if (units == 'metric')
    {
      var verticalRate = Math.round(airplane.trackingPoint.verticalRate*0.00508);
      $('verticalRate').innerHTML = '<img src="images/' + (verticalRate == 0 ? 'arrow_straight.png' : (verticalRate > 0 ? 'arrow_up.png' : 'arrow_down.png')) + '" alt="" title="" />&nbsp;' + (verticalRate > 0 ? '+' : '') + verticalRate + ' m/s';
    }
  }
}




//*****************************************************************************
// XTABS - Scrollable Tabs
//*****************************************************************************
var xListShown = new Array();
function toggleXTab(tab)
{
  if (xListShown[tab]) hideXTab(tab);
  else
  { 
    var xTabs = $('xTabs').getElementsByClassName('XTab');
    for(var i=0; i<xTabs.length; i++)
    {
      xTabs[i].style.zIndex = xTabs[i].id == tab ? 19 : xTabs.length-i;
    }
    showXTab(tab);
  }
}


function hideXTab(tab, withoutScrolling)
{
  if(!withoutScrolling && parseInt($(tab).style.bottom) >= (Element.getHeight('statusBar') - Element.getHeight(tab)) + xTabsScrollVelocity)
  {
    $(tab).style.bottom = parseInt($(tab).style.bottom) - xTabsScrollVelocity + 'px';
    window.setTimeout('hideXTab(\'' + tab + '\')', 1);
    return;
  }
  else if(parseInt($(tab).style.bottom) >= (Element.getHeight('statusBar') - Element.getHeight(tab)))
  {
    $(tab).style.bottom = (Element.getHeight('statusBar') - Element.getHeight(tab)) + 'px';
  }
  xListShown[tab] = false;
  var xTabs = $('xTabs').getElementsByClassName('XTab');
  for(var i=0; i<xTabs.length; i++)
  {
    xTabs[i].style.zIndex = xTabs.length-i;
  }
}


function showXTab(tab, withoutScrolling)
{
  if(!withoutScrolling && parseInt($(tab).style.bottom) <= Element.getHeight('statusBar')-11)
  {
    $(tab).style.bottom = parseInt($(tab).style.bottom) + xTabsScrollVelocity + 'px';
    window.setTimeout('showXTab(\'' + tab + '\')', 2);
    return;
  }
  if (window.innerHeight) //Firefox, Netscape, ...
  {
    $(tab).style.bottom = Element.getHeight('statusBar') - 4 + 'px';
  }
  else //IE
  {
    $(tab).style.bottom = Element.getHeight('statusBar') - 15 + 'px';
  }
  xListShown[tab] = true; 
}

function updateXTabs()
{
  $('xTabs').innerHTML =
  ' <div id="options" class="XTab">' +
  '  <h1 onclick="toggleXTab(\'options\')">' + texts[language].properties + '</h1>' +
  '  <p>' +
  '   <div class="option">' +
  '    <strong>' + texts[language].mapType + '</strong><br />' +
  '    <input type="radio" name="mapType" value="0" ' + (mapType == 0 ? 'checked="checked" ' : '') + 'onclick="updateMapType(this.value)">' + texts[language].streetMap + '</input>' +
  '    <input type="radio" name="mapType" value="1" ' + (mapType == 1 ? 'checked="checked" ' : '') + 'onclick="updateMapType(this.value)">' + texts[language].satelliteMap + '</input>' +
  '    <input type="radio" name="mapType" value="2" ' + (mapType == 2 ? 'checked="checked" ' : '') + 'onclick="updateMapType(this.value)">' + texts[language].hybridMap + '</input>' +
  '   </div>' +
  '   <div class="option">' +
  '    <strong>' + texts[language].language + '</strong><br />' +
  '    <table>' +
  '     <tr>' +
  '      <td>' +
  '       <input type="radio" name="language" value="en" ' + (language == 'en' ? 'checked="checked" ' : '') + 'onclick="updateLanguage(this.value)"><img src="images/flags/uk.gif" alt="" title="" /> english</input><br />' +
  '       <input type="radio" name="language" value="fr" ' + (language == 'fr' ? 'checked="checked" ' : '') + 'onclick="updateLanguage(this.value)"><img src="images/flags/fr.gif" alt="" title="" /> fran&ccedil;ais</input>' +
  '      </td>' +
  '      <td>' +
  '       <input type="radio" name="language" value="de" ' + (language == 'de' ? 'checked="checked" ' : '') + 'onclick="updateLanguage(this.value)"><img src="images/flags/de.gif" alt="" title="" /> deutsch</input><br />' +
  '       <input type="radio" name="language" value="it" ' + (language == 'it' ? 'checked="checked" ' : '') + 'onclick="updateLanguage(this.value)"><img src="images/flags/it.gif" alt="" title="" /> italiano</input>' +
  '      </td>' +
  '     </tr>' +
  '    </table>' +
  '   </div>' +
  '   <div class="option">' +
  '    <strong>' + texts[language].units + '</strong><br />' +
  '    <input type="radio" name="units" value="metric" ' + (units == 'metric' ? 'checked="checked" ' : '') + 'onclick="updateUnits(this.value)">' + texts[language].metric + '</input>' +
  '    <input type="radio" name="units" value="aviatic" ' + (units == 'aviatic' ? 'checked="checked" ' : '') + 'onclick="updateUnits(this.value)">' + texts[language].aviatic + '</input>' +
  '   </div>' +
  '   <div class="option">' +
  '    <strong>' + texts[language].tracking + '</strong><br />' +
  '    <input type="radio" name="trackingOption" value="false" ' + (!isTrackingEnabled ? 'checked="checked" ' : '') + 'onclick="disableTracking()">' + texts[language].deactivated + '</input>' +
  '    <input type="radio" name="trackingOption" value="true" ' + (isTrackingEnabled ? 'checked="checked" ' : '') + 'onclick="enableTracking()">' + texts[language].activated + '</input>' +
  '   </div>' +
  '   <div class="option">' +
  '    <strong>' + texts[language].colorGradient + '</strong><br />' +
  '    <input type="radio" name="colorGradient" value="automatic" ' + (colorBar == 'automatic' ? 'checked="checked" ' : '') + 'onclick="updateColorBar(this.value)">' + texts[language].automatic + '</input><br />' +
  '    <input type="radio" name="colorGradient" value="3colors" ' + (colorBar == '3colors' ? 'checked="checked" ' : '') + 'onclick="updateColorBar(this.value)">3 ' + texts[language].colors + '</input>' +
  '    <input type="radio" name="colorGradient" value="5colors" ' + (colorBar == '5colors' ? 'checked="checked" ' : '') + 'onclick="updateColorBar(this.value)">5 ' + texts[language].colors + '</input>' +
  '   </div>' +
  '   <div class="option">' +
  '    <strong>' + texts[language].shadow + '</strong><br />' +
  '    <input type="radio" name="shadowOption" value="false" ' + (!isShadowEnabled ? 'checked="checked" ' : '') + 'onclick="disableShadow()">' + texts[language].deactivated + '</input>' +
  '    <input type="radio" name="shadowOption" value="true" ' + (isShadowEnabled ? 'checked="checked" ' : '') + 'onclick="enableShadow()">' + texts[language].activated + '</input>' +
  '   </div>' + 
  '  </p>' +
  ' </div>' +
  ' <div id="about" class="XTab">' +
  '  <h1 onclick="toggleXTab(\'about\')">' + texts[language].about + '</h1>' +
  '  <p><iframe src="about.html" width="100%" height="400px" scrolling="no" marginheight="0" marginwidth="0" frameborder="0" /></p>' +
  ' </div>';
  
  //setting order (zIndex) of xTabs elements
  var xTabs = $('xTabs').getElementsByClassName('XTab');
  for(var i=0; i<xTabs.length; i++) xTabs[i].style.zIndex = xTabs.length-i;
}


//*****************************************************************************
// SCROLL WHEEL ZOOM EVENT HANDLING
//*****************************************************************************
function handleScrollWheel(delta)
{
  if ($('transparentBackground').style.display != 'block')
  {
  	if (delta < 0) zoomOutMap();
  	else zoomInMap();
  }
}


function wheel(event)
{
  var delta = 0;
  if (!event) event = window.event; //for IE
  if (event.wheelDelta)
  {
    //IE/Opera
    delta = event.wheelDelta/120;
    //In Opera 9, delta differs in sign as compared to IE.
    if (window.opera) delta = -delta;
  }
  else if (event.detail)
  {
    //In Mozilla, sign of delta is different than in IE. Also, delta is multiple of 3.
    delta = -event.detail/3;
  }
  
  //If delta is nonzero, handle it. Basically, delta is now positive if wheel was scrolled up,
  //and negative, if wheel was scrolled down.
  if (delta) handleScrollWheel(delta);
  
  //Prevent default actions caused by mouse wheel. That might be ugly, but we handle scrolls somehow
  //anyway, so don't bother here..
  if (event.preventDefault) event.preventDefault();
  
  event.returnValue = false;
}




//*****************************************************************************
// COOKIE HANDLING
//*****************************************************************************
function loadPropertiesFromCookie()
{
  if(!document.cookie) return;
  var a = document.cookie;
  while(a != '')
  {
    var key = a.substring(0, a.indexOf('='));
    var val = a.substring(a.indexOf('=') + 1, a.indexOf(';') != -1 ? a.indexOf(';') : a.length);
    if (key == 'language') language = val;
    else if (key == 'mapType') mapType = parseInt(val);
    else if (key == 'units') units = val;
    else if (key == 'colorBar') colorBar = val;
    else if (key == 'isTrackingEnabled')
    {
      if (val == 'true') isTrackingEnabled = true;
      else if (val == 'false') isTrackingEnabled = false;
    }
    else if (key == 'isShadowEnabled')
    {
      if (val == 'true') isShadowEnabled = true;
      else if (val == 'false') isShadowEnabled = false;
    }
    else if (key == 'mapZoom')
    {
      var _mapZoom = parseInt(val);
      if (_mapZoom < minMapZoomLevel) _mapZoom = minMapZoomLevel;
      else if (_mapZoom > maxMapZoomLevel) _mapZoom = maxMapZoomLevel;
      mapZoom = _mapZoom;
    }
    else if (key == 'mapCenterLatitude') mapCenterLatitude = parseFloat(val);
    else if (key == 'mapCenterLongitude') mapCenterLongitude = parseFloat(val);
    else if (key == 'withoutTimeout')
    {
      if (val == 'true') withoutTimeout = true;
    }
    else if (key == 'kioskMode')
    {
      if (val == 'true') kioskMode = true;
    } else if (key == 'trackICAO') trackICAO = parseInt(val);  

    if (a.indexOf(';') == -1) break;
    else a = a.substring(a.indexOf(';') + 1, a.length);
    while(a != '' && a.startsWith(' ')) a = a.substring(1, a.length);
  }
}


function savePropertiesToCookie()
{
  var expirationDate = new Date();
  expirationDate = new Date(expirationDate.getTime() + (1000*60*60*24*100)); //save cookie for 100 days
  saveCookie('language', language, expirationDate);
  saveCookie('mapType', mapType, expirationDate);
  saveCookie('units', units, expirationDate);
  saveCookie('colorBar', colorBar, expirationDate);
  saveCookie('isTrackingEnabled', isTrackingEnabled, expirationDate);
  saveCookie('isShadowEnabled', isShadowEnabled, expirationDate);
  saveCookie('mapZoom', map.getZoom(), expirationDate);
  saveCookie('mapCenterLatitude', map.getCenter().lat(), expirationDate);
  saveCookie('mapCenterLongitude', map.getCenter().lng(), expirationDate);
}


function saveCookie(key, value, expirationDate)
{
  document.cookie = key + '=' + value + '; expires=' + expirationDate.toGMTString() + ';'; 
}

