/** 
 *  TRU3DGlobe Globe Javascript API
 *  Curiosity is bliss.
*/


if (typeof __$ == 'undefined') {
 function __$(id) {
  if (typeof id == 'string') return document.getElementById(id);
  else return id;
 }
}

/**
 * @private
 */
if (Function.prototype.bind == undefined) {
Function.prototype.bind = function() {
  var __method = this, args = $A(arguments), object = args.shift();
  return function() {
    return __method.apply(object, args.concat($A(arguments)));
  }
}
}

if ($A == undefined) {
var $A = Array.from = function(iterable) {
  if (!iterable) return [];
  if (iterable.toArray) {
    return iterable.toArray();
  } else {
    var results = [];
    for (var i = 0, length = iterable.length; i < length; i++)
      results.push(iterable[i]);
    return results;
  }
}
}

if (typeof FE == 'undefined')
 var FE = {};
if (typeof FE._baseURL == 'undefined')
 FE._baseURL = 'http://globe.poly9.com/';
FE.a = true;

/**
 * Loads a remote JavaScript file
 * @param   {String} url URL where the script is located.
 */
FE.include = function(url) {
 document.write('<script type="text/javascript" src="' + url + '"></script>');
}

FE._xyprox = 'http://globe.poly9.com/xyprox/?u=';

FE.Event = {};

/**
 * @class   Dispatcher is a base class that provides basic event handling functionalities.
 */
FE.Event.Dispatcher = function() {
 this._events = {};
}

/**
 * Adds an event listener to an object.
 * @param {String} name Name of the event.
 * @param {Function} closure Function that will be triggered when the event is dispatched.
 */
FE.Event.Dispatcher.prototype.addListener = function(name, closure) {
 this._events[name] = closure;
}

FE.Event.Dispatcher.prototype.removeListener = function(name, closure) {
 delete this._events[name];
}

/**
 * Dispatches an event
 * @param {String} name Name of the event to dispatch.
 */
FE.Event.Dispatcher.prototype.dispatch = function(name) {
 if (this._events[name] != undefined) {
  var args = [this];
  for(var i=0; i<arguments.length; i++) args.push(arguments[i]);
  this._events[name].apply(this, args);
 }
}

/**
 * @private
 */
FE._debug = false;

/**
 * @private
 */
FE._onMapLoad = function(id) {
 try {
  FE._mapInstances[id]._onLoad();
 } catch(e) {
  alert(e);
  throw e;
 }
}

/**
 * @private
 */
FE.getInstance = function(id) {
 return FE._mapInstances[id];
}
/**
 * @private
 */
FE._mapInstances = [];

/**
 * Object that contains parameters to create a map.
 * Supported options are:<br/>
 *  includeBaseLayers (defaults to true)<br/>
 *  backgroundColor (defaults to black)<br/>
 * @see FE.Map
 * @constructor
 */
FE.MapOptions = function() {
    
}

/**
 * The Map class is the central class in the API. Instantiate it to create a globe.<br/>
 * It can only be instantiated once.<br/>
 * See our <a href="http://globe.poly9.com/api/hello_world.php">Hello World example</a>.
 * @class
 * @param container {HTMLElement}   container   DIV in which the map is created.
 * @param options   {FE.MapOption}  options     Optional map parameters. 
 */
FE.Map = function(container, options) {
   this.ready = false; 
   FE.track();
   if (FE._mapInstances.length > 0) {
    throw 'Globe can only be instantiated once.'
    return;
   }
   this._id = Math.floor(Math.random() * 1000);
   options = options || {};
   if (typeof options == 'string') {
    options = {'backgroundColor' : options};
   }
   this._includeBaseLayers = true;
   if (typeof options.includeBaseLayers != 'undefined')
    this._includeBaseLayers = options.includeBaseLayers;
   this.bgColor = FE.Color.parse(options.backgroundColor || 'black');
   FE._mapInstances[this._id] = this;
   this._container = __$(container);
   FE.disableSelection(this._container);
   this.onLoad = null;
   this._controls = [];
   this.iw = new FE.InfoWindow(this);
   this.lm = new FE.LayerManager(this);
   this._camera = new FE.Camera(this);
   FE.Feed._map = this;
}

FE.Map.prototype = new FE.Event.Dispatcher(); 


/**
 * Disables the globe's inertia. Inertia the globe keeps his rotation when you give it a spin.
 */
FE.Map.prototype.disableInertia = function() {
 this._flash.disableMomentum();
}

/**
 * Use this to access the globe's camera.
 * @return {FE.Camera} 
 */
FE.Map.prototype.getCamera = function() {
 return this._camera;
}

/**
 * Disables the terrain visualization. Use this when you need the best performance.
 */
FE.Map.prototype.disableTerrain = function() {
 this._flash.disableTerrain();
}

/**
 * Enables the terrain visualization. Enabled by default.
 */
FE.Map.prototype.enableTerrain = function() {
 this._flash.enableTerrain();
}

/**
 * Converts a pixel screen coordinate into a geographic coordinate.
 * @param {FE.Pixel} pixel  Coordinate relative to the container's top-left.
 * @return {FE.LatLng} 
 */
FE.Map.prototype.pixelToLatLng = function(pixel) {
 return FE.LatLng.fromObj(this._flash.pixelToLatLng(pixel.x, pixel.y));
}

/**
 * Converts a geographic coordinate into a pixel screen coordinate.
 * @param {FE.LatLng} ll  Coordinate to translate.
 * @return {FE.Pixel}  Coordinate relative to the container's top-left.
 */
FE.Map.prototype.latLngToPixel = function(ll) {
 var pix = this._flash.latLngToPixel(ll);
 return new FE.Pixel(pix[0], pix[1]);
}

/**
 * Displays a star background. Disabled by default. Stars are drawn randomly. 
 */
FE.Map.prototype.toggleStars = function() {
 this._flash.toggleStars();
}

/**
 * @private
 */
FE.Map.prototype._getID = function() {
   return this._id;
}

/**
 * @private
 */
FE.Map.prototype.getInfoWindow = function() {
 return this.iw;
}

/**
 * Returns the layer manager object. The layer manager is responsible to determine which layers should be drawn. Use it to add and/or remove layers.
 * @return {FE.LayerManager}
 */
FE.Map.prototype.getLayerManager = function() {
 return this.lm;
}

/**
 * @private
 */
FE.Map.prototype.addControl = function(control) {
 this._flash.addControl(control);
}

/**
 * @private
 */
FE.Map.prototype.removeControl = function(control) {
 this._flash.removeControl(control);
}

/**
 * @private
 */
FE.Map.prototype._getParams = function() {
 return 'instanceID=' + this._getID() + '&bgColor='+this.bgColor+'&initialTexture='+encodeURIComponent('http://www.tru3d.com/company/globe/worldbathy_low.jpg') + '&basePath=' + FE._baseURL;
}

/**
 * Loads the map object. You must call this method in order to see a globe.
 * An onload event will be dispatched once the map is ready to be used.
 */
FE.Map.prototype.load = function() {
   if (!FE._debug) {
   var params = this._getParams();
   var so = new SWFObject(FE._baseURL + "FreeEarth.swf?"+params, "_flash", this._container.offsetWidth-1, this._container.offsetHeight-1, "9.0.28", "#00000");
   so.addParam('name', '_flash');
   so.addParam('id', '_flash');
   so.addParam('allowFullScreen', 'true');
   so.addParam('allowScriptAccess', 'always');
   so.addParam('quality', 'low');
   so.addParam('menu', 'false');
   so.addParam('wmode', 'opaque');
   so.setAttribute('xiRedirectUrl', window.location);
   so.useExpressInstall(FE._baseURL + 'expressinstall.swf');
   var success = so.write(this._container.id);
   if (!success) {
    so.skipDetect = true;
    so.write(this._container.id);
    this._container.innerHTML += '<font color="#ffffff">TRU3DGlobe Globe requires the Adobe Flash Player. <a href="http://www.adobe.com/go/getflash/">Get Flash</a></font>';
   }
   this._flash = __$('_flash');
   } else {
    this._container.innerHTML = FE._mapGenerator(this);
    try {
     this._flash = this._container.getElementsByTagName('embed')[0];
    } catch(e) {
     this._flash = this._container.firstChild;
    }
   }
   FE.fixWheel(this._flash);
   FE._flashContainer = this._container;
   FE._flash = this._flash;
   var self = this;
   setTimeout(function() {
    FE._rightClickTakeover(self._container, self._flash);
   }, 100);
}

/**
 * Returns the DIV in which the map is contained.
 * @return {HTMLElement}
 */
FE.Map.prototype.getContainer = function() {
	return this._container;
}

/**
 * Returns the bounding box of the region viewed by the camera.
 * @return {FE.LatLngBounds}
 */
FE.Map.prototype.getBounds = function() {
 return FE.LatLngBounds.fromObj(this._flash.getBounds());
}

/**
 * Sets the camera's target coordinate.
 * @param {FE.LatLng} ll Coordinate on which center the map. 
 */
FE.Map.prototype.setTargetLatLng = function(ll) {
	this._flash.setTargetLatLng(ll);
}

/**
 * @private
 */
FE.Map.prototype._addOverlay = function(o) {
        if (o._type == 'GeoRSS') {
	 o._map = this;
	 o.load();
	 return;
        }
	o._map = null;
	var pending = null;
	if (o._pending != undefined)
	 pending = o._pending;
	o._pending = null;
   	this._flash.addOverlay(o, null); 
	o._map = this;
	if (pending) {
		o._pending = pending;
		o._flush();
	}
}

