/**
 * Global Variables
 *
 */

// BEGIN: LEGACY
var map;
var object_list;
var move_refresh = 0;
var geocoder = null;
var gmarkers;
var glabels;
var loading = new Object();
var runOnLoad = new Array();

// initialize variables
loading['chat'] = 0;
loading['server'] = 0;
loading['positions'] = 0;
loading['locate'] = 0;
// END: LEGACY

var gOpenAPRS = new OpenAPRS();

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS (object)
 * Defaults for OpenAPRS object.
 * Parameters:
 *   none
 * Returns: n/a
 */
function OpenAPRS() {
  this.runcount = 0;
  this.aprsMap = new Map();
  this.hnyMap = new Map();
  this.myClusterDisplay = false;
  this.oneShot = false;
  this.posArray = new Array();
  this.lastposArray = new Array();
  this.hnyArray = new Array();
  this.isPHG = false;
  this.defaultMapCenter = new GLatLng(38.256973, -122.311685);
  this.defaultMapZoom = 11;
  this.track = new Track(this);
  this.load = undefined;
  this.loadCenter = true;
  this.hnyAngle = 0.0;
  this.hnyCount = 0;
  this.hnyCenter = undefined;
  this.hnyRange = 5;
  this.centerMapArguments = false;
  this.findMapArguments = undefined;
} // OpenAPRS

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::onTimer
 * Performs routine actions based on a 500ms timer.
 * Parameters:
 *   none
 * Returns: n/a
 */
OpenAPRS.prototype.onTimer = function() {
  this.runcount += 0.5;
  var d = new Date();

  changeById('clock', d.toFormattedUTCString());

  this.parseAprsQueue();
  this.parseHnyQueue();

  // Load any new stations 
  if ((this.runcount % 15) == 0 || this.runcount == 1)
    this.refresh();

  if ((this.runcount % 5) == 0)
    this.checkTrackLinks();


  if ((this.runcount % 10) == 0 &&
      !isChecked('openaprs_form_pause'))
    this.expireAprs(6);

  // set the timer
  window.setTimeout('gOpenAPRS.onTimer()', 500);
} // OpenAPRS::onTimer

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::onLoad
 * Loads google Maps settings for OpenAPRS interface.
 * Parameters: n/a
 * Returns: n/a      
 */
OpenAPRS.prototype.onLoad = function() {
  document.body.style.overflow = 'hidden';      

  // Try and load map center and zoom.
  if (this.centerMapArguments == false)
    this.getDefaultCenterAndZoomFromCookie();

  // Center the map on Palo Alto
  map = new GMap2(document.getElementById("mapDiv"));
  //map.addControl(new GSmallMapControl());
  //map.addControl(new GMapTypeControl());
  //map.disableDragging();      
  map.setCenter(this.defaultMapCenter, this.defaultMapZoom);

  var topLeft = new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(10,100));
  var bottomRight = new GControlPosition(G_ANCHOR_BOTTOM_RIGHT, new GSize(0,0));

  map.addMapType(G_SATELLITE_3D_MAP);
  map.addMapType(G_PHYSICAL_MAP);
  //map.setMapType(G_HYBRID_MAP);
  map.addControl(new GLargeMapControl(), topLeft);
  map.addControl(new GMapTypeControl(), bottomRight);
  map.disableScrollWheelZoom();
  geocoder = new GClientGeocoder();

  var center = map.getCenter();
  navUpdate('navOverlay2', center.y, center.x);

  object_list = new Array();

  var me = this;
  GEvent.addListener(map, "moveend", function() {
    if (me.oneShot == true) {
      me.oneShot = false;
      log('Map: Will refresh on next interval.');
      return;
    } // if

    //me.refresh();
  });

  GEvent.addListener(map, "mousemove", function(point) {
    navUpdate('navOverlay2', point.lat(), point.lng());
  });

  GEvent.addListener(map, "mouseout", function(point) {
    var center = map.getCenter();

    navUpdate('navOverlay2', center.y, center.x);
    //hideWindow('divConsole');
  });

  GEvent.addListener(map, "infowindowopen", function() {
    pause = document.getElementById("openaprs_form_pause");
    if (pause == null)
      return;

    pause.checked = true;

    changeById('mapSTATUS', 'Paused');
    changeById('topMESSAGE', 'Paused');

    showControl('controlPAUSE', false);
  });

  GEvent.addListener(map, "infowindowclose", function() {
    if (cObject.isON())
      return;

    pause = document.getElementById("openaprs_form_pause");
    if (pause == null)
      return;

    changeById('mapSTATUS', 'Resuming');
    changeById('topMESSAGE', 'Resuming');

    pause.checked = false;
    showControl('controlPAUSE', false);
  });

  GEvent.addListener(map, "zoomend", function(oldLevel, newLevel) {
    if (!gOpenAPRS.track.isPaused()) {
      changeById('OpenAPRS:Menu:Status:Mode', 'Tracking');
      return;
    } // if

    if (newLevel < 9)
      changeById('OpenAPRS:Menu:Status:Mode', 'Cluster');
    else
      changeById('OpenAPRS:Menu:Status:Mode', 'Map');

    if (this.load != undefined)
      changeById('OpenAPRS:Menu:Status:Mode', 'View');

    me.refresh();
  });

  this.onTimer();
  updateChat();
  updateAd();
  checkPHG();
  checkHNY();
  checkMOUSE();

  toggleCONSOLE();
  //toggleSTATIONS();

  var loadTips = readCookie("myTips");
  if (loadTips == "yes" || loadTips == "") {
    loadHelp('random:tips');
    windowControl('helpOverlay');
  } // if

  // Restore tracking list if there is a cookie.
  this.track.restore();
  this.track.checkMiniImage();

  // add logout functions
  onLogout.push(new Function("changeFieldById('aprsMESSAGE:Form:Timestamp', '0'); clearRows('messagingTable')"));
  onLogout.push(new Function("clearRows('viewTable');"));

  if (this.findMapArguments != undefined)
    this.centerOnGeocode(this.findMapArguments);

} // OpenAPRS::onLoad

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::centerOnGeocode
 * Tries to look up an address, zip code or city & state pair.
 * Parameters:
 *   address		: Address to try and geocode lookup.
 * Returns: n/a      
 */
OpenAPRS.prototype.centerOnGeocode = function(address) {
  var me = this;

  if (geocoder) {
    geocoder.getLatLng(
      address,
      function(point) {
        if (!point) {
          changeById("locate_status", "Unable to locate");
          changeById("locateOverlayMessage", "Unable to locate");
          changeById('mapSTATUS', 'Not Found');
          changeById('topMESSAGE', 'Not Found');
          logById('logContainer', 'logTable', "Geocode: Unable to locate ["+address+"]");
        } // else
        else {
          changeById("locate_status", "Located, centering map");     
          hideWindow('locateOverlay');
          logById('logContainer', 'logTable', "Geocode: Found location, centering map ["+address+"]");
          map.setCenter(point, 11);
          changeById('mapSTATUS', 'Map Recentered');
          changeById('topMESSAGE', 'Map Recentered');
          //var marker = new GMarker(point);
          //map.addOverlay(marker);
          //marker.openInfoWindowHtml(address);
          me.getLastpositionsFromServer(map);
          setLocation();
        } // else
      } // function
    );
  } // if
} // OpenAPRS::centerOnGeocode

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::findClicked
 * Tries to look up an address, zip code or city & state pair.
 * Parameters:
 *   none
 * Returns: n/a      
 */
OpenAPRS.prototype.findClicked = function() {
  var f = document.getElementById('OpenAPRS:Console:Form:Find');

  if (f == null)
    return;

  this.centerOnGeocode(f.value);

  return;
} // OpenAPRS::findClicked

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::saveView
 * Tries to look up an address, zip code or city & state pair.
 * Parameters:
 *   none
 * Returns: n/a      
 */
OpenAPRS.prototype.saveView = function() {

  if (this.track.isPaused())
    this.sendViewToServer(map);
  else
    this.sendTrackToServer(map);

  return;
} // OpenAPRS::saveView

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::findClicked
 * Tries to look up an address, zip code or city & state pair.
 * Parameters:
 *   none
 * Returns: n/a      
 */