/**
 * Adds overlays to the map.
 * @param {FE.Overlay} ... Overlays to be added.
 */
FE.Map.prototype.addOverlay = function() {
 for(var i=0; i<arguments.length; i++)
  this._addOverlay(arguments[i]);
}

/**
 * Removes overlays from the map.
 * @param {FE.Overlay} ... Overlays to be removed.
 */
FE.Map.prototype.removeOverlay = function() {
 for(var i=0; i<arguments.length; i++)
  this._removeOverlay(arguments[i]);
}

/**
 * @private
 */
FE.Map.prototype._removeOverlay = function(o) {
 o._map = null;
 this._flash.removeOverlay(o);
}

/**
 * Pans the camera using a transition effect.
 * @param {FE.LatLng} ll Destination coordinate.
 * @param {Number} time Transition effect duration, in seconds. Defaults to 1.
 * @param {String} transition Transition effect to be used. See <a href="http://www.robertpenner.com/easing/easing_demo.html">Robert Penner's website</a>. Defaults to 'easenone'.
 */
FE.Map.prototype.panTo = function(ll, time, transition) {
   if (isNaN(ll.lat) || isNaN(ll.lng))
	return;
   try {
    this._flash.panTo(ll, time || 1, transition || 'easenone');
   }
   catch(e) {
   }
}

/**
 * Pans and zooms the camera so that a specific bounding box is visible.
 * @param {FE.LatLngBounds} bounds Bounding box to view.
 * @param {Number} time Transition effect duration, in seconds. Defaults to 1.
 * @param {String} transition Transition effect to be used. See <a href="http://www.robertpenner.com/easing/easing_demo.html">Robert Penner's website</a>. Defaults to 'easenone'.
 */
FE.Map.prototype.panToBoundingBox = function(bounds, time, transition) {
 this._flash.panToBoundingBox(bounds, time || 1, transition || 'easenone');
}

/**
 * Stops any panning effect that might be playing.
 */
FE.Map.prototype.stopPanning = function() {
 this._flash.stopPanning();
}

/**
 * Opens an infowindow containing HTML markup.<br/>
 * See our <a href="http://globe.poly9.com/api/infowindow.php">infowindow example</a>.
 * @param {FE.LatLng} at Coordinate where to pin the infowindow.
 * @param {String} html HTML to be included in the infowindow.
 * @param {int} width Width of the infowindow, in pixels. Defaults to 320.
 * @param {int} height Height of the infowindow, in pixels. Defaults to 200.
 * @return {FE.InfoWindow}
 */
FE.Map.prototype.openInfoWindowHtml = function(at, html, width, height) {
   try {
    width = width || 320;
    height = height || 200;
    if (at.lat != undefined && at.lng != undefined)
     this._flash.openInfoWindowHtml(at, html, width, height);
    else
     this._flash.openInfoWindowHtml(at._jsid, html, width, height);
   } catch(e) {
   }
  return this.getInfoWindow();
}

/**
 * Opens an infowindow containing a FLV movie.
 * See our <a href="http://globe.poly9.com/api/flvplayer.php">FLV infowindow example</a>.
 * @param {FE.LatLng} at Coordinate where to pin the infowindow.
 * @param {String} url URL where the FLV is located. Make sure a crossdomain.xml file is available.
 * @param {int} width Width of the infowindow, in pixels. Defaults to 320.
 * @param {int} height Height of the infowindow, in pixels. Defaults to 200.
 * @return {FE.FLVInfoWindow}
 */
FE.Map.prototype.openInfoWindowFlv = function(at, url, width, height) {
 try {
  width = width || 320;
  height = height || 200;
  if (at.lat != undefined && at.lng != undefined)
   this._flash.openInfoWindowFlv(at, url, width, height); 
  else
   this._flash.openInfoWindowFlv(at._jsid, url, width,height);
 } catch(e) {
 }
 return new FE.FLVInfoWindow(this, url);
}

/**
 * Opens an infowindow containing a SWF component.
 * @param {FE.LatLng} at Coordinate where to pin the infowindow.
 * @param {String} url URL where the SWF component is located. Make sure a crossdomain.xml file is available.
 * @param {int} width Width of the infowindow, in pixels. Defaults to 320.
 * @param {int} height Height of the infowindow, in pixels. Defaults to 200.
 * @return {FE.InfoWindow}
 */
FE.Map.prototype.openInfoWindowSwf = function(at, url, width, height, flashvars) {
 try {
  width = width || 320;
  height = height || 200;
  flashvars = flashvars || {};
  if (at.lat != undefined && at.lng != undefined)
   this._flash.openInfoWindowSwf(at, url, width, height, flashvars);
  else 
   this._flash.openInfoWindowSwf(at._jsid, url, width, height, flashvars);
 } catch(e) {
 }
 return this.getInfoWindow();
}

/**
 * Focuses the map object.
 */
FE.Map.prototype.focus = function() {
   this._flash.focus();
}

/**
 * Zooms to a given altitude using a transition effect.
 * @param {Number} altitude Altitude to which zoom, in meters above the mean sea level.
 * @param {Number} time Transition effect duration, in seconds. Defaults to 1.
 * @param {String} transition Transition effect to be used. See <a href="http://www.robertpenner.com/easing/easing_demo.html">Robert Penner's website</a>. Defaults to 'easenone'. 
 */
FE.Map.prototype.zoomTo = function(altitude, time, transition) {
   this._flash.zoomTo(altitude, time || 1, transition || 'easenone');
};

/**
 * @private
 */
FE.Map.prototype.zoomToRolling = function(altitude, time, transition) {
   time = time || 1;
   transition = transition || "easenone";
   this._flash.zoomToRolling(altitude, time, transition);
}

/**
 * Toggles the full screen button.
 */
FE.Map.prototype.toggleFullScreenButton = function() {
 this._flash.toggleFullScreenButton();
}

/**
 * Toggles the information box.
 */
FE.Map.prototype.toggleInfo = function() {
 this._flash.toggleInfo();
}

/**
 * Returns the current camera target coordinate.
 * @return {FE.LatLng} 
 */
FE.Map.prototype.getTargetLatLng = function() {
 return FE.LatLng.fromObj(this._flash.getTargetLatLng());
}

/**
 * Returns the current camera altitude, in meters above the mean sea level.
 * @return {Number}
 */
FE.Map.prototype.getAltitude = function() {
 return this._flash.getAltitude();
}

/**
 * Closes the infowindow.
 */
FE.Map.prototype.closeInfoWindow = function() {
 this._flash.closeInfoWindow();
}

/**
 * Clears all overlays.
 */
FE.Map.prototype.clearOverlays = function() {
 this._flash.clearOverlays();
}

/**
 * Toggles the controls on the map (including the information box, compass and scrollbars).
 */
FE.Map.prototype.toggleDashboard = function() {
 this._flash.toggleDashboard();
}

/**
 * Resizes the map control.
 */
FE.Map.prototype.resize = function(width, height) {
 this._container.style.width = width + "px";
 this._container.style.height = height + "px";
 this._flash.style.width = width + "px";
 this._flash.style.height = height + "px";
 this._flash.resize(width, height);
}

/**
 * @private
 */
FE.Map.prototype.toggleAtmosphere = function() {
 this._flash.toggleAtmosphere();
}

/**
 * @private
 */
FE.Map.prototype._initBaseLayers = function() {
 this.lm.addLayer(new FE.Layer.Base());
 this.lm.addLayer(new FE.Layer.BlueMarble());
 this.lm.addLayer(new FE.Layer.Landsat());
 this.lm.addLayer(new FE.Layer.USGS());
}

function push_ads(container) {

  var ads = document.getElementById('fe_ads');
  if (!ads) return;
 ads.style.display = 'block';
 ads.parentNode.removeChild(ads);
 ads.style.top = container.offsetHeight - 135 + 'px';
 ads.style.left = parseInt(container.left) + 6 + 'px';
 container.appendChild(ads);



}

/**
 * @private
 */
FE.Map.prototype._onLoad = function() {
 if (this._includeBaseLayers)
  this._initBaseLayers();
 this.focus();
 push_ads(this._container);
 this.ready = true;
 if (this.onLoad) this.onLoad();
 this.dispatch('load');
}

/**
 * @private
 */
FE.Map.prototype.setAltitude = FE.Map.prototype.zoomTo;
 
/**
 * Earth's radius
 * @final
 */
FE.Map.RADIUS = 6378135;

/**
 * Pixel coordinate
 */
FE.Pixel = function(x, y) {
 this.x = x;
 this.y = y;
}

FE.Pixel.prototype.toString = function() {
 return '(' + x + ', ' + y + ')';
}

/**
 * Camera class. Do not instantiate this class; use the map's getCamera method to access it.
 * @see FE.Map#getCamera
 */
FE.Camera = function(map) {
 this._map = map;
}

/**
 * Maximum altitude the camera can zoom out.
 */
FE.Camera.prototype.setMaxAltitude = function(alt) {
 this._map._flash.setMaxAltitude(alt);
}


/**
 * Locks the camera altitude
 */

FE.Camera.prototype.lockAltitude = function(alt) {
 this._map._flash.lockAltitude(alt);
}


/**
 * Locks the camera longitude
 */

FE.Camera.prototype.lockLongitude = function(lng) {
 this._map._flash.lockLongitude(lng);
}


/**
 * Locks the camera latitude
 */
FE.Camera.prototype.lockLatitude = function(lat) {
 this._map._flash.lockLatitude(lat);
}

/**
 * Sets the camera inertia, in degrees.
 */
FE.Camera.prototype.setLatInertia = function(v) {
 this._map._flash.setLatInertia(v);
}

/**
 * Sets the camera inertia, in degrees.
 */
FE.Camera.prototype.setLngInertia = function(v) {
 this._map._flash.setLngInertia(v);
}

/**
 * Pans the camera using a transition effect.
 * @see FE.Map#panTo
 */
FE.Camera.prototype.panTo = function(ll, time, transition) {
 this._map._flash.panTo(ll, time, transition);
}

/**
 * Tilts the camera using a transition effect.
 * @param {Number} tilt Angle to which the camera will be tilted, in degrees.
 * @param {Number} time Transition effect duration, in seconds. Defaults to 1.
 * @param {String} transition Transition effect to be used. See <a href="http://www.robertpenner.com/easing/easing_demo.html">Robert Penner's website</a>. Defaults to 'easenone'.
 */
FE.Camera.prototype.tiltTo = function(tilt, time, transition) {
 if (tilt > 0)
  tilt *= -1;
 this._map._flash.tiltTo(tilt, time || 1, transition || 'easenone');
}

/**
 * Returns the current camera tilt angle, in degrees.
 * @return {Number}
 */
FE.Camera.prototype.getTilt = function() {
 return this._map._flash.getTilt();
}

/**
 * Sets the camera tilt.
 * @param {Number} tilt Angle, in degrees.
 */
FE.Camera.prototype.setTilt = function(tilt) {
 if (tilt > 0)
  tilt *= -1;
 this._map._flash.setTilt(tilt);
}

/**
 * Rolls the camera using a transition effect.
 * @param {Number} heading Angle to which the camera will be headed, in degrees.
 * @param {Number} time Transition effect duration, in seconds. Defaults to 1.
 * @param {String} transition Transition effect to be used. See <a href="http://www.robertpenner.com/easing/easing_demo.html">Robert Penner's website</a>. Defaults to 'easenone'.
 */
FE.Camera.prototype.headTo = function(heading, time, transition) {
 this._map._flash.rollTo(heading, time || 1, transition || 'easenone');
}

/**
 * Sets the camera heading.
 * @param {Number} heading Angle to which the camera will be headed, in degrees.
 */
FE.Camera.prototype.setHeading = function(heading) {
 this._map._flash.setHeading(heading);
}

/**
 * Returns the current camera heading, in degrees.
 * @return {Number}
 */
FE.Camera.prototype.getHeading = function() {
 return this._map._flash.getHeading();
}

/**
 * Sets the camera target coordinate.
 * @param {FE.LatLng} ll Coordinate on which center the camera.
 */
FE.Camera.prototype.setTargetLatLng = function(ll) {
 this._map._flash.setTargetLatLng(ll);
}

/**
 * Returns the current camera target.
 * @return {FE.LatLng}
 */
FE.Camera.prototype.getTargetLatLng = function() {
 return this._map._flash.getTargetLatLng();
}

/**
 * Zooms the camera using a transition effect.
 * @see FE.Map#zoomTo
 */
FE.Camera.prototype.zoomTo = function(altitude, time, transition) {
 this._map._flash.zoomTo(altitude, time, transition);
}

/**
 * Returns the current camera altitude, in meters above the mean sea level.
 */
FE.Camera.prototype.getAltitude = function() {
 return this._map._flash.getAlttiude();
}

/**
 * Sets the camera altitude.
 * @param {Number} altitude  Altitude, in meters above the mean sea level.
 */
FE.Camera.prototype.setAltitude = function(altitude) {
 this._map._flash.zoomTo(altitude);
}

/**
 * @private
 */
FE.Extend = function(dst, src) {
 for(var k in src.prototype)
  dst.prototype[k] = src.prototype[k];
}

/**
 * @private
 */
FE._registerPlanet = function(radius, texture) {
 var target = function(container, backgroundColor) {
   if (FE._mapInstances.length > 0) {
    throw 'TRU3DGlobe Globe can only be instantiated once.'
    return;
   }
   this._id = Math.floor(Math.random() * 1000);
   this.bgColor = FE.Color.parse(backgroundColor || 'black');
   FE._mapInstances[this._id] = this;
   this._container = __$(container);
   FE.disableSelection(this._container);
   this.onLoad = null;
   this._controls = [];
   this.iw = new FE.InfoWindow(this);
   this.lm = new FE.LayerManager(this);
   this._camera = new FE.Camera(this);
 }
 FE.Extend(target, FE.Map);
 target.prototype._getParams = function() {
  return "instanceID=" + this._getID() + '&radius=' + radius + '&initialTexture=' + encodeURIComponent(texture);
 }
 target.prototype._initBaseLayers = function() {
  var baseLayer = new FE.Layer.Globe('t0', FE.LayerManager.getEarthBounds(), texture);
  this.lm.addLayer(baseLayer);
 }
 target.RADIUS = radius;
 target.prototype._onLoad = function() {
  this.toggleAtmosphere();
  this._initBaseLayers();
  if (this.onLoad) this.onLoad();
 }
 return target;
}

FE.Moon = FE._registerPlanet(1737400, 'http://s3.amazonaws.com/freeearth/moon.jpg');
FE.Mars = FE._registerPlanet(3397200, 'http://s3.amazonaws.com/freeearth/mars.jpg');
FE.Venus = FE._registerPlanet(6051800, 'http://s3.amazonaws.com/freeearth/venus.jpg');
FE.Sun = FE._registerPlanet(695000000, 'http://s3.amazonaws.com/freeearth/sunmap.jpg');
FE.Mercury = FE._registerPlanet(2439700,'http://s3.amazonaws.com/freeearth/mercurymap.jpg');
FE.Jupiter = FE._registerPlanet(69911000, 'http://s3.amazonaws.com/freeearth/jupitermap.jpg');
FE.Saturn = FE._registerPlanet(60268000, 'http://s3.amazonaws.com/freeearth/saturnmap.jpg');
FE.Uranus = FE._registerPlanet(25559000, 'http://s3.amazonaws.com/freeearth/uranusmap.jpg');
FE.Neptune = FE._registerPlanet(24764000 , 'http://s3.amazonaws.com/freeearth/neptunemap.jpg');
FE.Pluto = FE._registerPlanet(1195000, 'http://s3.amazonaws.com/freeearth/plutomap1k.jpg');
FE.WidgetMap = FE._registerPlanet(FE.Map.RADIUS, 'http://s3.amazonaws.com/freeearth/widgetmap.jpg');

/**
 * Geographic coordinate.
 * @class
 * @constructor
 * @param {Number} lat Latitude
 * @param {Number} lng Longitude
 * @param {Number} elevation Elevation, in meters above the mean sea level. Defaults to 0.
 */
FE.LatLng = function(lat, lng, elevation) {
 this.lat = parseFloat(lat);
 this.lng = parseFloat(lng);
 this.elevation = parseFloat(elevation) || 0;
}

/**
 * Compares two geographic coordinates.
 * @param {FE.LatLng} ll Coordinate to compare.
 * @return {Boolean}
 */
FE.LatLng.prototype.equals = function(ll) {
 return ll.lat ==  this.lat && ll.lng == this.lng && ll.elevation == this.elevation;
}

/**
 * @private
 */
FE.LatLng.prototype.elevate = function(elevation) {
 this.elevation += elevation;
 return this;
}

/**
 * Deep copy of a coordinate object.
 * @return {FE.LatLng}
 */
FE.LatLng.prototype.copy = function() {
 return new FE.LatLng(this.lat, this.lng, this.elevation);
}

/**
 * @private
 */
FE.LatLng.fromObj = function(obj) {
 return new FE.LatLng(obj.lat, obj.lng, obj.elevation);
}

/**
 * Converts a TRU3DGlobe Globe geographic coordinate to a Google Maps geographic coordinate.<br/>
 * Note: The Google Maps script needs to be loaded.
 */
FE.LatLng.prototype.toGoogle = function() {
 return new GLatLng(this.lat, this.lng);
}

FE.LatLng.prototype.toString = function() {
 return '(' + this.lat + ',' + this.lng + ')';
}

/**
 * Parses a geographic coordinate. Format is "lat lng"
 */
FE.LatLng.parse = function(text) {
 var match = /([\d\.\-]+)[,\s]+([\d\.\-]+)/.exec(text);
 if (!match) return null;
 if (match.length != 3) return null;
 var lat = parseFloat(match[1]);
 var lng = parseFloat(match[2]);
 return new FE.LatLng(lat, lng);
}

/**
 * Converts a Google Maps geographic coordinate to a TRU3DGlobe Globe geographic coordinate.
 * Note: The Google Maps script needs to be loaded. 
 */
FE.LatLng.fromGoogle = function(ll) {
 return new FE.LatLng(ll.lat(), ll.lng());
}

/**
 * Defines an icon used for pushpins.
 * @class
 * @constructor
 * @param {String} url URL at which the icon is located.
 * @param {Number} minScale Minimum scale, defaults to 0.
 * @param {Number} maxScale Maximum scale, defaults to 1.
 */
FE.Icon = function(url,minScale, maxScale) {
 this.minScale = minScale || 0;
 this.maxScale = maxScale || 1;
 this.iconAnchor = null;
 this.setImage(url || 'http://www.tru3d.com/company/globe/bullmarker.png');
}


/**
 * @private
 */