OpenAPRS.prototype.loadView = function(id) {
  unPause('openaprs_form_pause');

  showWindow('OpenAPRS:Console:Unload');
  this.load = id;
  this.clearRefresh();
  this.loadCenter = true;

  return;
} // OpenAPRS::loadView

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::unloadView
 * Unloads the currently loaded view.
 * Parameters:
 *   none
 * Returns: n/a      
 */
OpenAPRS.prototype.unloadView = function(id) {
  unPause('openaprs_form_pause');

  hideWindow('OpenAPRS:Console:Unload');
  this.load = undefined;
  this.clearRefresh();
  this.loadCenter = false;

  return;
} // OpenAPRS::unloadView

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::trackClicked
 * Adds a callsign to the tracking list.
 * Parameters:
 *   id			: Id of field to pull callsign from.
 * Returns: n/a      
 */
OpenAPRS.prototype.trackClicked = function(id) {
  var t = document.getElementById(id);

  if (t == undefined || t.value == undefined)
    return;

  if (t.value.length < 1)
    return;

  this.track.add(t.value);

  windowControl('trackOverlay');

  if (!this.track.isOn())
    this.track.toggle();

  if (this.track.isPaused())
    this.track.unpause();

  return;
} // OpenAPRS::trackClicked

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::trackStation
 * Adds a callsign to the tracking list.
 * Parameters:
 *   n			: Name of station to track.
 * Returns: n/a      
 */
OpenAPRS.prototype.trackStation = function(n) {
  if (n == undefined)
    return;

  if (n.length < 1)
    return;

  this.track.add(n);

  windowControl('trackOverlay');

  if (!this.track.isOn())
    this.track.toggle();

  if (this.track.isPaused())
    this.track.unpause();

  return;
} // OpenAPRS::trackStation

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::getDefaultCenterAndZoomFromCookie
 * Tries to look up an address, zip code or city & state pair.
 * Parameters:
 *   none
 * Returns: n/a      
 */
OpenAPRS.prototype.getDefaultCenterAndZoomFromCookie = function() {
  var caz = readCookie('myLocation');

  if (caz == undefined)
    return false;

  var s = caz.split(',');
  if (s.length != 3)
    return false;

  var lat = parseFloat(s[0]);
  var lng = parseFloat(s[1]);
  var zoom = parseInt(s[2]);

  if (zoom < 0 || zoom > 23)
    return false;

  if (!isCoordinate(lat, lng))
    return false;

  this.defaultMapCenter = new GLatLng(lat, lng);
  this.defaultMapZoom = zoom;

  return true;
} // OpenAPRS::getDefaultCenterAndZoomFromCookie

/**
 * OpenAPRS::Control
 *
 * Select all text on click.
 *
 */
OpenAPRS.prototype.Control = function(id, doACTION) {
  var myImg = document.getElementById(id);
  var path = '/images/c/';

  if (myImg == null)
    return;

  var menus = {
    "cOPENAPRS": new Array("openaprs.png", "OpenAPRS", "cOPENAPRS:Overlay"),
    "cEDIT":  new Array("edit.png", "Edit", "cEDIT:Overlay"),
    "cTOOLS":  new Array("tools.png", "Tools", "cTOOLS:Overlay"),
    "cVIEW":  new Array("view.png", "View", "cVIEW:Overlay"),
    "cHELP":  new Array("help.png", "Help", "cHELP:Overlay"),
    "cDONATE":  new Array("donate.png", "Donate"),
    "cBLOG":  new Array("blog.png", "BLOG")
  };

  var tooltips = {
    "controlCHAT": '<div class="tooltip"><b>Web Chat</b><br /><br />Chat with others users currently on the website.</div>'
  };

  for(var m in menus) {
    if (m == id) {
      if (doACTION == 'over') {
        var mySub = "over/";
        if (tooltips[m] != undefined)
          Tip(tooltips[m], BALLOON, true, ABOVE, true, OFFSETX, 0);

        if (menus[m][2] != undefined)
          showWindow(menus[m][2]);
      } // if
      else if (doACTION == 'on') {
        var mySub = "on/";
        myImg.src = path + mySub + menus[m][0];
      } // if
      else if (doACTION == 'off') {
        var mySub = "off/";
        myImg.src = path + mySub + menus[m][0];
        UnTip();
        if (menus[m][2] != undefined)
          hideWindow(menus[m][2]);
      } // if
      else {
        var mySub = "off/";
        UnTip();
      } // else

      myImg.src = path + mySub + menus[m][0];
    } // if
  } // for

  return void(0);
} // OpenAPRS::Control()

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::getLastpositionsFromServer
 * Retrieves the latest positions from the server based on coordinates.
 * Parameters:
 *   none
 * Returns: n/a
 */
OpenAPRS.prototype.getLastpositionsFromServer = function(map) {
  var request = createXMLHttpRequest();
  var pause = document.getElementById("openaprs_form_pause");
  var me = this;

  if (pause != null && pause.checked == true) {
    changeById('locate_status', 'Paused');
    changeById('mapSTATUS', 'Paused');
    changeById('topMESSAGE', 'Paused');
    return;
  } // if

  if (requestMap.isKey('getLastpositionsFromServer')) {
    // We already have a request in progress.
    var r = requestMap.get('getLastpositionsFromServer');
    if (r.time < (getUnixTime() - 10)) {
      r.request.abort();
      requestMap.remove('getLastpositionsFromServer');
      log('Clusters: Connection timed out.');
      changeById('mapSTATUS', 'Timed out');
    } // if
    else {
      changeById('mapSTATUS', 'Please wait');
      return false;
    } // else
  } // if

  if (map.getZoom() < 4) {
    changeById('locate_status', 'Stopped');
    changeById('mapSTATUS', 'Stopped');
    changeById('topMESSAGE', 'Stopped');
    log('Map Refresh: Stopped, please zoom in.');
    return;
  } // if

  if (map.getZoom() < 9 && this.load == undefined) {
    if (this.oneShot == true) {
      changeById('locate_status', 'Stopped');
      changeById('mapSTATUS', 'Stopped');
      changeById('topMESSAGE', 'Stopped');
      return;
    } // if

    gOpenAPRS.getClustersFromServer(map);

    this.oneShot = true;
    return;
  } // if
  else
    this.oneShot = false;


 // Need a bounding box for query X = Long, Y = Lat
  var bounds = map.getBounds();

  var sw = bounds.getSouthWest();
  var ne = bounds.getNorthEast();

  /**
   * Default Variables
   *
   * Span through the possible field values
   * and set our variable list accordingly.
   *
   */
  var ids = {
    "find": "openaprs_form_find",
    "tocall": "openaprs_form_tocall",
    "limit": "openaprs_form_limit",
    "limit_tracks": "openaprs_form_limit_tracks",
    "limit_uls": "openaprs_form_limit_uls",
    "age": "openaprs_form_age",
    "units": "openaprs_form_units",
    "geocoords": "openaprs_form_geocoords",
    "save": "aprsVIEW:Form:Name",
    "show_tracks": "openaprs_form_show_tracks",
    "show_irlp": "openaprs_form_show_irlp",
    "show_uls": "openaprs_form_show_uls",
    "show_phg": "openaprs_form_show_phg",
    "show_quakes": "openaprs_form_show_quakes",
    "tactical": "openaprs_form_tactical",
    "zoom": "openaprs_form_fail",
    "timezone": "openaprs_form_timezone",
    "load": "-none",
    "BBOX": "-none"
  };

  var dfts = {
   "find": "",
    "tocall": "",
    "limit": "15",
    "limit_tracks": "15",
    "limit_uls": "100",
    "age": ((map.getZoom() < 9) ? "20 MINUTE" : "4 HOUR"),
    "units": "imperial",
    "geocoords": "decimal",
    "save": "",
    "show_tracks": ((map.getZoom() < 9) ? "no" : "yes"),
    "show_irlp": "yes",
    "show_uls": "no",
    "show_phg": "no",
    "show_quakes": "yes",
    "tactical": "",
    "zoom" : map.getZoom().toString(),
    "timezone": "America/Los_angeles",
    "load": (this.load != undefined ? this.load : ""),
    "BBOX": sw.lng()+","+sw.lat()+","+ne.lng()+","+ne.lat()
  };

  var var_list = createURI(document, false, ids, dfts);

  changeById('mapSTATUS', 'Updating');
  changeById('topMESSAGE', 'Updating');
  changeById("locate_status", "<i>updating</i>");
  if (this.load != undefined)
    changeById('OpenAPRS:Menu:Status:Mode', 'View');
  else
    changeById('OpenAPRS:Menu:Status:Mode', 'Map');

  var queryString = "/ajax/openaprs/aprs/lastpositions.php?hash="
                    + seed
                    + var_list;

  var sw = new Stopwatch();
  var sw2 = new Stopwatch();
  var sw3 = new Stopwatch();
  sw.Start();
  sw2.Start();

  var r = new requestObject(request);
  requestMap.add('getLastpositionsFromServer', r);

  request.open("GET", queryString, true);
  request.onreadystatechange = function() {
    var len;

    if (request.readyState == 4) {
      var xmlDoc = request.responseXML;
      sw2.Stop();

      // Remove from requestMap.
      requestMap.remove('getLastpositionsFromServer');

      sw3.Start();
      var c = me.storeLastpositionsFromServer(request);
      sw3.Stop();

      changeById("mapRESULTS", "Results " + c + " stations (" + sw.Time() + " seconds)");
      log('Map: Results ' + c + ' stations (T:' + sw.Time() + '/X:'+sw2.Time()+'/P:'+sw3.Time()+')');
      changeById("mapSTATUS", 'Idle');
      changeById('topMESSAGE', 'Idle');

    } // if

  } // function()

  request.send(null);
} // OpenAPRS::getLastpositionsFromServer

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::getTrackLastpositionsFromServer
 * Retrieves the latest positions from the server based on track list.
 * Parameters:
 *   none
 * Returns: n/a
 */
OpenAPRS.prototype.getTrackLastpositionsFromServer = function(map) {
  var request = createXMLHttpRequest();
  var pause = document.getElementById("openaprs_form_pause");
  var me = this;

  if (pause != null && pause.checked == true) {
    changeById('locate_status', 'Tracking Paused');
    changeById('mapSTATUS', 'Tracking Paused');
    changeById('topMESSAGE', 'Paused');
    return;
  } // if

  if (requestMap.isKey('getLastpositionsFromServer')) {
    // We already have a request in progress.
    var r = requestMap.get('getTrackLastpositionsFromServer');
    if (r.time < (getUnixTime() - 10)) {
      r.request.abort();
      requestMap.remove('getTrackLastpositionsFromServer');
      log('Track: Connection timed out.');
      changeById('mapSTATUS', 'Tracking: Timed out');
    } // if
    else {
      changeById('mapSTATUS', 'Tracking: Please wait');
      return false;
    } // else
  } // if

  /**
   * Default Variables
   *
   * Span through the possible field values
   * and set our variable list accordingly.
   *
   */
  var ids = {
    "f": "-none",
    "tocall": "openaprs_form_tocall",
    "limit": "openaprs_form_limit",
    "limit_tracks": "openaprs_form_limit_tracks",
    "limit_uls": "openaprs_form_limit_uls",
    "age": "openaprs_form_age",
    "units": "openaprs_form_units",
    "geocoords": "openaprs_form_geocoords",
    "save": "aprsVIEW:Form:Name",
    "show_tracks": "openaprs_form_show_tracks",
    "show_irlp": "openaprs_form_show_irlp",
    "show_uls": "openaprs_form_show_uls",
    "show_phg": "openaprs_form_show_phg",
    "show_quakes": "openaprs_form_show_quakes",
    "tactical": "openaprs_form_tactical",
    "zoom": "openaprs_form_fail",
    "load": "-none",
    "timezone": "openaprs_form_timezone"
  };

  var dfts = {
    "f": this.track.getTrackList(),
    "tocall": "",
    "limit": "15",
    "limit_tracks": "15",
    "limit_uls": "100",
    "age": ((map.getZoom() < 9) ? "20 MINUTE" : "4 HOUR"),
    "units": "imperial",
    "geocoords": "decimal",
    "save": "",
    "show_tracks": ((map.getZoom() < 9) ? "no" : "yes"),
    "show_irlp": "yes",
    "show_uls": "no",
    "show_phg": "no",
    "show_quakes": "yes",
    "tactical": "",
    "zoom" : map.getZoom().toString(),
    "load": (this.load != undefined ? this.load : ""),
    "timezone": "America/Los_angeles"
  };

  var var_list = createURI(document, false, ids, dfts);

  changeById('mapSTATUS', 'Updating');
  changeById('topMESSAGE', 'Updating');
  if (this.load != undefined)
    changeById('OpenAPRS:Menu:Status:Mode', 'View: Track');
  else
    changeById('OpenAPRS:Menu:Status:Mode', 'Tracking');

  changeById("locate_status", "<i>updating</i>");

  var queryString = "/ajax/openaprs/aprs/track.php?hash="
                    + seed
                    + var_list;

  var sw = new Stopwatch();
  var sw2 = new Stopwatch();
  var sw3 = new Stopwatch();
  sw.Start();
  sw2.Start();

  var r = new requestObject(request);
  requestMap.add('getTrackLastpositionsFromServer', r);

  request.open("GET", queryString, true);
  request.onreadystatechange = function() {
    var len;

    if (request.readyState == 4) {
      var xmlDoc = request.responseXML;
      sw2.Stop();

      // Remove from requestMap.
      requestMap.remove('getTrackLastpositionsFromServer');

      sw3.Start();
      var c = me.storeLastpositionsFromServer(request);
      sw3.Stop();

      changeById("mapRESULTS", "Results " + c + " stations (" + sw.Time() + " seconds)");
      log('Tracking Map: Results ' + c + ' stations (T:' + sw.Time() + '/X:'+sw2.Time()+'/P:'+sw3.Time()+')');
      changeById("mapSTATUS", 'Tracking: Idle');
      changeById('topMESSAGE', 'Idle');

    } // if

  } // function()

  request.send(null);
} // OpenAPRS::getTrackLastpositionsFromServer

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::getHnyFromServer
 * Retrieves ULS entries for the city map is centered on.
 * Parameters:
 *   none
 * Returns: n/a
 */
OpenAPRS.prototype.getHnyFromServer = function(map) {
  var request = createXMLHttpRequest();
  var me = this;

  if (requestMap.isKey('getHnyFromServer')) {
    // We already have a request in progress.
    var r = requestMap.get('getHnyFromServer');
    if (r.time < (getUnixTime() - 10)) {
      r.request.abort();
      requestMap.remove('getHnyFromServer');
      log('HNY: Connection timed out.');
    } // if
    else
      return false;
  } // if

  // Need a bounding box for query X = Long, Y = Lat
  var center = map.getCenter();
  this.clearHny();
  this.hnyCenter = center;
  this.hnyCount = 0;
  this.hnyAngle = 0.0;
  this.hnyRange = 5;

  /**
   * Default Variables
   *
   * Span through the possible field values
   * and set our variable list accordingly.
   *
   */
  var ids = {
    "la": center.lat().toString(),
    "lo": center.lng().toString(),
    "limit_tracks": "openaprs_form_limit_tracks",
    "limit_uls": "openaprs_form_limit_uls",
    "timezone": "openaprs_form_timezone"
  };

  var dfts = {
    "la": center.lat().toString(),
    "lo": center.lng().toString(),
    "limit_uls": "100",
    "timezone": "America/Los_angeles"
  };

  var var_list = createURI(document, false, ids, dfts);

  var queryString = "/ajax/openaprs/hny/hny.php?hash="
                    + seed
                    + var_list;

  var sw = new Stopwatch();
  var sw2 = new Stopwatch();
  var sw3 = new Stopwatch();
  sw.Start();
  sw2.Start();

  var r = new requestObject(request);
  requestMap.add('getHnyFromServer', r);

  request.open("GET", queryString, true);
  request.onreadystatechange = function() {
    var len;

    if (request.readyState == 4) {
      var xmlDoc = request.responseXML;
      sw2.Stop();

      // Remove from requestMap.
      requestMap.remove('getHnyFromServer');

      sw3.Start();
      var c = me.storeHnyFromServer(request);
      sw3.Stop();

      log('HNY: Results ' + c + ' stations (T:' + sw.Time() + '/X:'+sw2.Time()+'/P:'+sw3.Time()+')');
    } // if

  } // function()

  request.send(null);
} // OpenAPRS::getHnyFromServer

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::storeHnyFromServer
 * Processes and stores list of ULS entries returned from the server.
 * Parameters:
 *   none
 * Returns: n/a
 */