FE.Icon.prototype.setImage = function(url) {
 if (url.indexOf('globe.poly9.com') == -1) {
    this.image = FE.Transport.getURL(url);
 }
 else
  this.image = url;
}

/**
 * Adds an event listener to an object.
 * @param {Object} obj Object to which an event listener is added.
 * @param {String} name Event name.
 * @param {Function} closure Function that is executed when the event is dispatched. 
 * @see FE.Event.Dispatcher#addListener
 */
FE.Event.addListener = function(obj, name, closure) {
 obj.addListener(name, closure);
}

FE.Event.removeListener = function(obj, name, closure) {
 obj.removeListener(name, closure);
}

/**
 * Dispatches an event on a specific object.
 * @param {Object} obj Objects on which an event is dispatched.
 * @param {String} name Event name.
 * @see FE.Event.Dispatcher#dispatch
 */
FE.Event.dispatch = function(obj, name) {
 obj.dispatch(name);
}

/**
 * Base class for layers.
 * @class
 */
FE.Layer = function(layerType) {
 this.name = '';
 this.minAltitude = 0;
 this.maxAltitude = 0;
 this.visible = true;
 this._lm = null;
 this._type = layerType;
}

/**
 * @private
 */
FE.Layer.getRandomName = function() {
 return 'layer' + Math.ceil(Math.random() * 1000);
}

FE.Layer.prototype = new FE.Event.Dispatcher();

/**
 * @private
 */
FE.Layer.prototype.isCurrentLayer = function() {
 if (this._lm)
  return this._lm.getActiveLayer() == this;
}

/**
 * TRU3DGlobe Globe base imagery. This is the lowest resolution Blue Marble imagery.
 * @class
 */
FE.Layer.Base = function() {
}

FE.Layer.Base.prototype = new FE.Layer('Base');

/**
 * @private
 */
FE.Layer.Custom = function(name, baseurl, format, startZoom, endZoom, lzs, minAltitude, maxAltitude) {
 this.name = name;
 if (baseurl.substring(baseurl.length-2) != '/')
  baseurl += '/';
 this.baseurl = baseurl;
 this.startZoom = startZoom;
 this.endZoom = endZoom;
 this.lzs = lzs;
 this.format = format;
 this.minAltitude = minAltitude || 0;
 this.maxAltitude = maxAltitude || 30000000;
}

FE.Layer.Custom.prototype = new FE.Layer('Custom');

/**
 * DayNight imagery.
 * @class
 */
FE.Layer.DayNight = function(name) {
 this.name = name  || FE.Layer.getRandomName();
}

FE.Layer.DayNight.prototype = new FE.Layer('DayNight');

/**
 * Defines a tiled WMS layer.
 * @class
 * @constructor
 * @param {String} name Layer name.
 * @param {String} url WMS endpoint
 * @param {Object} params Object defining additional parameters to put in WMS queries. Example: { 'TRANSPARENT' : 'TRUE', 'FORMAT' : 'image/png' }
 * @param {Number} minAltitude Minimum altitude at which the layer is displayed. Optional.
 * @param {Number} maxAltitude Maximum altitude at which the layer is displayed. Optional.
 */
FE.Layer.WMS = function(name, url, layers, params, minAltitude, maxAltitude) {
 this.name = name || FE.Layer.getRandomName();
 this.url = url;
 this.layers = layers;
 this.params = params || {};
 this.minAltitude = minAltitude || 0;
 this.maxAltitude = maxAltitude || 30000000;
}

FE.Layer.WMS.prototype = new FE.Layer('WMS');

/**
 * Non-tiled WMS layer. This is less heavy on the WMS as less requests are made.<br/>
 * The layer can be refreshed manually or after the camera stops (this is the default behavior).<br/>
 * @class
 * @constructor
 * @param {String} name Layer name.
 * @param {String} url WMS endpoint.
 * @param {Object} params Object defining additional parameters to put in WMS queries. Example: { 'TRANSPARENT' : 'TRUE', 'FORMAT' : 'image/png' }
 * @param {Number} minAltitude Minimum altitude at which the layer is displayed. Optional.
 * @param {Number} maxAltitude Maximum altitude at which the layer is displayed. Optional.
 * @param {FE.Layer.RefreshPolicy} Refresh policy to be used. Defaults to FE.Layer.RefreshPolicy.View.
 */
FE.Layer.UntiledWMS = function(name, url, layers, params, minAltitude, maxAltitude, refreshPolicy) {
 this.name = name || FE.Layer.getRandomName();
 this.url = url;
 this.layers = layers;
 this.params = params || {}; 
 this.minAltitude = minAltitude || 0;
 this.maxAltitude = maxAltitude || 30000000;
 this.refreshPolicy = refreshPolicy || new FE.Layer.RefreshPolicy.View(2000);
 FE.Transport.allowDomain('http://freeearth-origin.poly9.com/crossdomain.xml');
}

FE.Layer.UntiledWMS.prototype = new FE.Layer('UntiledWMS');

/**
 * Refreshes the WMS layer.
 */
FE.Layer.UntiledWMS.prototype.refresh = function() {
 this._lm._map._flash.refreshWMS(this.name);
}

/**
 * Defines an untiled WMS layer refresh policy. This is an abstract class, do not instantiate.
 */
FE.Layer.RefreshPolicy = function(_type) {
 this._type = _type;
}

/**
 * Defines a view-based refresh policy.
 * @class
 * @constructor
 * @param {Number} ms Delay in milliseconds to wait, after the camera stops, before refreshing the layer. 
 */
FE.Layer.RefreshPolicy.View = function(ms) {
 this._ms = ms || 2000;
}
FE.Layer.RefreshPolicy.View.prototype = new FE.Layer.RefreshPolicy('View');

/**
 * Defines a manual refresh policy.
 * @see FE.Layer.UntiledWMS#refresh
 */
FE.Layer.RefreshPolicy.Manual = function() {
}
FE.Layer.RefreshPolicy.Manual.prototype = new FE.Layer.RefreshPolicy('Manual');

/**
 * Defines a custom imagery layer.
 * @param {String} name Layer name.
 * @param {FE.LatLngBounds} bounds Layer extent
 * @param {String} url Image location. 
 * @param {Number} minAltitude Minimum altitude at which the layer is displayed. Optional.
 * @param {Number} maxAltitude Maximum altitude at which the layer is displayed. Optional.
 * @param {int} precision Tessellation factor to be used. Defaults to 32. 
 */
FE.Layer.Globe = function(name, bounds, url, minAltitude, maxAltitude, precision) {
 this.name = name || FE.Layer.getRandomName();
 this.minAltitude = minAltitude || 0;
 this.maxAltitude = maxAltitude || 30000000;
 this.url = FE.Transport.getURL(url);
// this.url = FE.Transport._proxyURL + encodeURIComponent(url) + '&h=' + location.host;
 this.bounds = bounds.copy();
 this.precision = precision || 32;
}

FE.Layer.Globe.prototype = new FE.Layer('Globe');

/**
 * Defines a Landsat 7 layer.
 */
FE.Layer.Landsat = function() {
 this.name = 'Landsat';
 this.minAltitude = 20000;
 this.maxAltitude = 700000;
}

FE.Layer.Landsat.prototype = new FE.Layer('Landsat');

/**
 * @private
 */
FE.Layer.USGS = function() {
 this.name = 'USGS';
 this.minAltitude = 0;
 this.maxAltitude = 20000;
}

FE.Layer.USGS.prototype = new FE.Layer('USGS');

/**
 * Defines a Blue Marble layer.
 */
FE.Layer.BlueMarble = function() {
 this.name = 'BlueMarble';
 this.minAltitude = 700000;
 this.maxAltitude = 2000000;
}

FE.Layer.BlueMarble.prototype = new FE.Layer('BlueMarble');

/**
 * The layer manager is responsible to determine which layers are visible. Do not instantiate this class.
 * @see FE.Map#getLayerManager
 */
FE.LayerManager = function(map) {
 this._layers = [];
 this._map = map;
}

FE.LayerManager.prototype = new FE.Event.Dispatcher();

/**
 * @private
 */
FE.LayerManager.prototype._add_test = function() {
 this.addLayer(new FE.Layer('TEST'));
}

/**
 * Returns the earth bounding box.
 * @return {FE.LatLngBounds}
 */
FE.LayerManager.getEarthBounds = function() {
 return new FE.LatLngBounds(new FE.LatLng(90, 180), new FE.LatLng(-90, -180));
}

/**
 * Adds a layer to the layer manager.
 * @param {FE.Layer} layer Layer to be added.
 */
FE.LayerManager.prototype.addLayer = function(layer) {
 layer._lm = null;
 this._layers.push(layer);
 this._map._flash.addLayer(layer);
 layer._lm = this;
}

/**
 * Removes a layer from the layer manager.
 * @param {FE.Layer} layer Layer to be removed.
 */
FE.LayerManager.prototype.removeLayer = function(layer) {
 layer._lm = null;
 var idx = -1;
 for(var i=0; i<this._layers.length; i++) if (this._layers[i] == layer) {
	idx = i;
	break;
 }
 if (idx != -1) {
  this._layers.splice(idx, 1);
  this._map._flash.removeLayer(layer);
 }
}

/**
 * Returns current layers.
 * @return {Array}
 */
FE.LayerManager.prototype.getLayers = function() {
 return this._layers;
}

/**
 * @private
 */
FE.LayerManager.prototype.getActiveLayer = function() {
 return this.getLayerByName(this._map._flash.getActiveLayer());
}