OpenAPRS.prototype.storeHnyFromServer = function(xmlDoc) {
  var me = this;
  var numRows = 0;
  var i;

  if (xmlDoc == null)
    return;

  var parseMe = xmlDoc.responseText.split('\n');

  // Main large loop
  for(i=0; i < parseMe.length; i++) {
    // Skip broken lines or comments
    if (parseMe[i].length < 1 ||
        parseMe[i].substr(0, 1) == "#")
      continue;

    var v = new Vars();

    v.Parse(parseMe[i]);

    // BEGIN: PROCESS LOCATIONS
    if (v.getField("TY") == "loc") {
      var la = parseFloat(v.getField("la"));
      var lo = parseFloat(v.getField("lo"));
      var p = new GLatLng(la, lo);
      me.hnyCenter = p;

      map.setCenter(p);
    } // if

    // BEGIN: PROCESS LOCATIONS
    if (v.getField("TY") == "uls") {
      this.hnyArray.push(v);
      numRows++;
    } // if
    // END: PROCESS LOCATIONS
  } // for

  return numRows;
} // OpenAPRS::storeHnyFromServer

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::storeLastpositionsFromServer
 * Processes and stores list of last positions returned from the server.
 * Parameters:
 *   none
 * Returns: n/a
 */
OpenAPRS.prototype.storeLastpositionsFromServer = function(xmlDoc) {
  var new_object_list = [];
  var numLastpositions = 0;
  var i;

  if (xmlDoc == null)
    return;

  // Remove cluster display if it's on.
  if (this.myClusterDisplay) {
    map.clearOverlays();
    this.myClusterDisplay = false;
  } // if

  var parseMe = xmlDoc.responseText.split('\n');

  // Main large loop
  for(i=0; i < parseMe.length; i++) {
    // Skip broken lines or comments
    if (parseMe[i].length < 1 ||
        parseMe[i].substr(0, 1) == "#")
      continue;

    var v = new Vars();

    v.Parse(parseMe[i]);

    // Recenter the map if requested.
    if (this.loadCenter 
        && v.getField("TY") == "CN") {
      var la = parseFloat(v.getField("LA"));
      var lo = parseFloat(v.getField("LO"));
      var zm = parseInt(v.getField("ZM"));
      map.setCenter(new GLatLng(la, lo), zm);
      this.loadCenter = false;
    } // if

    // BEGIN: PROCESS LOCATIONS
    if (v.getField("TY") == "LP") {
      //this.addAprs(v);
      //this.addAprsPositions(v);
      this.lastposArray.push(v);

      numLastpositions++;
    } // if
    // END: PROCESS LOCATIONS
  } // for

  return numLastpositions;
} // OpenAPRS::storeLastpositionsFromServer

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::getClustersFromServer
 * Gets positions clusters from server.
 * Parameters:
 *   none
 * Returns: n/a
 */
OpenAPRS.prototype.getClustersFromServer = function(map) {
  var request = createXMLHttpRequest();
  var pause = document.getElementById("openaprs_form_pause");
  var me = this;

  if (requestMap.isKey('getClustersFromServer')) {
    // We already have a request in progress.
    var r = requestMap.get('getClustersFromServer');
    if (r.time < (getUnixTime() - 10)) {
      r.request.abort();
      requestMap.remove('getClustersFromServer');
      log('Clusters: Connection timed out.');
      changeById('mapSTATUS', 'Clusters: Timed out');
    } // if
    else {
      changeById('mapSTATUS', 'Clusters: Please wait');
      return false;
    } // else
  } // if

  if (pause != null && pause.checked == true) {
    changeById('locate_status', 'Clusters: Paused');
    changeById('mapSTATUS', 'Clusters: Paused');
    changeById('topMESSAGE', 'Paused');
    return;
  } // if

 // Need a bounding box for query X = Long, Y = Lat
  var bounds = map.getBounds();

  var sw = bounds.getSouthWest();
  var ne = bounds.getNorthEast();


  /**
   * Default Variables
   *
   * Span through the possible field values
   * and set our variable list accordingly.
   *
   */
  var ids = {
    "find": "openaprs_form_find",
    "units": "openaprs_form_units",
    "geocoords": "openaprs_form_geocoords",
    "timezone": "openaprs_form_timezone",
    "zoom": "none",
    "la1": "none",
    "lo1": "none",
    "la2": "none",
    "lo2": "none"
  };

  var dfts = {
    "find": "",
    "units": "imperial",
    "geocoords": "decimal",
    "timezone": "America/Los_angeles",
    "zoom": map.getZoom().toString(),
    "la1": ne.lat().toString(),
    "lo1": sw.lng().toString(),
    "la2": sw.lat().toString(),
    "lo2": ne.lng().toString()
  };

  var var_list = createURI(document, false, ids, dfts);

  changeById('mapSTATUS', 'Clusters: Updating');
  changeById('topMESSAGE', 'Updating');
  changeById("locate_status", "<i>updating</i>");
  changeById('OpenAPRS:Menu:Status:Mode', 'Cluster');

  // clear the processing queue
  this.lastposArray = new Array();

  var queryString = "/ajax/openaprs/aprs/cluster.php?hash="
                    + seed
                    + var_list;

  var sw = new Stopwatch();
  var sw2 = new Stopwatch();
  var sw3 = new Stopwatch();
  sw.Start();
  sw2.Start();

  var r = new requestObject(request);
  requestMap.add('getClustersFromServer', r);

  request.open("GET", queryString, true);
  request.onreadystatechange = function() {
    var len;

    if (request.readyState == 4) {
      var xmlDoc = request.responseXML;
      sw2.Stop();

      // Remove from requestMap.
      requestMap.remove('getClustersFromServer');

      sw3.Start();
      var c = me.storeClustersFromServer(request);
      sw3.Stop();

      changeById("mapRESULTS", "Results " + c + " clusters (" + sw.Time() + " seconds)");
      log('Cluster: Results ' + c + ' clusters (T:' + sw.Time() + ' X:'+sw2.Time()+' P:'+sw3.Time()+')');

    } // if

  } // function()

  request.send(null);
} // OpenAPRS::getClustersFromServer

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::storeClustersFromServer
 * Stores clusters loaded from server.
 * Parameters:
 *   none
 * Returns: n/a
 */
OpenAPRS.prototype.storeClustersFromServer = function(xmlDoc) {
  var new_object_list = [];
  var numClusters = 0;
  var i;

  if (xmlDoc == null)
    return;

  var parseMe = xmlDoc.responseText.split('\n');
  var numMax = 0;

  // Main large loop
  for(i=0; i < parseMe.length; i++) {
    // Skip broken lines or comments
    if (parseMe[i].length < 1 ||
        parseMe[i].substr(0, 1) == "#")
      continue;

    var v = new Vars();

    v.Parse(parseMe[i]);

    if (v.getField("TY") == "MX")
      numMax = parseInt(v.getField("NUM")) * 1.40;

    // BEGIN: PROCESS LOCATIONS
    if (v.getField("TY") == "CL") {
      var lat1 = parseFloat(v.getField("la1"), 6);
      var lng1 = parseFloat(v.getField("lo1"), 6);
      var lat2 = parseFloat(v.getField("la2"), 6);
      var lng2 = parseFloat(v.getField("lo2"), 6);
      var num = parseInt(v.getField("num"));

      var points = new Array(
                              new GLatLng(lat1, lng1),
                              new GLatLng(lat1, lng2),
                              new GLatLng(lat2, lng2),
                              new GLatLng(lat2, lng1),
                              new GLatLng(lat1, lng1)
                            );

      var f = num / numMax + 0.1;

      var pg = new GPolygon(points, 'red', 1, f, 'red', f);

      //if (map.getZoom() >= 6)  {
        var label = new ELabel(new GLatLng(lat1, lng1), v.getField("num"), "clusterLabelName", new GSize(0,15), 90);
        new_object_list.push(label);
      //} // if

      new_object_list.push(pg);
      numClusters++;
    } // if
    // END: PROCESS LOCATIONS
  } // for

  changeById("locate_status", "<i>" + numClusters + " clusters displayed</i>");
  changeById("mapSTATUS", "Clusters: Idle");
  changeById('topMESSAGE', 'Idle');

  // clear the map markers and draw the new ones
  this.clear();
  map.clearOverlays();
  this.myClusterDisplay = true;

  for(var k = 0; k < new_object_list.length; k++)
    map.addOverlay(new_object_list[k]);

  object_list = new_object_list;

  return numClusters;
} // OpenAPRS::storeClustersFromServer

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::storeLocationFromServer
 * Stores a retrieved location from the server.
 * Parameters:
 *   none
 * Returns: n/a
 */
OpenAPRS.prototype.storeLocationFromServer = function(xmlDoc) {
  var new_object_list = [];
  var numLastpositions = 0;
  var i;

  if (xmlDoc == null)
    return;

  // Remove cluster display if it's on.
  if (this.myClusterDisplay) {
    map.clearOverlays();
    this.myClusterDisplay = false;
  } // if

  var parseMe = xmlDoc.responseText.split('\n');

  // Main large loop
  for(i=0; i < parseMe.length; i++) {
    // Skip broken lines or comments
    if (parseMe[i].length < 1 ||
        parseMe[i].substr(0, 1) == "#")
      continue;

    var v = new Vars();

    v.Parse(parseMe[i]);

    // BEGIN: PROCESS LOCATIONS
    if (v.getField("TY") == "LP") {
      //this.addAprs(v);
      //this.addAprsPositions(v);
      this.lastposArray.push(v);

      numLastpositions++;
    } // if
    // END: PROCESS LOCATIONS
  } // for

  changeById("mapSTATUS", "Idle");
  changeById('topMESSAGE', 'Idle');

  return numLastpositions;
} // OpenAPRS::storeLocationFromServer

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::sendViewToServer
 * Retrieves the latest positions from the server based on coordinates.
 * Parameters:
 *   none
 * Returns: n/a
 */
OpenAPRS.prototype.sendViewToServer = function(map) {
  var request = createXMLHttpRequest();
  var me = this;

  if (this.load != undefined) {
    changeById('aprsVIEW:Error', 'Unload current view first.');
    return 0;
  } // if

  if (requestMap.isKey('sendViewToServer')) {
    // We already have a request in progress.
    var r = requestMap.get('sendViewToServer');
    if (r.time < (getUnixTime() - 10)) {
      r.request.abort();
      requestMap.remove('sendViewToServer');
      log('Save View: Connection timed out.');
      changeById('aprsVIEW:Error', 'Timed out');
    } // if
    else {
      changeById('aprsVIEW:Error', 'Please wait');
      return false;
    } // else
  } // if

  if (map.getZoom() < 9) {
    changeById('aprsVIEW', 'Zoomed out too far, please zoom in and try again.');
    return;
  } // if

 // Need a bounding box for query X = Long, Y = Lat
  var bounds = map.getBounds();
  var center = map.getCenter();

  var sw = bounds.getSouthWest();
  var ne = bounds.getNorthEast();
  var la = center.lat();
  var lo = center.lng();

  /**
   * Default Variables
   *
   * Span through the possible field values
   * and set our variable list accordingly.
   *
   */
  var ids = {
    "find": "openaprs_form_find",
    "tocall": "openaprs_form_tocall",
    "limit": "openaprs_form_limit",
    "limit_tracks": "openaprs_form_limit_tracks",
    "limit_uls": "openaprs_form_limit_uls",
    "age": "openaprs_form_age",
    "units": "openaprs_form_units",
    "geocoords": "openaprs_form_geocoords",
    "save": "aprsVIEW:Form:Name",
    "show_tracks": "openaprs_form_show_tracks",
    "show_irlp": "openaprs_form_show_irlp",
    "show_uls": "openaprs_form_show_uls",
    "show_phg": "openaprs_form_show_phg",
    "show_quakes": "openaprs_form_show_quakes",
    "tactical": "openaprs_form_tactical",
    "zoom": "openaprs_form_fail",
    "timezone": "openaprs_form_timezone",
    "la": "-none",
    "lo": "-none",
    "BBOX": "-none"
  };

  var dfts = {
    "find": "",
    "tocall": "",
    "limit": "15",
    "limit_tracks": "15",
    "limit_uls": "100",
    "age": ((map.getZoom() < 9) ? "20 MINUTE" : "4 HOUR"),
    "units": "imperial",
    "geocoords": "decimal",
    "save": "",
    "show_tracks": ((map.getZoom() < 9) ? "no" : "yes"),
    "show_irlp": "yes",
    "show_uls": "no",
    "show_phg": "no",
    "show_quakes": "yes",
    "tactical": "",
    "zoom" : map.getZoom().toString(),
    "timezone": "America/Los_angeles",
    "la": center.lat().toString(),
    "lo": center.lng().toString(),
    "BBOX": sw.lng()+","+sw.lat()+","+ne.lng()+","+ne.lat()
  };

  var var_list = createURI(document, false, ids, dfts);

  changeById("aprsVIEW:Error", "Saving...");

  var queryString = "/ajax/openaprs/aprs/save/view.php?hash="
                    + seed
                    + var_list;

  var sw = new Stopwatch();
  var sw2 = new Stopwatch();
  var sw3 = new Stopwatch();
  sw.Start();
  sw2.Start();

  var r = new requestObject(request);
  requestMap.add('sendViewToServer', r);

  request.open("GET", queryString, true);
  request.onreadystatechange = function() {
    var len;

    if (request.readyState == 4) {
      var xmlDoc = request.responseXML;
      sw2.Stop();

      // Remove from requestMap.
      requestMap.remove('sendViewToServer');

      sw3.Start();
      var c = me.storeViewReplyFromServer(request);
      sw3.Stop();

      log('Save View: Results ' + c + ' reply (T:' + sw.Time() + '/X:'+sw2.Time()+'/P:'+sw3.Time()+')');
      refreshSavedTracks();
    } // if

  } // function()

  request.send(null);
} // OpenAPRS::sendViewToServer

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::sendTrackToServer
 * Sends the current track to the server.
 * Parameters:
 *   none
 * Returns: n/a
 */