/**
 * Returns a layer object
 * @param {String} name Layer name.
 * @return {FE.Layer}
 */
FE.LayerManager.prototype.getLayerByName = function(name) {
 for(var i=0; i<this._layers.length; i++) if (this._layers[i].name == name)
	return this._layers[i];
 return null;
}

/**
 * Returns the minimum altitude among active layers.
 * @return {Number}
 */
FE.LayerManager.prototype.getMinAltitude = function() {
 var min = 2000000;
 for(var i=0; i<this._layers.length; i++)
  min = Math.min(min, this._layers[i].minAltitude);
 return min;
}

/**
 * Returns the maximum altitude among active layers.
 * @return {Number}
 */
FE.LayerManager.prototype.getMaxAltitude = function() {
 var max = 30000000;
 for(var i=0; i<this._layers.length; i++)
  max = Math.max(max, this._layers[i].maxAltitude);
 return max;
}

/**
 * Removes all layers
 */
FE.LayerManager.prototype.clear = function() {
 for(var i=0; i<this._layers.length; i++)
  this.removeLayer(this._layers[i]);
 this._map._flash.clearLayers();
}

/**
 * Base class for overlays. An overlay is something that can be added on the map. This is an abstract class, do not instantiate.
 */
FE.Overlay = function(_type) {
 this._type = _type;
 this._location = null;
}

FE.Overlay._count = 0;
FE.Overlay._overlays = {};
FE.Overlay.prototype = new FE.Event.Dispatcher();
/*
 * @private
 */
FE.Overlay.prototype.initialize = function() {
 this._jsid = ++FE.Overlay._count;
 this._events = {};
 this._map = null;
 this._visible = true;
 FE.Overlay._overlays[this._jsid] = this;
}

/**
 * Returns the overlay's geographic coordinate.
 * @return {FE.LatLng}
 */
FE.Overlay.prototype.getLocation = function() {
 return FE.LatLng.fromObj(this._map._flash.getLocation(this._jsid));
}

/**
 * Hides an overlay.
 */
FE.Overlay.prototype.hide = function() {
 this._visible = false;
 try {
  this._map._flash.hideOverlay(this._jsid);
 } catch(e) {};
}

/**
 * Returns true if the overlay is visible, false otherwise.
 * @return {Boolean}
 */
FE.Overlay.prototype.isVisible = function() {
 return this._map._flash.overlayIsVisible(this._jsid);
}

/**
 * Shows the overlay,
 */
FE.Overlay.prototype.show = function() {
 this._visible = true;
 try {
  this._map._flash.showOverlay(this._jsid);
 } catch(e) {};
}

/**
 * Have the overlay follow the path defined by a polyline.
 * @param {FE.Polyline} p Polyline defining the path.
 * @param {Number} time Path duration, in milliseconds.
 */
FE.Overlay.prototype.followPolyline = function(p, time) {
 p._getMap();
 this._getMap()._flash.overlayFollowPolyline(this._jsid, p._jsid, time);
}

/**
 * @private
 */
FE.Overlay.prototype._getMap = function() {
 if (!this._map)
  throw "Overlay not added to map";
 return this._map;
}

/**
 * Updates the overlay geographic location.
 * @param {FE.LatLng} ll Coordinate.
 */
FE.Overlay.prototype.updateLocation = function(ll) {
 this._location = ll;
 this._getMap()._flash.updateLatLng(this._jsid, this._location);
}

/**
 * @private
 */
FE.Overlay._getOverlay = function(id) {
 return FE.Overlay._overlays[id];
}

FE.Placemark = function(location, icon, caption, fontName, fontSize, fontColor, alpha, name) {
 this.initialize();
 this._location = location;
 this._icon = icon || new FE.Icon();
 this._caption = caption || '';
 this._fontName = fontName || 'Arial';
 this._fontColor = FE.Color.parse(fontColor || '0xffffff');
 this._fontSize = fontSize || 12;
 this._alpha = alpha || 1;
 this._name = name || '';
}

FE.Placemark.prototype = new FE.Overlay('Placemark');


/**
 * @private
 */
FE.Folder = function() {
 this.initialize();
 this._name = arguments[0];
 this._pending = [];
 for(var i=1; i<arguments.length; i++)
  this.addOverlay(arguments[i]);
}

FE.Folder.prototype = new FE.Overlay('Folder');

FE.Folder.prototype.addOverlay = function(o) {
 this._pending.push(o);
 this._flush();
}

FE.Folder.prototype.getLocation = function() {
 return null;
}

FE.Folder.prototype._flush = function() {
 if (this._map != null) {
  while (this._pending.length > 0) {
   var o = this._pending.pop();
   o._map = null;
   this._map._flash.addChildOverlay(this._jsid, o);
   o._map = this._map;
   if (o._flush != undefined) o._flush();
  }
 }
}

FE.Folder.prototype.removeOverlay = function(o) {
 var idx = -1;
 for(var i=0; i<this._pending.length; i++)
  if (this._pending[i]._jsid == o._jsid) {
   idx = i;
   break;
  }
 if (idx != -1)
  this._pending.splice(idx, 1);
 o._map = null;
 if (this._map != null) {
  this._map._flash.removeChildOverlay(this._jsid, o);
 }
}

/**
 * Defines a SWF component that can be put on a map.
 * @param {FE.LatLng} location Geographic coordinate.
 * @param {String} url URL pointing to the SWF component. Make sure a crossdomain.xml file is available.
 * @param {String} name Object name. Optional.
 */
FE.SWFOverlay = function(location, url, name) {
 this.initialize();
 this._location = location;
 this._url = FE.Transport.getURL(url);
 this._name = name || '';
}

FE.SWFOverlay.prototype = new FE.Overlay('SWFOverlay');

/*
 * @ignore
 */
FE.SWFOverlay.prototype.dispatch = function(name) {
 switch(name) {
  case 'click':
   if (this._events['click'] != undefined)
    this._events['click'](this);
   break;
 }
}

/**
 * Opens an infowindow containing HTML markup.
 * @param {String} html HTML to be displayed in the infowindow.
 * @param {int} width Width of the infowindow. Defaults to 320.
 * @param {int height Height of the infowindow. Defaults to 200.
 * @return {FE.InfoWindow}
 */
FE.SWFOverlay.prototype.openInfoWindowHtml = function(html, width, height) {
 this._getMap()._flash.openInfoWindowHtml(this._jsid, html, width || 320, height || 200);
 return this._getMap().getInfoWindow();
}

/**
 * Opens an infowindow containing a FLV movie.
 * @param {String} url URL where the FLV movie is hosted.
 * @param {int} width Width of the infowindow. Defaults to 320.
 * @param {int height Height of the infowindow. Defaults to 200.
 * @return {FE.FLVInfoWindow}
 */
FE.SWFOverlay.prototype.openInfoWindowFlv = function(url, width, height) {
 width = width || 320;
 height = height || 200;
 this._getMap()._flash.openInfoWindowFlv(this._jsid, url, width, height);
 return new FE.FLVInfoWindow(this._getMap(), url, width, height);
}

/**
 * Opens an infowindow containing a SWF component.
 * @param {String} url URL where the SWF component is hosted.
 * @param {int} width Width of the infowindow. Defaults to 320.
 * @param {int height Height of the infowindow. Defaults to 200.
 * @return {FE.InfoWindow}
 */
FE.SWFOverlay.prototype.openInfoWindowSwf = function(url, width, height, flashvars) {
 width = width || 320;
 height = height || 200;
 flashvars = flashvars || {};
 this._getMap()._flash.openInfoWindowSwf(this._jsid, url, width, height, flashvars);
 return this._getMap().getInfoWindow();
}

/**
 * Closes the info window.
 */
FE.SWFOverlay.prototype.closeInfoWindow = function() {
 this._getMap().closeInfoWindow();
}

/**
 * Defines an icon that can be put on top of a map.
 * @class
 * @param {FE.LatLng} location Initial geographic location.
 * @param {FE.Icon} Icon to display.
 * @param {String} name Overlay name. Optional.
 */
FE.Pushpin = function(location, icon, name) {
 this.initialize();
 this._location = location;
 this._icon = icon || new FE.Icon();
 this._name = name || '';
}

FE.Pushpin.prototype = new FE.Overlay('Pushpin');

/**
 * @ignore
 */
FE.Pushpin.prototype.dispatch = function(name) {
 switch(name) {
  case 'click':
   if (this._events['click'] != undefined)
    this._events['click'](this);
   break;
 }
}

/**
 * Returns the pushpin icon.
 */
FE.Pushpin.prototype.getIcon = function() {
 return this._icon;
}

/**
 * Opens an infowindow containing HTML markup.
 * @param {String} html HTML to be displayed in the infowindow.
 * @param {int} width Width of the infowindow. Defaults to 320.
 * @param {int height Height of the infowindow. Defaults to 200.
 * @return {FE.InfoWindow}
 */
FE.Pushpin.prototype.openInfoWindowHtml = function(html, width, height) {
 this._getMap()._flash.openInfoWindowHtml(this._jsid, html, width || 320, height || 200);
 return this._getMap().getInfoWindow();
}

/**
 * Opens an infowindow containing a FLV movie.
 * @param {String} url URL where the FLV movie is hosted.
 * @param {int} width Width of the infowindow. Defaults to 320.
 * @param {int height Height of the infowindow. Defaults to 200.
 * @return {FE.FLVInfoWindow}
 */
FE.Pushpin.prototype.openInfoWindowFlv = function(url, width, height) {
 width = width || 320;
 height = height || 200;
 this._getMap()._flash.openInfoWindowFlv(this._jsid, url, width, height);
 return new FE.FLVInfoWindow(this._getMap(), url, width, height)
}

/**
 * Opens an infowindow containing a SWF component.
 * @param {String} url URL where the SWF component is hosted.
 * @param {int} width Width of the infowindow. Defaults to 320.
 * @param {int height Height of the infowindow. Defaults to 200.
 * @return {FE.InfoWindow}
 */
FE.Pushpin.prototype.openInfoWindowSwf = function(url, width, height, flashvars) {
 width = width || 320;
 height = height || 200;
 flashvars = flashvars || {};
 this._getMap()._flash.openInfoWindowSwf(this._jsid, url, width, height, flashvars);
 return this._getMap().getInfoWindow();
}

/**
 * Closes the infowindow.
 */
FE.Pushpin.prototype.closeInfoWindow = function() {
 this._getMap().closeInfoWindow();
}

/**
 * InfoWindow class. Do not instantiate this class.
 * @see FE.Map#getInfoWindow
 * @class
 */
FE.InfoWindow = function(map) {
 this._map = map;
 FE.InfoWindow._map = map;
 FE.InfoWindow._init();
}

FE.InfoWindow.prototype = new FE.Event.Dispatcher();

/**
 * Closes the infowindow.
 */
FE.InfoWindow.prototype.close = function() {
 this._map.closeInfoWindow();
}

/**
 * Resizes the infowindow.
 * @param {int} width Width, in pixels.
 * @param {int} height Height, in pixels.
 */
FE.InfoWindow.prototype.resize = function(width, height) {
 FE.InfoWindow._map._flash.resizeInfoWindow(width, height);
 FE.InfoWindow._open(width, height);
}

/**
 * Returns true if the infowindow is visible, false otherwise.
 * @return {Boolean}
 */
FE.InfoWindow.prototype.isVisible = function() {
 return FE.InfoWindow._ih.style.display != 'block';
}

/**
 * Returns the HTML defining the infowindow content.
 * @return {String}
 */
FE.InfoWindow.prototype.getContent = function() {
 return FE.InfoWindow._ih.innerHTML;
}

/**
 * Sets the HTML markup defining the infowindow content.
 * @param {String} content HTML markup 
 */
FE.InfoWindow.prototype.setContent = function(content) {
 if (typeof content == 'object') {
  FE.InfoWindow._ih.innerHTML = '';
  FE.InfoWindow._ih.appendChild(content);
 }
 else
  FE.InfoWindow._ih.innerHTML = content;
}

/**
 * Infowindow used to display a FLV movie. Do not instantiate this class.
 * @see FE.Map#openInfoWindowFlv
 */
FE.FLVInfoWindow = function(map, url) {
    this._map = map;
    this.url = url;
    FE.FLVInfoWindow._info = this;
}

FE.FLVInfoWindow.prototype = new FE.Event.Dispatcher();

/**
 * @private
 */
FE.FLVInfoWindow.prototype.resizeFLV = function(width, height) {
 this._map._flash.resizeFLV(width, height);
}

/**
 * Sets the movie buffering length. Default buffer length is 10 seconds.
 * @param {Number} seconds Buffer length, in seconds.
 */
FE.FLVInfoWindow.prototype.setBufferLength = function(seconds) {
 this._map._flash.setFLVBuffer(seconds);
}

/**
 * Resumes the FLV playback.
 */
FE.FLVInfoWindow.prototype.resumeFLV = function() {
 this._map._flash.resumeFLV();
}

/**
 * Pauses the FLV playback.
 */
FE.FLVInfoWindow.prototype.pauseFLV = function() {
 this._map._flash.pauseFLV();
}

/**
 * Starts the FLV playback.
 */
FE.FLVInfoWindow.prototype.startFLV = function() {
 this._map._flash.startFLV();
}

/**
 * Seeks the FLV to a specific offset.
 * @param {Number} offset Offset, in seconds.
 */
FE.FLVInfoWindow.prototype.seekFLV = function(offset) {
 this._map._flash.seekFLV(offset);
}

/**
 * Returns the position of the playhead, in seconds.
 * @return Number
 */
FE.FLVInfoWindow.prototype.positionFLV = function() {
 return this._map._flash.offsetFLV();
}

/**
 * @private 
 */
FE.InfoWindow.prototype.getContainer = function() {
 return FE.InfoWindow._ih;
}

/**
 * @private
 */
FE.House = function(location, url, width, height, depth) {
 this.initialize();
 this._location = location;
 this._url = url;
 this._width = parseInt(width);
 this._height = parseInt(height);
 this._depth = parseInt(depth);
}

FE.House.prototype = new FE.Overlay('House');

/**
 * @private
 */
FE.House.prototype.openInfoWindowHtml = function(html, width, height) {
 this._getMap()._flash.openInfoWindowHtml(this._jsid, html, width || 320, height || 200);
 return this._getMap().getInfoWindow();
}

/**
 * Exception class
 */
FE.Exception = function(msg) {
 this.message = msg;
}

/**
 * Option class used in the FE.Label constructor.
 * @param {String} fontName Font name, defaults to Arial.
 * @param {Number} fontSize Font size, in pixels. Defaults to 12.
 * @param {String} color Font color, defined as an hexadecimal string ('0xff0000') or plain string ('red')
 * @param {Number} alpha Label opacity, between 0.0 and 1.0. Defaults to 1.0
 */
FE.LabelOpts = function(fontName, fontSize, color, alpha) {
 this.fontName = fontName || 'Arial';
 this.fontSize = fontSize || 12;
 this.setColor(color || "0xffffff");
 this.alpha = alpha || 1;
 this.inert = true;
 this.width = 0;
 this.height = 0;
 this.wordWrap = false;
}

/**
 * Sets a label option (attribute).
 * @param {String} name Attribute name
 * @param {String} value Attribute value
 */
FE.LabelOpts.prototype.setAttribute = function(name, value) {
 if (name == 'fontColor')
  this.setColor(value);
 else
  this[name] = value;
}

/**
 * Sets a label color. Handles string and hexadecimal colors. 
 */
FE.LabelOpts.prototype.setColor = function(color) {
 this.fontColor = FE.Color.parse(color);
}

/**
 * Color helper
 */
FE.Color = function() {
}

/**
 * Parses a color.
 */
FE.Color.parse = function(color) {
 if (color.indexOf('0x') == 0) {
  return parseInt(color.substring(2), 16);
 } else {
  return parseInt(FE.Color._colors[color.toLowerCase()], 16);  
 }
}

FE.Color._colors =  {
        aliceblue: 'f0f8ff',
        antiquewhite: 'faebd7',
        aqua: '00ffff',
        aquamarine: '7fffd4',
        azure: 'f0ffff',
        beige: 'f5f5dc',
        bisque: 'ffe4c4',
        black: '000000',
        blanchedalmond: 'ffebcd',
        blue: '0000ff',
        blueviolet: '8a2be2',
        brown: 'a52a2a',
        burlywood: 'deb887',
        cadetblue: '5f9ea0',
        chartreuse: '7fff00',
        chocolate: 'd2691e',
        coral: 'ff7f50',
        cornflowerblue: '6495ed',
        cornsilk: 'fff8dc',
        crimson: 'dc143c',
        cyan: '00ffff',
        darkblue: '00008b',
        darkcyan: '008b8b',
        darkgoldenrod: 'b8860b',
        darkgray: 'a9a9a9',
        darkgreen: '006400',
        darkkhaki: 'bdb76b',
        darkmagenta: '8b008b',
        darkolivegreen: '556b2f',
        darkorange: 'ff8c00',
        darkorchid: '9932cc',
        darkred: '8b0000',
        darksalmon: 'e9967a',
        darkseagreen: '8fbc8f',
        darkslateblue: '483d8b',
        darkslategray: '2f4f4f',
        darkturquoise: '00ced1',
        darkviolet: '9400d3',
        deeppink: 'ff1493',
        deepskyblue: '00bfff',
        dimgray: '696969',
        dodgerblue: '1e90ff',
        feldspar: 'd19275',
        firebrick: 'b22222',
        floralwhite: 'fffaf0',
        forestgreen: '228b22',
        fuchsia: 'ff00ff',
        gainsboro: 'dcdcdc',
        ghostwhite: 'f8f8ff',
        gold: 'ffd700',
        goldenrod: 'daa520',
        gray: '808080',
        green: '008000',
        greenyellow: 'adff2f',
        honeydew: 'f0fff0',
        hotpink: 'ff69b4',
        indianred : 'cd5c5c',
        indigo : '4b0082',
        ivory: 'fffff0',
        khaki: 'f0e68c',
        lavender: 'e6e6fa',
        lavenderblush: 'fff0f5',
        lawngreen: '7cfc00',
        lemonchiffon: 'fffacd',
        lightblue: 'add8e6',
        lightcoral: 'f08080',
        lightcyan: 'e0ffff',
        lightgoldenrodyellow: 'fafad2',
        lightgrey: 'd3d3d3',
        lightgreen: '90ee90',
        lightpink: 'ffb6c1',
        lightsalmon: 'ffa07a',
        lightseagreen: '20b2aa',
        lightskyblue: '87cefa',
        lightslateblue: '8470ff',
        lightslategray: '778899',
        lightsteelblue: 'b0c4de',
        lightyellow: 'ffffe0',
        lime: '00ff00',
        limegreen: '32cd32',
        linen: 'faf0e6',
        magenta: 'ff00ff',
        maroon: '800000',
        mediumaquamarine: '66cdaa',
        mediumblue: '0000cd',
        mediumorchid: 'ba55d3',
        mediumpurple: '9370d8',
        mediumseagreen: '3cb371',
        mediumslateblue: '7b68ee',
        mediumspringgreen: '00fa9a',
        mediumturquoise: '48d1cc',
        mediumvioletred: 'c71585',
        midnightblue: '191970',
        mintcream: 'f5fffa',
        mistyrose: 'ffe4e1',
        moccasin: 'ffe4b5',
        navajowhite: 'ffdead',
        navy: '000080',
        oldlace: 'fdf5e6',
        olive: '808000',
        olivedrab: '6b8e23',
        orange: 'ffa500',
        orangered: 'ff4500',
        orchid: 'da70d6',
        palegoldenrod: 'eee8aa',
        palegreen: '98fb98',
        paleturquoise: 'afeeee',
        palevioletred: 'd87093',
        papayawhip: 'ffefd5',
        peachpuff: 'ffdab9',
        peru: 'cd853f',
        pink: 'ffc0cb',
        plum: 'dda0dd',
        powderblue: 'b0e0e6',
        purple: '800080',
        red: 'ff0000',
        rosybrown: 'bc8f8f',
        royalblue: '4169e1',
        saddlebrown: '8b4513',
        salmon: 'fa8072',
        sandybrown: 'f4a460',
        seagreen: '2e8b57',
        seashell: 'fff5ee',
        sienna: 'a0522d',
        silver: 'c0c0c0',
        skyblue: '87ceeb',
        slateblue: '6a5acd',
        slategray: '708090',
        snow: 'fffafa',
        springgreen: '00ff7f',
        steelblue: '4682b4',
        tan: 'd2b48c',
        teal: '008080',
        thistle: 'd8bfd8',
        tomato: 'ff6347',
        turquoise: '40e0d0',
        violet: 'ee82ee',
        violetred: 'd02090',
        wheat: 'f5deb3',
        white: 'ffffff',
        whitesmoke: 'f5f5f5',
        yellow: 'ffff00',
        yellowgreen: '9acd32'
};