OpenAPRS.prototype.sendTrackToServer = function(map) {
  var request = createXMLHttpRequest();
  var me = this;

  if (this.load != undefined) {
    changeById('aprsVIEW:Error', 'Unload current view first.');
    return 0;
  } // if

  if (requestMap.isKey('sendViewToServer')) {
    // We already have a request in progress.
    var r = requestMap.get('sendViewToServer');
    if (r.time < (getUnixTime() - 10)) {
      r.request.abort();
      requestMap.remove('sendViewToServer');
      log('Save View: Connection timed out.');
      changeById('aprsVIEW:Error', 'Timed out');
    } // if
    else {
      changeById('aprsVIEW:Error', 'Please wait');
      return false;
    } // else
  } // if

 // Need a bounding box for query X = Long, Y = Lat
  var center = map.getCenter();

  var la = center.lat();
  var lo = center.lng();

  /**
   * Default Variables
   *
   * Span through the possible field values
   * and set our variable list accordingly.
   *
   */
  var ids = {
    "f": "-none",
    "tocall": "openaprs_form_tocall",
    "limit": "openaprs_form_limit",
    "limit_tracks": "openaprs_form_limit_tracks",
    "limit_uls": "openaprs_form_limit_uls",
    "age": "openaprs_form_age",
    "units": "openaprs_form_units",
    "geocoords": "openaprs_form_geocoords",
    "save": "aprsVIEW:Form:Name",
    "show_tracks": "openaprs_form_show_tracks",
    "show_irlp": "openaprs_form_show_irlp",
    "show_uls": "openaprs_form_show_uls",
    "show_phg": "openaprs_form_show_phg",
    "show_quakes": "openaprs_form_show_quakes",
    "tactical": "openaprs_form_tactical",
    "zoom": "openaprs_form_fail",
    "timezone": "openaprs_form_timezone",
    "la": "-none",
    "lo": "-none"
  };

  var dfts = {
    "f": this.track.getTrackList(),
    "tocall": "",
    "limit": "15",
    "limit_tracks": "15",
    "limit_uls": "100",
    "age": ((map.getZoom() < 9) ? "20 MINUTE" : "4 HOUR"),
    "units": "imperial",
    "geocoords": "decimal",
    "save": "",
    "show_tracks": ((map.getZoom() < 9) ? "no" : "yes"),
    "show_irlp": "yes",
    "show_uls": "no",
    "show_phg": "no",
    "show_quakes": "yes",
    "tactical": "",
    "zoom" : map.getZoom().toString(),
    "timezone": "America/Los_angeles",
    "la": center.lat().toString(),
    "lo": center.lng().toString()
  };

  var var_list = createURI(document, false, ids, dfts);

  changeById("aprsVIEW:Error", "Saving Track...");

  var queryString = "/ajax/openaprs/aprs/save/track.php?hash="
                    + seed
                    + var_list;

  var sw = new Stopwatch();
  var sw2 = new Stopwatch();
  var sw3 = new Stopwatch();
  sw.Start();
  sw2.Start();

  var r = new requestObject(request);
  requestMap.add('sendViewToServer', r);

  request.open("GET", queryString, true);
  request.onreadystatechange = function() {
    var len;

    if (request.readyState == 4) {
      var xmlDoc = request.responseXML;
      sw2.Stop();

      // Remove from requestMap.
      requestMap.remove('sendViewToServer');

      sw3.Start();
      var c = me.storeViewReplyFromServer(request);
      sw3.Stop();

      log('Save Track View: Results ' + c + ' reply (T:' + sw.Time() + '/X:'+sw2.Time()+'/P:'+sw3.Time()+')');
      refreshSavedTracks();
    } // if

  } // function()

  request.send(null);
} // OpenAPRS::sendTrackToServer

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::storeViewReplyFromServer
 * Stores a retrieved location from the server.
 * Parameters:
 *   none
 * Returns: n/a
 */
OpenAPRS.prototype.storeViewReplyFromServer = function(xmlDoc) {
  var numReplies = 0;
  var i;

  if (xmlDoc == null)
    return;

  var parseMe = xmlDoc.responseText.split('\n');

  // Main large loop
  for(i=0; i < parseMe.length; i++) {
    // Skip broken lines or comments
    if (parseMe[i].length < 1 ||
        parseMe[i].substr(0, 1) == "#")
      continue;

    var v = new Vars();

    v.Parse(parseMe[i]);

    // BEGIN: PROCESS LOCATIONS
    if (v.getField("TY") == "ERR") {
      changeById('aprsVIEW:Error', v.getField("MS"));
      numReplies++;
    } // if
    // END: PROCESS LOCATIONS
  } // for

  return numReplies;
} // OpenAPRS::storeViewReplyFromServer

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::addAprs
 * Adds a new station to the map.
 * Parameters:
 *   v: Special Vars's type record with satellite information.
 * Returns: n/a
 */
OpenAPRS.prototype.addAprs = function(v) {
  var latitude = parseFloat(v.getField("la"), 6);
  var longitude = parseFloat(v.getField("lo"), 6);
  var k = v.getField("nm");
  var id = v.getField("id");
  var ct = parseInt(v.getField("ct"));

  if (isNaN(latitude) || isNaN(longitude))
    return;

  if (this.aprsMap.isKey(k)) {
    // Already in the satellite objectMap.
    // Re just want to sync the time index.
    var a = this.aprsMap.get(k);

    if (ct < a.timeIndex)
      // This really should never happen, it means the packets
      // returned are earlier than the current packet stored.
      return undefined;

    var p = new GLatLng(latitude, longitude);
    a.sync(id, ct, v);
    a.changePosition(p);

    return undefined;
  } // if

  var ov = undefined;
  if (v.isName("OVR"))
    ov = v.getField("ovr");

  // Draw the marker on the map only if it isn't in the station list
  var img = "http://www.opensats.net/images/icons/" + v.getField("img");
  var marker = this.createAprsMarker(k, latitude, longitude, img, ov);
  var name = k;
  if (v.isName("tac"))  
    name = v.getField("tac") + "<br />" + name;    

  var label = new ELabel(new GLatLng(latitude, longitude), k, "satLabelName", new GSize(-50, +20), 90);
  //label.hide();

  // Add this satellite to the map.
  var aprs = new APRS(id, k, ct, marker, label, v.getField("nm"), v);
  aprs.addListener();

  // Only enable the compass line or ground circle if we're in tracking mode
  // for this satellite.
  if (v.isName("TRK") && v.getField("TRK") == "Y") {
//    sat.enableCompassLine();
//    sat.enableGroundCircle();
  } // if

  this.aprsMap.add(k, aprs);

  //if (k == "25544") {
  //  sat.enableTracking();
  //  this.boldSatelliteName(k);
  //} // if

  // Set star next to satellite in the satellite list.
  //this.linkSatelliteName(k);

  map.addOverlay(marker);
  map.addOverlay(label);
  //o.push(marker);
  //o.push(label);

  return marker;
} // OpenAPRS::addAprs

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::removeAprs
 * Removes an aprs station from the map.
 * Parameters:
 *   k		: APRS name to remove.
 * Returns: n/a
 */
OpenAPRS.prototype.removeAprs = function(k) {
  if (!this.aprsMap.isKey(k))
    return false;

  // Set star next to satellite in the satellite list.
  // We shouldn't unstar the satellite since it may
  // be a temporary outage.
  //this.unstarSatelliteName(k);
  //this.unboldSatelliteName(k);
  //this.unlinkSatelliteName(k);

  var a = this.aprsMap.get(k);

  //var refreshPaths = s.isTracking;

  a.remove();
  this.aprsMap.remove(k);

  //if (refreshPaths)
  //  this.getPathsFromServer(map);

  return true;
} // OpenAPRS::removeAprs

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::addHny
 * Adds a new ULS entry to the map.
 * Parameters:
 *   v: Special Vars's type record with satellite information.
 * Returns: n/a
 */
OpenAPRS.prototype.addHny = function(v) {
  var k = v.getField("frn");
  var nm = v.getField("nm");
  var address = v.getField("ad")
                +", "
                +v.getField("cty")
                +", "
                +v.getField("sta");
  var center = this.hnyCenter;
  var gcircle = new GCircle(center.lat(), center.lng());
  var p = gcircle.getPointAtAngle(center.lat(), center.lng(), (this.hnyRange*1000)/2, this.hnyAngle);

  this.hnyAngle += 15;
  if (this.hnyAngle > 360) {
    this.hnyRange += 4;
    this.hnyAngle = 0;
    if (this.hnyRange > 10)
      this.hnyAngle /= 2;
  } // if

  if (this.hnyMap.isKey(k)) {
    // Already in the satellite objectMap.
    // Re just want to sync the time index.
    var u = this.hnyMap.get(k);

    u.sync(v);
    return undefined;
  } // if

  var balloon = '<b>'+v.getField("cl")+'</b><br />'
                +'(<i>'+v.getField("cla")+'</i>)<br /><br />'
                +'<b>'+v.getField("nm")+'</b><br />'
                +v.getField("ad")+'<br />'
                +v.getField("ad2");

  // Draw the marker on the map only if it isn't in the station list
  var img = "http://www.opensats.net/images/icons/gender/no" + v.getField("img");
  var marker = this.createHnyMarker(k, p.lat(), p.lng(), img, balloon);
  var name = nm;

  var label = new ELabel(new GLatLng(p.lat(), p.lng()), nm, "satLabelName", new GSize(-50, +20), 90);
  label.hide();

  // Add this ULS entry to the map.
  var hny = new HNY(k, marker, label, nm, v);
  //hny.addListener();

  this.hnyMap.add(k, hny);

  map.addOverlay(marker);
  map.addOverlay(label);

  this.hnyCount++;
  window.setTimeout('gOpenAPRS.showHam(\''+k+'\', \''+address+'\')', this.hnyCount*200);

  return marker;
} // OpenAPRS::addHny

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::removeHny
 * Removes an aprs station from the map.
 * Parameters:
 *   k		: APRS name to remove.
 * Returns: n/a
 */
OpenAPRS.prototype.removeHny = function(k) {
  if (!this.hnMap.isKey(k))
    return false;

  var h = this.hnyMap.get(k);

  h.remove();
  this.hnyMap.remove(k);

  return true;
} // OpenAPRS::removeHny

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::showHam
 * Use geocoder to move HAM's position to where their address is.
 * Parameters:
 *   k		: HNY id to remove.
 * Returns: n/a
 */
OpenAPRS.prototype.showHam = function(k, address) {
  var me = this;

  if (!this.hnyMap.isKey(k))
    return false;

  var h = this.hnyMap.get(k);

  if (geocoder) {
    geocoder.getLatLng(
      address,
      function(point) {
        if (point) {  
          h.changePosition(point);
        } // if
//        else
//          logById('logContainer', 'logTable', 'ULS: Unable to locate '+h.name+' ['+address+']');

        me.hnyCount--;
        if (me.hnyCount == 0)
          me.organizeHny();
      } // function
    );
  } // if


  return true;
} // OpenAPRS::showHam

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::clear
 * Clears all aprs stations on the map.
 * Parameters:
 *   none
 * Returns: n/a
 */
OpenAPRS.prototype.clear = function() {
  var keys = this.aprsMap.getKeys();
  for(var i=0; i < keys.length; i++) {
    var a = this.aprsMap.get(keys[i]);
    a.remove();
    this.aprsMap.remove(keys[i]);
  } // for
} // OpenAPRS::clear

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::clearHny
 * Clears all aprs stations on the map.
 * Parameters:
 *   none
 * Returns: n/a
 */
OpenAPRS.prototype.clearHny = function() {
  var keys = this.hnyMap.getKeys();
  for(var i=0; i < keys.length; i++) {
    var h = this.hnyMap.get(keys[i]);
    h.remove();
    this.hnyMap.remove(keys[i]);
  } // for
} // OpenAPRS::clearHny

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::organizeHny
 * Clears all aprs stations on the map.
 * Parameters:
 *   none
 * Returns: n/a
 */
OpenAPRS.prototype.organizeHny = function() {
  var la = this.hnyCenter.lat() + 0.06;
  var lo = this.hnyCenter.lng() - 0.2;
  var lo2;

  var keys = this.hnyMap.getKeys();
  var losCount = 0;
  for(var i=0; i < keys.length; i++) {
    var h = this.hnyMap.get(keys[i]);
    if (h.posFound)
      continue;

    if ((losCount % 10) == 0) {
      la -= 0.03;
      lo2 = lo;
    } // if
    else
      lo2 += 0.01;

    var p = new GLatLng(la, lo2);
    h.changePosition(p)
    losCount++;
  } // for
} // OpenAPRS::organizeHny

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::addAprsPositions
 * Adds a new set of future positions to satellite storage.
 * Parameters:
 *   v: Special Vars's type record with satellite information.
 * Returns: n/a
 */
OpenAPRS.prototype.addAprsPositions = function(v) {
  var num_rows = 0;

  if (!v.isName("POS") || !v.isName("NM"))
    return 0;

  // Get the satellite number (record key).
  var k = v.getField("NM");

  if (!this.aprsMap.isKey(k))
    // APRS station wasn't stored in database.
    return 0;

  // Get satellite record.
  var a = this.aprsMap.get(k);

  // clear old positions
  a.clearPositions();

  var parseMe = v.getField("POS").split('\n');
  var plat = 0;
  var plng = 0;
  var redraw = false;

  for (var i=0; i < parseMe.length; i++) {
    var v2 = new Vars();
    v2.Parse(parseMe[i]);

    if (!v2.isName("t"))
      continue;

    var t = parseInt(v2.getField("t"));

    if (a.positions.isKey(t))
      continue;

    redraw = true;
    var lat = parseFloat(v2.getField("l"));
    var lng = parseFloat(v2.getField("g"));
    //lat += plat;
    //lng += plng;

    if (isNaN(lat) || isNaN(lng))       
      continue; 


    var point = new GLatLng(lat, lng);

    a.addPosition(k, t, point, v2);

    plat = lat;
    plng = lng;

    num_rows++;
  } // for

  if (redraw)
    a.plotTrail();

  return num_rows;
} // OpenAPRS::addAprsPositions

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::createAprsMarker
 * Change satellite tracking to num specified.
 * Parameters:
 *   k			: Key to aprs marker.
 *   lat		: Latitude to create marker at.
 *   lng		: Longitude to create marker at.
 *   img		: Image to use for marker.
 *   ov			: Overlay if needed.
 * Returns: n/a
 */
OpenAPRS.prototype.createAprsMarker = function(k, lat, lng, img, ov) {
  var icon;
  var point;
  var marker;
  var me = this;

  // Create our "tiny" marker icon
  var point = new GLatLng(lat, lng);
  var icon = new GIcon();

  icon.image = img;
  icon.shadow = "http://www.openaprs.net/images/icons/shadow.png";
  icon.iconSize = new GSize(22, 22);
  icon.shadowSize = new GSize(22, 20);
  icon.iconAnchor = new GPoint(11, 11);
  icon.infoWindowAnchor = new GPoint(25, 0);

  var opts = new Object();
  opts.icon = icon;
  opts.zIndexProcess = PutOnTop;

  var opts2 = {
    "icon": icon,
    "clickable": true,
    "draggable": false,
    "labelText": ov,
    "labelOffset": new GSize(-4, -8)
  };

  if (ov != undefined)
    var marker = new LabeledMarker(point, opts2);
  else
    var marker = new GMarker(point, opts);

  GEvent.addListener(marker, "click", function() {
    gOpenAPRS.clickAprsMarker(k);
    //marker.openInfoWindowHtml(html);
  });

  GEvent.addListener(marker, "infowindowclose", function() {
    gOpenAPRS.closeAprsMarker(k);
    //marker.openInfoWindowHtml(html);
  });

  return marker;
} // createAprsMarker

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::createHnyMarker
 * Change satellite tracking to num specified.
 * Parameters:
 *   k			: Key to aprs marker.
 *   lat		: Latitude to create marker at.
 *   lng		: Longitude to create marker at.
 *   img		: Image to use for marker.
 *   b			: Balloon html.
 * Returns: n/a
 */
OpenAPRS.prototype.createHnyMarker = function(k, lat, lng, img, b) {
  var icon;
  var point;
  var marker;
  var me = this;

  // Create our "tiny" marker icon
  var point = new GLatLng(lat, lng);
  var icon = new GIcon();

  icon.image = img;
  icon.shadow = "http://www.openaprs.net/images/icons/shadow.png";
  icon.iconSize = new GSize(22, 22);
  icon.shadowSize = new GSize(22, 20);
  icon.iconAnchor = new GPoint(11, 11);
  icon.infoWindowAnchor = new GPoint(25, 0);

  var opts = new Object();
  opts.icon = icon;
  opts.zIndexProcess = PutOnTop;

  var marker = new GMarker(point, opts);

  GEvent.addListener(marker, "click", function() {
    //gOpenAPRS.clickHnyMarker(k);
    marker.openInfoWindowHtml(b);
  });

  GEvent.addListener(marker, "infowindowclose", function() {
    //gOpenAPRS.closeHnyMarker(k);
    //marker.openInfoWindowHtml(b);
  });

  GEvent.addListener(marker, "mouseover", function() {
    var h = gOpenAPRS.getHny(k);

    if (h == undefined)
      return;

    h.showLabel();
  });

  GEvent.addListener(marker, "mouseout", function() {
    var h = gOpenAPRS.getHny(k);

    if (h == undefined)
      return;

    h.hideLabel();
  });

  return marker;
} // createHnyMarker

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::clickAprsMarker
 * Change satellite tracking to num specified.
 * Parameters:
 *   k			: Key to aprs marker.
 * Returns: n/a
 */