/**
 * Defines a geographic extent.
 * @param {FE.LatLng} ... Any number of geographic coordinates can be used to initialize the bouding box.
 */
FE.LatLngBounds = function() {
 this.north = NaN;
 this.south = NaN;
 this.east = NaN;
 this.west = NaN;
 for(var i=0; i<arguments.length; i++)
  this.extend(arguments[i]);
}

/**
 * Returns the bounding box center.
 * @return {FE.LatLng}
 */
FE.LatLngBounds.prototype.getCenter = function() {
 return new FE.LatLng((this.north + this.south)*0.5, (this.west + this.east)*0.5);
}

/**
 * Returns the south west corner.
 * @return {FE.LatLng}
 */
FE.LatLngBounds.prototype.getSouthWest = function() {
 return new FE.LatLng(this.south, this.west);
}

/**
 * Returns the north east corner.
 * @return {FE.LatLng}
 */
FE.LatLngBounds.prototype.getNorthEast = function() {
 return new FE.LatLng(this.north, this.east);
}

FE.LatLngBounds.prototype.toString = function() {
 return '(' + this.north + ',' + this.east + ', ' + this.south + ', ' + this.west + ')';
}

/**
 * Deep-copy
 */
FE.LatLngBounds.prototype.copy = function() {
 return new FE.LatLngBounds(this.getNorthEast(), this.getSouthWest());
}

/**
 * @private
 */
FE.LatLngBounds.fromObj = function(obj) {
 return new FE.LatLngBounds(new FE.LatLng(obj.north, obj.east), new FE.LatLng(obj.south, obj.west));
}

/**
 * Extracts a bouding box from overlays
 */
FE.LatLngBounds.fromOverlays = function() {
 var bb = new FE.LatLngBounds();
 var arg = arguments;
 if (arguments.length == 1 && typeof arguments[0].sort == 'function')
  arg = arguments[0];
 for(var i=0; i<arg.length; i++) {
  var ll = arg[i].getLocation();
  if (ll != null)
   bb.extend(ll);
 }
 return bb;
}

/**
 * Converts a TRU3DGlobe Globe bouding box to a Google Maps bouding box.
 */
FE.LatLngBounds.fromGoogle = function(bb) {
 var r = new FE.LatLngBounds();
 r.extend(FE.LatLng.fromGoogle(bb.getSouthWest()));
 r.extend(FE.LatLng.fromGoogle(bb.getNorthEast()));
 return r;
}

/**
 * Extends the bouding box.
 * @param {FE.LatLng} ll Coordinate.
 */
FE.LatLngBounds.prototype.extend = function(ll) {
 if (isNaN(this.north) || isNaN(this.south) || isNaN(this.east) || isNaN(this.west)) {
   this.north = ll.lat;
   this.south = ll.lat;
   this.east = ll.lng;
   this.west = ll.lng;
   return this;
 }
 if (ll.lat > this.north)
  this.north = ll.lat;
 if (ll.lat < this.south) 
  this.south = ll.lat
 if (ll.lng > this.east)
  this.east = ll.lng;
 if (ll.lng < this.west)
  this.west = ll.lng;
 return this;
}

/**
 * Label overlay.
 * @param {FE.LatLng} location Initial location of the label.
 * @param {String} caption Caption
 * @param {FE.LabelOptions} opts Label parameters. Optional.
 */
FE.Label = function(location, caption, opts) {
 this.initialize();
 this._location = location;
 this._caption = caption;
 this._opts = opts || new FE.LabelOpts();
 this._name = caption;
}

FE.Label.prototype = new FE.Overlay('Label');

/**
 * Defines a polyline. Can be used to draw lines on top of the map.<br/>
 * Great-circle lines will automatically be drawn if the distance between supplied points is too large.
 * @param {Array} points FE.LatLng array of coordinates defining a line.
 * @param {String} color Hexadecimal or plain string color.
 * @param {Number} weight Line width.
 * @param {Number} alpha Line opacity, between 0.0 and 1.0. Defaults to 1.0.
 */
FE.Polyline = function(points, color, weight, alpha, name) {
 this.initialize();
 this._points = points || new Array();
 this._color = FE.Color.parse(color || '0xff0000');
 this._weight = weight || 1;
 this._alpha = alpha || 1;
 this._name = name || '';
}

FE.Polyline.prototype = new FE.Overlay('Polyline');

/**
 * @ignore
 */
FE.Polyline.prototype.followPolyline = function() {
}

/**
 * Returns polyline points
 * @return {Array}
 */
FE.Polyline.prototype.getPoints = function() {
 return this._points;
}

/**
 * Converts a Google Maps polyline to a TRU3DGlobe Globe polyline.
 */
FE.Polyline.fromGoogle = function(poly) {
 var pts = [];
 var n = poly.getVertexCount();
 for(var i=0; i<n; i++) {
  pts.push(FE.LatLng.fromGoogle(poly.getVertex(i)));
 }
 return new FE.Polyline(pts);
}

/*FE.Polyline.prototype.simplify = function(tolerance) {
 this._simplify = true;
}*/

/*FE.Shapefile = function(url) {
 this.url = url;
}*/

/**
 * AJAX helper.
 */
FE.getXmlHttp = function() {
		var xmlhttp=false;
		/*@cc_on @*/
		/*@if (@_jscript_version >= 5)
		// JScript gives us Conditional compilation, we can cope with old IE versions.
		// and security blocked creation of the objects.
		 try {
		  xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
		 } catch (e) {
		  try {
		   xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
		  } catch (E) {
		   xmlhttp = false;
		  }
		 }
		@end @*/
		if (!xmlhttp && typeof XMLHttpRequest!='undefined') {
		  xmlhttp = new XMLHttpRequest();
		}
		return xmlhttp;
}

/**
 * Transport abstraction. Can be used to access cross-domain scripts.
 */
FE.Transport =  function() {
  this.a = 'b';
}

FE.Transport._proxyURL = FE._xyprox;

/**
 * Use this if you want to handle the crossdomain.xml handling yourself. This will speed up your mashup.
 */
FE.Transport.disableProxy = function() {
 FE.Transport._proxyURL = '';
}

/**
 * Fetches a crossdomain.xml file.
 * @param {String} url Location of the crossdomain.xml file.
 */
FE.Transport.allowDomain = function(url) {
 for(var key in FE._mapInstances) {
  try {
   FE._mapInstances[key]._flash.getCrossdomain(url);
  } catch(e) {
  }
 }
}

/**
 * @private
 */
FE.Transport.getURL = function(url) {
 if (FE.Transport._proxyURL == '')
  return url;
 else
  return FE.Transport._proxyURL + encodeURIComponent(url) + '&h=' + location.host;
}

/**
 * @private
 */
FE.Transport._getRandomCallback = function() {
  var r = 'raiseCallback';
  for(var i=0; i<10; i++)
   r += (Math.round((Math.random()*9)));
  return r;
}

/**
 * Loads an URL without worrying about the same-domain policy.
 * @param {String} url URL to load
 * @param {Object} params GET parameters.
 * @param {Function} callback Function to call on success.
 */