OpenAPRS.prototype.clickAprsMarker = function(k) {
  if (!this.aprsMap.isKey(k))
    return;

  var a = this.aprsMap.get(k);

  a.openBalloon();

  return;
} // clickAprsMarker

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::closeAprsMarker
 * Change satellite tracking to num specified.
 * Parameters:
 *   k			: Key to aprs marker.
 * Returns: n/a
 */
OpenAPRS.prototype.closeAprsMarker = function(k) {
  if (!this.aprsMap.isKey(k))
    return;

  var a = this.aprsMap.get(k);

  a.balloonOpen = false;

  return;
} // closeAprsMarker

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::clickHnyMarker
 * Change satellite tracking to num specified.
 * Parameters:
 *   k			: Key to aprs marker.
 * Returns: n/a
 */
OpenAPRS.prototype.clickHnyMarker = function(k) {
  if (!this.hnyMap.isKey(k))
    return;

  var h = this.hnyMap.get(k);

  h.openBalloon();

  return;
} // clickHnyMarker

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::closeHnyMarker
 * Change satellite tracking to num specified.
 * Parameters:
 *   k			: Key to aprs marker.
 * Returns: n/a
 */
OpenAPRS.prototype.closeHnyMarker = function(k) {
  if (!this.hnyMap.isKey(k))
    return;

  var h = this.hnyMap.get(k);

  h.balloonOpen = false;

  return;
} // closeHnyMarker

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::getHny
 * Trys to find an HNY marker.
 * Parameters:
 *   k			: Key to HNY marker.
 * Returns: n/a
 */
OpenAPRS.prototype.getHny = function(k) {
  if (!this.hnyMap.isKey(k))
    return undefined;

  var h = this.hnyMap.get(k);

  return h;
} // getHny

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::parseAprsQueue
 * Processes queue of APRS last positions and positions.
 * Parameters: n/a
 * Returns: n/a      
 */
OpenAPRS.prototype.parseAprsQueue = function() {
  // Parse position buffer
  var tmplastposArray = new Array();
  for(var i=0; i < this.lastposArray.length; i++) {
    if (i < 20) {
      this.addAprs(this.lastposArray[i]);
      this.addAprsPositions(this.lastposArray[i]);
    } // if
    else
      tmplastposArray.push(this.lastposArray[i]);
  } // for

  this.lastposArray = tmplastposArray;

  if (this.lastposArray.length > 0) {
    showWindow('OpenAPRS:Queue');
    changeById('OpenAPRS:Queue:Number', this.lastposArray.length);
  } // if
  else
    hideWindow('OpenAPRS:Queue');

  return;
} // OpenAPRS::parseAprsQueue

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::parseHnyQueue
 * Processes queue of APRS last positions and positions.
 * Parameters: n/a
 * Returns: n/a      
 */
OpenAPRS.prototype.parseHnyQueue = function() {
  var center = map.getCenter();
  var la = center.lat();
  var lo = center.lng();

  // Parse position buffer
  var tmpHnyArray = new Array();
  for(var i=0; i < this.hnyArray.length; i++) {
    if (i < 10)
      this.addHny(this.hnyArray[i]);
    else
      tmpHnyArray.push(this.hnyArray[i]);
  } // for

  this.hnyArray = tmpHnyArray;

  if (this.hnyArray.length > 0) {
    showWindow('OpenAPRS:Queue');
    changeById('OpenAPRS:Queue:Number', this.hnyArray.length);
  } // if
  else
    hideWindow('OpenAPRS:Queue');

  return;
} // OpenAPRS::parseHnyQueue

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::enablePHG
 * Processes queue of APRS last positions and positions.
 * Parameters: n/a
 * Returns: n/a      
 */
OpenAPRS.prototype.enablePHG = function() {
  if (this.isPHG)
    return;

  this.isPHG = true;

  var keys = this.aprsMap.getKeys();
  for(i=0; i < keys.length; i++) {
    var a = this.aprsMap.get(keys[i]);
    a.enablePHG();
  } // for

  return;
} // OpenAPRS::enablePHG

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::disablePHG
 * Processes queue of APRS last positions and positions.
 * Parameters: n/a
 * Returns: n/a      
 */
OpenAPRS.prototype.disablePHG = function() {
  if (!this.isPHG)
    return;

  this.isPHG = false;

  var keys = this.aprsMap.getKeys();
  for(i=0; i < keys.length; i++) {
    var a = this.aprsMap.get(keys[i]);
    a.disablePHG();
  } // for

  return;
} // OpenAPRS::disablePHG

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::checkTrackLinks
 * Processes queue of APRS last positions and positions.
 * Parameters: n/a
 * Returns: n/a      
 */
OpenAPRS.prototype.checkTrackLinks = function() {
  var t = this.track;
  var keys = t.callMap.getKeys();

  for(var i in keys) {
    var k = keys[i];

    if (this.aprsMap.isKey(k)) {
      t.link(k);
      t.star(k);
    } // if
    else {
      t.unlink(k);
      t.unstar(k);
    } // else
  } // for

  return;
} // OpenAPRS::checkTrackLinks

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::snapToAprs
 * Processes queue of APRS last positions and positions.
 * Parameters: n/a
 * Returns: n/a      
 */
OpenAPRS.prototype.snapToAprs = function(k) {
  if (!this.aprsMap.isKey(k))
    return false;

  var a = this.aprsMap.get(k);

  var latlng = a.marker.getLatLng();

  map.setCenter(latlng, 11);

  return true;
} // OpenAPRS::snapToAprs

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::snapToLatLngAndZoom
 * Snaps the map to a lat and lng and zoom.
 * Parameters: n/a
 * Returns: n/a      
 */
OpenAPRS.prototype.snapToLatLngAndZoom = function(la, lo, zm) {
  var latlng = new GLatLng(la, lo);

  map.setCenter(latlng, zm);

  return;
} // OpenAPRS::snapToLatLngAndZoom

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::refresh
 * Refreshes the map based on what mode the map is in.
 * Parameters: n/a
 * Returns: n/a      
 */
OpenAPRS.prototype.refresh = function() {
  if (this.track.isPaused())
    this.getLastpositionsFromServer(map);
  else
    this.getTrackLastpositionsFromServer(map);

  return true;
} // OpenAPRS::refresh

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::clearRefresh
 * Clears all stations off of the map then refreshes.
 * Parameters: n/a
 * Returns: n/a      
 */
OpenAPRS.prototype.clearRefresh = function() {
  this.clear();
  this.refresh();

  return true;
} // OpenAPRS::clearRefresh

/* ----------------------------------------------------------------------------
 * Name: OpenAPRS::expireAprs
 * Expires any aprs stations after a specified timeframe.
 * Parameters:
 *   n		: Number of seconds *10 after expiration.
 * Returns: n/a      
 */
OpenAPRS.prototype.expireAprs = function(n) {
  var sw = new Stopwatch();
  var numRemoved = 0;
  sw.Start();
  // Remove any stations that haven't been updated in a while.
  var keys = this.aprsMap.getKeys();
  for(var i=0; i < keys.length; i++) {
    var a = this.aprsMap.get(keys[i]);
    a.wantToRemove++;
    if (a.wantToRemove >= n) {
      this.removeAprs(keys[i]);
      numRemoved++;
    } // if
  } // for

  if (numRemoved > 0) {
    log('Cleanup: Results '+numRemoved+' stations removed (T:'+sw.Time()+')');
    changeById('mapRESULTS', 'Results '+numRemoved+' removed ('+sw.Time()+' seconds)');
  } // if
} // OpenAPRS::expireAprs