FE.Transport.loadURL = function(url, params, callback) {
  var domain = '';
  try {
   var p = new TRU3DGlobe.URLParser(url);
   domain = p.getHost();
   if (p.getPort() != '')
    domain += ':' + p.getPort();
  } catch(e) {
   domain = '';
  }
  url = (p.getProtocol() ? p.getProtocol() : 'http') + '://' + domain + p.getPathname();
  var localDomain = document.domain;
  if (location.port && location.port != 80) localDomain += ':' + location.port;
  if (!domain || domain == localDomain) // local request
   FE.Transport._loadURLLocal(url, params, callback);
  else
   FE.Transport._loadURLCD(url, params, callback);
}

/**
 * @private
 */
FE.Transport._loadURLLocal = function(url, params, callback) {
 var request = FE.getXmlHttp();
 request.open('get', url + '?' + params);
 request.onreadystatechange = FE.Transport._localCallback(request, callback);
 request.send(null);
}

/**
 * @private
 */
FE.Transport._localCallback = function(req, callback) {
 return function() {
  if (req.readyState == 4) {
   if (callback != undefined)
    callback.apply(this, [req.responseText]);
  }
 }
}

/**
 * @private
 */
FE.Transport._createCallback = function(callback, tag) {
 return function() {
  if (callback != undefined)
   callback.apply(this, arguments);
  document.body.removeChild(tag);
 };
}

/**
 * @private
 */
FE.Transport._loadURLCD = function(url, params, callback) {
  var s = document.createElement('script');
  var randomCallback = '';
  do {
   randomCallback = FE.Transport._getRandomCallback();
  } while (typeof FE.Transport[randomCallback] != 'undefined');
  FE.Transport[randomCallback] = FE.Transport._createCallback(callback, s);
  var scriptURL = FE.Transport._proxyURL;
  if (typeof params != 'undefined' && params)
   url +=  '?' + params;
  scriptURL += encodeURIComponent(url);
  scriptURL += '&f=FE.Transport' + encodeURIComponent('.' + randomCallback);
  s.src = scriptURL;
  document.body.appendChild(s);
}

/**
 * Defines a GeoRSS feed.
 * @param {String} url Location of the feed.
 * @param {FE.Icon} icon Default icon to be used.
 */
FE.GeoRSS = function(url, icon) {
 this._url = url;
 this._type = 'GeoRSS';
 this._events = {};
 this._overlays = [];

 if (icon == undefined) {
  this.icon = new FE.Icon();
  this.icon.d = true;
 }
 else
  this.icon = icon;
}

FE.GeoRSS.prototype = new FE.Event.Dispatcher();

/**
 * Returns all overlays created by the feed.
 * @return {Array}
 */
FE.GeoRSS.prototype.getOverlays = function() {
 return this._overlays;
}

/**
 * Loads a GeoRSS feed. This is called when the feed is added to the map.
 */
FE.GeoRSS.prototype.load = function() {
 var p = new TRU3DGlobe.URLParser(this._url);
 FE.Transport.loadURL(this._url, p.getQuerystring(), this._processRSS.bind(this));
}

/**
 * Parses the GeoRSS from a string
 * @param {String} str String to parse.
 */
FE.GeoRSS.prototype.loadFromString = function(str) {
 this._importXML(str);
}

/**
 * @private
 */
FE.GeoRSS.prototype._processRSS = function(r) {
 var feed = this._importXML(r);
 var items;
 items = feed.getElementsByTagName('item');
 var i;
 for(i=0; i<items.length; i++)
  this.itemCallback(items[i]);
 items = feed.getElementsByTagName('entry');
 for(i=0; i<items.length; i++)
  this.itemCallback(items[i]);
}

/**
 * @private
 */
FE.GeoRSS.prototype._importXML = function(xml) {
 var doc;
 if (window.ActiveXObject)
  {
  doc=new ActiveXObject("Microsoft.XMLDOM");
  doc.async="false";
  doc.loadXML(xml);
  }
 else
  {
   var parser=new DOMParser();
   doc=parser.parseFromString(xml,"text/xml");
  }
 return doc;
}

/**
 * @private
 */
FE.GeoRSS.prototype._getTag = function(item, name) {
 var node = item.getElementsByTagName(name);
 if (node.length == 0) return '';
 return node[0].firstChild.nodeValue;
}

/**
 * @private
 */
FE.GeoRSS.prototype.itemCallback = function(item) {
 var title = this._getTag(item, 'title');
 var desc = this._getTag(item, 'description') || this._getTag(item, 'content');
 var point = this._getTag(item, 'point');
 if (!point) {
  point = this._getTag(item, 'georss:point');
 }
 if (!point) {
  point = this._getTag(item, 'pos');
  if (!point)
   point = this._getTag(item, 'gml:pos');
 }
 if (!point) {
  var lat = this._getTag(item, 'geo:lat') || this._getTag(item, 'lat');
  var lng = this._getTag(item, 'geo:long') || this._getTag(item, 'long');
  if (lat && lng) {
   point = lat + ',' + lng;
  }
 }
 if (point) {
  this.pointCallback(item, title, desc, point);
  return;
 }
 var poly = item.getElementsByTagName('LineString');
 if (!poly) {
  poly = this._getTag(item, 'gml:LineString');
 } 
 if (poly) {
  poly = poly[0];
  var pts = this._getTag(poly, 'posList');
  if (!pts) pts = this._getTag(poly, 'gml:posList');
  if (pts) {
   pts = pts.split(/\s+/);
   var latlngs = [];
   for(var i=0; i<pts.length; i+=2) if (pts[i] && pts[i+1]) {
    latlngs.push(new FE.LatLng(pts[i], pts[i+1]));
   }
   this.polyCallback(item, title, desc, latlngs);
   return;
  }
 }
}

/**
 * @private
 */
FE.GeoRSS.prototype.polyCallback = function(item, title, desc, latlngs) {
 var poly = new FE.Polyline(latlngs);
 this._map.addOverlay(poly);
 this._overlays.push(poly);
}

/**
 * @private
 */
FE.GeoRSS.prototype.pointCallback = function(item, title, desc, point) {
var thumb1 = item.getElementsByTagName('media:thumbnail'); 
var thumb2 = item.getElementsByTagName('thumbnail'); 
	
var thumb = thumb1;
if(thumb2.length > thumb1.length)
	thumb = thumb2;

 var icon;
 if (thumb.length > 0 && (typeof this.icon.d != 'undefined')) {
  icon = new FE.Icon(thumb[0].getAttribute('url'));
 } else
  icon = this.icon;
 var location = FE.LatLng.parse(point);
 var content = '<b>' + title + '</b><br/>';
 content += desc;
 var pin = new FE.Pushpin(location, icon);
 FE.Event.addListener(pin, 'click', function(){ pin.openInfoWindowHtml(content, 400, 240) } /*this._onPointClick.bind(this, content)*/);
 this._map.addOverlay(pin);
 this._overlays.push(pin);
}

/**
 * @private
 */
FE.GeoRSS.prototype._onPointClick = function(pin, content) {
 pin.openInfoWindowHtml(content, 400, 240);
}

/**
 * @private
 */
FE.Control = function(type) {
 this._type = type;
 FE.Control._controls[type] = this;
}
/**
 * @private
 */
FE.Control.prototype = new FE.Event.Dispatcher();
/**
 * @private
 */
FE.Control._controls = {};

/**
 * @private
 */
FE.Control.Browser = function() {
}
/**
 * @private
 */
FE.Control.Browser.prototype = new FE.Control('Browser');
/**
 * @private
 */
FE.Control.Browser.prototype.dispatch = function(name, overlay) {
 switch(name) {
  case 'click':
   if (this._events['click'] != undefined)
    this._events['click'](overlay);
   break;
 }
}

/**
 * @private
 */
FE.Feed = function(type) {
 this._type = type;
 this.loaded = false;
}

FE.Feed._map = null;
FE.Feed.prototype = new FE.Overlay('Feed');

FE.Feed.prototype.load = function() {
 this.loaded = true;
 FE.Feed._map._flash.loadFeed(this);
}

/**
 * @private
 */
FE.Feed.KML = function(url) {
 this.initialize();
 this.url = url;
}
/**
 * @private
 */
FE.Feed.KML.prototype = new FE.Feed('KML');

/**
 * @private
 */
FE.Feed.Shapefile = function(url, tolerance) {
}
/**
 * @private
 */
FE.Feed.Shapefile.prototype= new FE.Feed('Shapefile');

/**
 * @private
 */
FE.Feed.GeoRSS = function(url) {
}
/**
 * @private
 */
FE.Feed.GeoRSS.prototype = new FE.Feed('GeoRSS');

/**
 * @private
 */
FE.Feed.GPX = function(url) {
}
/**
 * @private
 */
FE.Feed.GPX.prototype = new FE.Feed('GPX');

/**
 * @private
 */
FE.Feed.WFS = function(url) {
}
/**
 * @private
 */
FE.Feed.WFS.prototype = new FE.Feed('WFS');

FE.include(FE._baseURL + 'js/swfobject_source.js');
FE.include(FE._baseURL + 'js/urlparser.js');
FE.include(FE._baseURL + 'js/jscompat.js');
//FE.include(FE._baseURL + 'js/mod_adsense.js');

document.write('<script src="http://www.google-analytics.com/urchin.js" type="text/javascript"></script>');

/**
 * @private
 */
FE.track = function() {
 try {
 if ((window.location + '').indexOf('gmodules') != -1)
  return;
 var prev_uacct = _uacct;
 _uacct = "UA-6252272-5";
 urchinTracker("/api/" + window.location);
 _uacct = prev_uacct;
 } catch(e) {}
}


