/*------------------------------------------------------------------------------------------- 
//
// Copyright © 1990-2007 by Imaginova Canada, Inc., All rights reserved.
//
// Contains:	Starry Night Web Service classes and utility functions
//
// Author:		David Whipps
//
// Date			Initials	Version		Comments
// ----------	---------	----------	---------------------------
// 
// 2007/11/08	DWW			1.1.0		Added to Unfuddle and uses SVN
// 2007/05/10	DWW			1.1.0		Added 'hitbox' tracking
// 2007/03/07	DWW			1.0.0		New for all widgets to share
//
//--------------------------------------------------------------------------------------------
*/


/*
// NOTE
// DON'T edit any of this HitBox stuff!

//<!--WEBSIDESTORY CODE HBX2.0 (Universal)-->
//<!--COPYRIGHT 1997-2005 WEBSIDESTORY,INC. ALL RIGHTS RESERVED. U.S.PATENT No. 6,393,479B1. MORE INFO:http://websidestory.com/privacy-->
var _hbEC = 0;
var _hbE = [];
function _hbEvent(a,b)
{
	b=_hbE[_hbEC++] = {};
	b._N=a;
	b._C=0;
	return b;
}

var hbx=_hbEvent("pv");
hbx.vpc="HBX0200u";
hbx.gn="ehg-space.hitbox.com";

//BEGIN EDITABLE SECTION
//CONFIGURATION VARIABLES
hbx.acct="DM5310155MZA92EN3";//ACCOUNT NUMBER(S)
hbx.pn="PUT+PAGE+NAME+HERE";//PAGE NAME(S)
hbx.mlc="CONTENT+CATEGORY";//MULTI-LEVEL CONTENT CATEGORY
hbx.pndef="title";//DEFAULT PAGE NAME
hbx.ctdef="full";//DEFAULT CONTENT CATEGORY

//OPTIONAL PAGE VARIABLES
//ACTION SETTINGS
hbx.fv="";//FORM VALIDATION MINIMUM ELEMENTS OR SUBMIT FUNCTION NAME
hbx.lt="none";//LINK TRACKING
hbx.dlf="n";//DOWNLOAD FILTER
hbx.dft="n";//DOWNLOAD FILE NAMING
hbx.elf="n";//EXIT LINK FILTER

//SEGMENTS AND FUNNELS
hbx.seg="";//VISITOR SEGMENTATION
hbx.fnl="";//FUNNELS

//CAMPAIGNS
hbx.cmp="";//CAMPAIGN ID
hbx.cmpn="";//CAMPAIGN ID IN QUERY
hbx.dcmp="";//DYNAMIC CAMPAIGN ID
hbx.dcmpn="";//DYNAMIC CAMPAIGN ID IN QUERY
hbx.dcmpe="";//DYNAMIC CAMPAIGN EXPIRATION
hbx.dcmpre="";//DYNAMIC CAMPAIGN RESPONSE EXPIRATION
hbx.hra="";//RESPONSE ATTRIBUTE
hbx.hqsr="";//RESPONSE ATTRIBUTE IN REFERRAL QUERY
hbx.hqsp="";//RESPONSE ATTRIBUTE IN QUERY
hbx.hlt="";//LEAD TRACKING
hbx.hla="";//LEAD ATTRIBUTE
hbx.gp="";//CAMPAIGN GOAL
hbx.gpn="";//CAMPAIGN GOAL IN QUERY
hbx.hcn="";//CONVERSION ATTRIBUTE
hbx.hcv="";//CONVERSION VALUE
hbx.cp="null";//LEGACY CAMPAIGN
hbx.cpd="";//CAMPAIGN DOMAIN

//CUSTOM VARIABLES
hbx.ci="";//CUSTOMER ID
hbx.hc1="";//CUSTOM 1
hbx.hc2="";//CUSTOM 2
hbx.hc3="";//CUSTOM 3
hbx.hc4="";//CUSTOM 4
hbx.hrf="";//CUSTOM REFERRER
hbx.pec="";//ERROR CODES
*/

if (document.body.isHomePage === undefined)
{
	document.body.isHomePage = function() { void(true); };
}

// Create an empty object in the global namespace.
// Below, create an instance of SNWebService assign it to our golbal SNWEB object.
var SNWEB = (function () {
	// PRIVATES
	var kServerURL				=	"http://www.starrynighteducation.com/";
	var kImageServerIP			=	"http://voyager.starrynight.com/";

	var kGregorianCalendar		= 588829;
	var kGregorianJD			= 2299161;
	var kTorontoLat				= 43.6555;
	var kTorontoLong			= -79.4119;
	var kTorontoTimezone		= -0.166667; // days off of GMT
	var kTorontoElevation		= 3.0;
	
	var kDefaultGaze			= {alt: 15.0, az: 180.0, FOV:90.0};
	
	var kApollo11Lat			= 0.67;
	var kApollo11Long			= 23.47;
		
	// more private consts
	var kDefaultLocationName	= "Earth";
	var kDefaultImageWidth	= 256;
	var kDefaultImageHeight	= 256;

	// request field constants
	var kYES				= "Yes";
	var kNO					= "No";
	var kLocationNotFound 	= "Not Found";
	
	var kLatitude		= "Lat";
	var kLatitudeD		= "LatD";
	var kLatitudeM		= "LatM";
	var kLatitudeDir	= "LatRG";
	var kLongitude		= "Lon";
	var kLongitudeD		= "LonD";
	var kLongitudeM		= "LonM";
	var kLongitudeDir	= "LonRG";
	
	var kLocationName	= "LocN"; // this is the planet/body we are located on
	var kElevation		= "Elev";
	
	var kLocMarkersMoonApollo 	= "LocMMA"; //control location markers for apollo sites on the Moon
	var kSurfaceGuides	= "SG";
	
	var kMonth			= "MM";
	var kDay			= "DD";
	var kYear			= "YY";
	var kHours			= "HH";
	var kMinutes		= "Min";
	var kAmPm			= "AmPm";
	var kFOV			= "FOV";
	
	var kPlanetLabels		= "Lab";
	var kStarLabels			= "LabStar";
	
	var kConstellations		= "Cons";
	var kConstBoundaries	= "ConsB";
	var kConstStickFigs		= "ConsS";
	var kConstImages		= "ConsI";
	
	var kHorizon			= "Hor";
	var kEcliptic			= "Ecl";
	var kShowDaylight		= "SD";
	
	var kPostalCode			= "PC";
	var kScenePixelWidth	= "PW";
	var kScenePixelHeight	= "PH";
	var kAltitude			= "Alt";
	var kAzimuth			= "Az";

	var kObjectName			= "ObN";
	var kObjectKind			= "ObK";
					
	var kDST				= "DST";
	var kTimeZone			= "TZ";
	
	var kRequest			= "Req";
	var kGetFullHTML		= "HTML";
	
	var kPlatform			= "Plat";
	var kVersionString		= "Vers";
	
	var kSNFFile			= "SNF";
	
	var kSNFTag				=
								{
									width: 					"Scene_PixelWidth",
									height: 				"Scene_PixelHeight",
									lat: 					"Location_Latitude",
									lon: 					"Location_Longitude",
									dst: 					"Location_UseDST",
									planet: 				"Location_PlanetName",
									elevation: 				"Location_Elevation",
									alt: 					"Scene_Altitude",
									az: 					"Scene_Azimuth",
									fovWidth: 				"Scene_Width",
									fovHeight: 				"Scene_Height",
									showPlanets: 			"Planets_ShowLabels",
									showStars: 				"Stars_ShowLabels",
									showConstLabels: 		"Constellations_ShowLabels",
									showConstBoundaries: 	"Constellations_ShowBoundaries",
									showConstIllustrations: "Constellations_Illustrations",
									showConstStickFigures: 	"Constellations_ShowStickFigures",
									showHorizon: 			"Horizon_Visible",
									showEcliptic: 			"EclipticGuides_ValuesExist",
									showEclipticEquator: 	"EclipticGuides_Equator",
									showDaylight: 			"Background_ShowDaylight",
									showSurfaceGuides: 		"Planets_ShowSurfaceGuides",
									julianDay: 				"Time_JulianDayLocal",
									julianDayUTC:			"Time_JulianDay"
								};

	var snfFile_Header		= "<HTML><BODY>";
	var snfFile_Footer		= "</BODY></HTML>";
	
	var snTag_Prefix		= "<SN_VALUE name=\"";
	var snTag_Middle		= "\" value=\"";
	var snTag_Suffix		= "\">\n";
	
	var snTag_HorizonFontSize		= "Horizon_FontSize";

	var snTag_ConstFontSize			= "Constellations_FontSize";

	var snTag_PlanetsFontSize		= "Planets_FontSize";

	var snTag_StarsFontSize			= "Stars_FontSize";

	var	 kRSSLocationXMLPlatformStr		= "platform";
	var	 kRSSLocationXMLPlatConstStr	= "platformConst";
	var	 kRSSLocationXMLPlatVersionStr	= "vers";
	var	 kDefaultVersionStr				= "default";
	var	 kRSSLocationXMLURLStr			= "url";

	//----------------------------------------------------------------------
	//	Class:	SNWebService
	//
	//	Purpose:	Main class for interacting with SN Server
	//
	//	Date			Initials	Version		Comments
	//  ----------	---------	----------	---------------------------
	//	2007/11/12		DWW			2.0.0		New
	//
	//----------------------------------------------------------------------
	var SNWebService = Class.create(
	{
		WebServiceVersion: '2.0.0',
		REQUIRED_PROTOTYPE: '1.6.0',
		initialize: function()
		{
			function convertVersionString(versionString)
			{
				var r = versionString.split('.');
				return parseInt(r[0], 10)*100000 + parseInt(r[1], 10)*1000 + parseInt(r[2], 10);
			}
			
			if(	(typeof Prototype=='undefined') || 
				(typeof Element == 'undefined') || 
				(typeof Element.Methods=='undefined') ||
				(convertVersionString(Prototype.Version) < convertVersionString(this.REQUIRED_PROTOTYPE)))
			{
				throw("Starry Night WebService requires the Prototype JavaScript framework >= " + this.REQUIRED_PROTOTYPE);
			}

			this.platform	= this.consts.kUnknownPlatform; // each user should set these for their case!
			this.version	= this.consts.kUnknownVersionString;  // each user should set these for their case!
			this.utils		= new Utilities();	
		},
		// PUBLIC constants
		consts:
		{
			// request constants
			kSearchPostalCodeRequest	: 1,
			kGenerateImageRequest		: 2,
			kGenerateImageHTMLAndTile	: 3,
			kGenerateImageAndMapRequest	: 4,
			kGetNearestCityRequest		: 5,
			kGenerateImageHTML			: 6,
			kGetObjectInfoRequest		: 7,
			
			// platform constants
			kUnknownPlatform			: 0,
			kWebApplication				: 1,
			kDashboardWidget			: 2,
			kGoogleGadget				: 3,
			kYahooWidget				: 4,
			kBlackBerryApp				: 5,
			kVistaGadget				: 6,
			kMEW_Dashboard				: 7,
			kMEW_Yahoo					: 8,
			kBSW_Dashboard				: 9,
			kBSW_Google					: 10,
			kBSW_Facebook				: 11,
			kAppleMobile				: 12,
			
			// version constants
			kUnknownVersionString		: "0.0.0",
			
			// RSS feed constants
			kGetFullRSSFeedSummaries		: false, // This should probably stay as FALSE always.
			kDefaultWidgetRSSFeedURL		: "http://feeds.feedburner.com/StarryNightOnlineTicker",
			kDynamicRSSFeedLocationFileURL	: "http://www.starrynight.com/widget/rss/feedlocations.xml",
			kNumberOfRSSFeedEntriesToUse	: 10, // A reasonable number of feed entries
			
			// Object Info XML names
			kInfo_Constellation		: "Constellation name",
	
			// Planet Location Constants
			kName_Sun		: "Sun",
			kName_Mercury	: "Mercury",
			kName_Venus		: "Venus",
			kName_Earth		: "Earth",
			 kName_Moon			: "Moon",
			kName_Mars		: "Mars",
			 kName_Phobos 		: "Phobos",
			 kName_Deimos 		: "Deimos",
			kName_Jupiter	: "Jupiter",
			 kName_Io			: "Io",
			 kName_Europa		: "Europa",
			 kName_Ganymede		: "Ganymede",
			 kName_Callisto		: "Callisto",
			 kName_Amalthea		: "Almathea",
			 kName_Himalia		: "Himalia",
			 kName_Elara		: "Elara",
			kName_Saturn	: "Saturn",
			 kName_Mimas		: "Mimas",
			 kName_Enceladus	: "Enceladus",
			 kName_Tethys		: "Tethys",
			 kName_Dione		: "Dione",
			 kName_Rhea			: "Rhea",
			 kName_Titan		: "Titan",
			 kName_Hyperion		: "Hyperion",
			 kName_Iapetus		: "Iapetus",
			 kName_Phoebe		: "Phoebe",
			 kName_Janus		: "Janus",
			kName_Uranus	: "Uranus",
			 kName_Ariel		: "Ariel",
			 kName_Umbriel		: "Umbriel",
			 kName_Titania		: "Titania",
			 kName_Oberon		: "Oberon",
			 kName_Miranda		: "Miranna",
			 kName_Neptune		: "Neptune",
			 kName_Triton		: "Triton",
			 kName_Nereid		: "Nereid",
			kName_Pluto		: "Pluto",
			kName_Charon	: "Charon",
			kName_Ceres		: "Ceres",
			kName_Eris		: "Eris",
			kName_SpaceStation	: "ISS",
			kName_HubbleScope	: "HST",
			kName_ChandraScope	: "CXO",
			
			// Object Type Constants
			// using these with a kGetObjectInfoRequest will greatly speed up the response
			// do not modify these as they are in synch with those defined in StarryNightPlugins.h
			kKind_AnyType						: 'xxxx',
		
			kKind_StarGrouping					: 'Cxxx',
			kKind_Asterisim						: 'CAxx',
			kKind_Constellation					: 'CCxx',
			kKind_SmallStarGrouping				: 'CSxx',
		
			kKind_SolarSystemObject				: 'sxxx',
			kKind_Sun							: 'sSxx',
			kKind_Planet						: 'sPxx',
			kKind_Comet							: 'sCxx',
			kKind_Asteroid						: 'sAxx',
			kKind_MoonOfPlanet					: 'sMxx',
			kKind_MeteorShower					: 'ssxx',
			kKind_Planetoid						: 'sOxx',
			kKind_DwarfPlanet					: 'sDxx',
		
			kKind_Star							: 'Sxxx',
			kKind_MultipleStar					: 'SMxx',
			kKind_DoubleStar					: 'S2xx',
			kKind_TripleStar					: 'S3xx',
			kKind_VariableStar					: 'SxxV',
			kKind_VariableAndMultiple			: 'SMxV',
			kKind_StarWtihExtrasolarPlanet		: 'SxEV',
		
			kKind_Galaxy						: 'Gxxx',
			kKind_GalaxyCluster					: 'GCxx',
			kKind_EllipticalGalaxy				: 'GExx',
			kKind_EllipticalEMinusGalaxy		: 'GEe-',
			kKind_EllipticalE0Galaxy			: 'GEex',
			kKind_EllipticalEPlusGalaxy			: 'GEe+',
			kKind_EllipticalS0MinusGalaxy		: 'GES-',
			kKind_LenticularS0Galaxy			: 'GLSx',
			kKind_LenticularS0PlusGalaxy		: 'GLS+',
			kKind_TransitionS0					: 'GTSx',
			kKind_SpiralGalaxy					: 'GSxx',
			kKind_SpiralSaGalaxy				: 'GSSa',
			kKind_SpiralSabGalaxy				: 'GSSA',
			kKind_SpiralSbGalaxy				: 'GSSb',
			kKind_SpiralSbcGalaxy				: 'GSSB',
			kKind_SpiralScGalaxy				: 'GSSc',
			kKind_SpiralScdGalaxy				: 'GSSC',
			kKind_SpiralSdGalaxy				: 'GSSd',
			kKind_IregularGalaxy				: 'GIxx',
			kKind_IregularSpiralSdmGalaxy		: 'GISd',
			kKind_IregularSpiralSmGalaxy		: 'GISm',
			kKind_IregularImGalaxy				: 'GImx',
			kKind_IregularI0Galaxy				: 'GI0x',
		
			kKind_Nebula						: 'Nxxx',
			kKind_DiffuseNebula					: 'NDxx',
			kKind_DarkNebula					: 'Ndxx',
			kKind_PlanetaryNebula				: 'NPxx',
			kKind_ReflectionNebula				: 'NRxx',
		
			kKind_ClusterOfStars				: 'Kxxx',
			kKind_GlobularCluster				: 'KGxx',
			kKind_OpenCluster					: 'KOxx',
			kKind_ClusterWithNebulosity			: 'KNxx',
		
			kKind_Other							: 'Oxxx',
			kKind_Globules						: 'OGxx',
			kKind_H2Region						: 'OHxx',
			kKind_Quasar						: 'OQxx',
			kKind_RadioSource					: 'ORxx',
			kKind_SupernovaRemnant				: 'ONxx',
			kKind_BlackHole						: 'OBxx',
		
			kKind_Nonexistant					: 'Xxxx',
		
			kKind_ManmadeObject					: 'Mxxx',
			kKind_Satellite						: 'MSxx',
			kKind_GeosyncSatellite				: 'MSGx',
			kKind_AstroObsSatellite				: 'MSAx',
			kKind_Probe							: 'MPxx',
			kKind_MannedSpacecraft				: 'MMxx',	
	
			// Tracking Action Contants 
			kTrack_UnspecifiedError		: 0, // use this if you get some unknown error, and pass in any error info in the 'data'
			kTrack_Initialized			: 1,
			kTrack_ImageRequest			: 2,
			kTrack_NearestCityRequest	: 3,
			kTrack_ZipPostalRequest		: 4,
			kTrack_tickerRSSRequest		: 5,
			kTrack_tickerLinkClicked	: 6, // make sure to pass in the name of the clicked link as the'data'
			kTrack_openWebAppClicked	: 7, // make sure to pass in the URL of the clicked link as the'data'
			kTrack_marketingLinkClicked	: 8, // make sure to pass in the URL of the clicked link as the'data'
			kTrack_adDisplayed			: 9, // make sure to pass in the URL of the advertisement link as the'data'
			
			// public default values
			
			// Constants for default location
			kDefaultLatitude				: kTorontoLat,
			kDefaultLongitude				: kTorontoLong,
			kDefaultTimezone				: kTorontoTimezone, // NOTE! If this timezone doesn't properly match the lat long; you'll get strange images until you fix it!
			
			kDefaultElevation	: kTorontoElevation,
			
			kDefaultAltitude	: kDefaultGaze.alt,
			kDefaultAzimuth		: kDefaultGaze.az,
			kDefaultFOV			: kDefaultGaze.FOV,
			kDefaultAboveFOV	: 45.0,
			
			// Constants for default moon location
			kDefaultMoonLatitude			: kApollo11Lat,
			kDefaultMoonLongitude			: kApollo11Long,
			
			// Server urls
			kServerCGI			:	kServerURL + "skychart/snclient.php",
			kRedirectPHP		:	"http://www.starrynight.com/widget/snredirect.php?",
			kServerImages		:	kImageServerIP + "snserverimages/"
		},
		//----------------------------------------------------------------------
		//	Object:	getRequestURL
		//
		//	Purpose:	Returns a properly formatted url based on passed arguements
		//
		//	Date		Initials	Version		Comments
		//  ----------	---------	----------	---------------------------
		//	2007/03/18		DWW			1.0.0		New
		//
		//----------------------------------------------------------------------
		getRequestURL: function (inParams) 
		{
			var platform= this.platform	|| this.consts.kUnknownPlatform;
			var version	= this.version	|| this.consts.kUnknownVersionString;
			
			// optional parameters
			var type		= (inParams && inParams.type)		 || this.consts.kGenerateImageRequest;
			var getFullHTML = ((inParams && inParams.getFullHTML) === undefined) ? true : inParams.getFullHTML;
			var width		= (inParams && inParams.width)		 || kDefaultImageWidth;
			var height		= (inParams && inParams.height)		 || kDefaultImageHeight;
			var postal		= (inParams && inParams.postal)		 || "";
			var objectName	= (inParams && inParams.objectName)	 || this.consts.kName_Sun;
			var objectKind	= (inParams && inParams.objectKind)	 || this.consts.kKind_Planet;
		
			// small performace boost here for zipcode lookups... don't create a situation
			var situ = null;
			if (type !== this.consts.kSearchPostalCodeRequest)
			{
				situ = inParams.situ || new SNSituation(); // if inSituation is null, we create one here 
			}

			var getSNF 		= (inParams && (inParams.getSNF !== undefined)) ? inParams.getSNF : situ.sendSNF;
			
			// create the request
			var requestURL = {
								url: this.consts.kServerCGI,
							    parameters: {}
							 }		

			requestURL.parameters[kRequest] = type;	// Request Type
			
			if (getSNF)
			{
				requestURL.parameters[kSNFFile] = "";
			}	
			
			switch (type)
			{
				case this.consts.kGetNearestCityRequest:
					situ.location.getParamsString(requestURL.parameters, false, false); // Need the lat/long. Don't pass the timezone with this request
					break;
				case this.consts.kSearchPostalCodeRequest:
					requestURL.parameters[kPostalCode] = postal; // zip / postal code
					break;
				case this.consts.kGetObjectInfoRequest:					
					requestURL.parameters[kObjectName] = objectName; // object name
					requestURL.parameters[kObjectKind] = objectKind; // object type

					situ.getParamsString(requestURL.parameters, getSNF); // Situation params
					break;
				default:
					requestURL.parameters[kGetFullHTML] = (getFullHTML ? "1" : "0"); // should returned value be in full HTML (ie. a standalone HTML document)
					
					requestURL.parameters[kScenePixelWidth]	= width; // Image Width and Height
					requestURL.parameters[kScenePixelHeight] = height;	// Image Width and Height

					situ.getParamsString(requestURL.parameters, getSNF); // Situation params						
					
					break;
			}
			
			requestURL.parameters[kPlatform] = platform; // platform constant
			requestURL.parameters[kVersionString] = version; // version string
		
			return requestURL;
		},
		// OBSOLETE?  getParamsString used to return a string but now doesn't. We have to assemble everything again.
		//----------------------------------------------------------------------
		//	Object:	getRedirectURL
		//
		//	Purpose:	Returns a properly formatted url based on passed arguements
		//				URL contains all necessary arguements to match the web app
		//				to the passed situation
		//
		//	Date			Initials	Version		Comments
		//  ----------	---------	----------	---------------------------
		//	2007/03/28		DWW			1.0.0			New
		//
		//----------------------------------------------------------------------
		getRedirectURL: function (inParams) 
		{
			// If no values are passed, use defaults
			var platform= this.platform	|| this.consts.kUnknownPlatform;
			var version	= this.version	|| this.consts.kUnknownVersionString;
			var situ	= (inParams && inParams.situ) || new SNSituation(); // if inSituation is null, we create one here 
			var parameters = {};
		
			// create the URL
			var url = this.consts.kRedirectPHP;
			
			var savedUseUTC = situ.useTimezoneAndUTC;
			situ.useTimezoneAndUTC = false;
			
			var completeRequestURL = url+"?";
			situ.getParamsString(parameters, false);
			
			for (var key in parameters)
			{
				completeRequestURL += key + '=' + parameters[key] + "&";
			}
			
			situ.useTimezoneAndUTC = savedUseUTC;
			
			completeRequestURL += kPlatform + platform + "&"; // platform constant
			completeRequestURL += kVersionString + version; // version string
			
			return url;
		},
		//----------------------------------------------------------------------
		//	Object:	getWidgetRSSFeedURL
		//
		//	Purpose:	Start an async request that will returns a properly formatted url based on passed arguements
		//				URL points to the appropriate RSS feed to be used in our widgets.
		//
		//	Date			Initials	Version		Comments
		//  ----------	---------	----------	---------------------------
		//	2007/04/10		IPA			1.0.0			New
		//
		//----------------------------------------------------------------------
		getWidgetRSSFeedURL: function (inCallback)
		{
			var widgetRSSFeedRequest;
			
			if (window.XMLHttpRequest)
			{
				widgetRSSFeedRequest = new XMLHttpRequest();
			}
			else if (window.ActiveXObject)
			{
				widgetRSSFeedRequest = new ActiveXObject("Microsoft.XMLHTTP");
			}
			else
			{
				return;
			}		
			widgetRSSFeedRequest.onreadystatechange = inCallback;
			widgetRSSFeedRequest.open('GET', this.consts.kDynamicRSSFeedLocationFileURL, true);
			if (widgetRSSFeedRequest.overrideMimeType)
			{
				widgetRSSFeedRequest.overrideMimeType('text/xml');
			}
			widgetRSSFeedRequest.send(null);
			
			return widgetRSSFeedRequest;
		},
		//----------------------------------------------------------------------
		//	Object:	getInfoAtomFromResponse
		//
		//	Purpose:	Takes in a response string from a getObjectInfo request and returns the value
		//				of the passed tag.
		//
		//	Date		Initials	Version		Comments
		//  ----------	---------	----------	---------------------------
		//	2007/10/09	DWW			1.1.0		New for GetObjectInfo
		//
		//----------------------------------------------------------------------
		getInfoAtomFromResponse: function (inResponseXML, inRequestedInfoAtom)
		{
			if ((inResponseXML === NULL) || (inResponseXML.length === 0)) {
				return null;
			}
	
			if ((inRequestedInfoAtom === NULL) || (inRequestedInfoAtom.length === 0)) {
				return null;
			}
			
			var atomsArray = inResponseXML.getElementsByTagName(inRequestedInfoAtom);
			if (atomsArray[0] !== null) {
				return atomsArray[0].textContent;
			}
			else {
				return null;
			}
		},
		//----------------------------------------------------------------------
		//	Object:	parseRSSXML
		//
		//	Purpose:	parse the returned XML to get the URL that
		//				points to the appropriate RSS feed to be used in our widgets.
		//
		//	Date			Initials	Version		Comments
		//  ----------	---------	----------	---------------------------
		//	2007/04/10		IPA			1.0.0			New
		//
		//----------------------------------------------------------------------
		parseRSSXML: function (inResponseXML)
		{
			var platform= this.platform	|| this.consts.kUnknownPlatform;
			var version	= this.version	|| this.consts.kUnknownVersionString;
	
			var url = this.consts.kDefaultWidgetRSSFeedURL; // default this to something in case there is a failure with the get.
		
			var xmldoc = inResponseXML;
			var platformArray = xmldoc.getElementsByTagName(kRSSLocationXMLPlatformStr);
			var bFoundNewVersion = false;
			
			var versionsArray;
			var typeConsts;
			var typeConst;
			var urls;
		
			var i, j;
			
			for (i = 0; i < platformArray.length; i +=1 )
			{ 
				typeConsts = platformArray[i].getElementsByTagName(kRSSLocationXMLPlatConstStr);
				typeConst = typeConsts[0]; // there should be only one.
				if (typeConst.firstChild.data == platform)
				{
					versionsArray = platformArray[i].getElementsByTagName(kRSSLocationXMLPlatVersionStr); // grab the versions from the XML
					for (j = 0; j < versionsArray.length; j +=1)
					{
						if (versionsArray[j].getAttribute("value") == kDefaultVersionStr)
						{
							if (!bFoundNewVersion)
							{
								urls = versionsArray[j].getElementsByTagName(kRSSLocationXMLURLStr); // grab the URL from the XML
								url = urls[0].firstChild.data;
							}
						}
						else if (versionsArray[j].getAttribute("value") > version)
						{
							urls = versionsArray[j].getElementsByTagName(kRSSLocationXMLURLStr); // grab the URL from the XML
							url = urls[0].firstChild.data;
							bFoundNewVersion = true;
						}
					}
				}
			}		
		
			return url;
		}
		//----------------------------------------------------------------------
		//	Object:	trackAction
		//
		//	Purpose:	Sends a request to the current tracking site (HitBox) for the passed event
		//
		//	Date			Initials	Version		Comments
		//  ----------	---------	----------	---------------------------
		//	2007/05/09		DWW			1.0.1		New
		//
		//----------------------------------------------------------------------
/*		trackAction: function (inParams) 
		{
			var platform= SNWEB.platform	|| this.consts.kUnknownPlatform;
			var version	= SNWEB.version		|| this.consts.kUnknownVersionString;
			var action	= (inParams && inParams.action)		|| this.consts.kTrack_UnspecifiedError;
			var isError	= (inParams && inParams.isError)	|| false;
			var data	= (inParams && inParams.data)		|| "";
			
			var kUnknownPlatformFolder			= "unknown";
			var kWebApplicationFolder			= "snweb";
			var kDashboardWidgetFolder			= "dashboard";
			var kGoogleGadgetFolder				= "google";
			var kYahooWidgetFolder				= "yahoo";
			var kBlackBerryAppFolder			= "blackberry";
			var kVistaGadgetFolder				= "vista";
			var kMEWDashboardFolder				= "moonDashboard";
			var kMEWYahooFolder					= "moonYahoo";
			var kBSWDashboardFolder				= "birthdayDashboard";
			var kBSWGoogleFolder				= "birthdayGoogle";
			var kBSWFacebookFolder				= "birthdayFacebook";
			var kAppleMobileFolder				= "appleMobile";
		
			var kTrack_ERRORS					= "ERROR";
			var kTrack_InitializedS				= "initialized";
			var kTrack_NearestCityRequestS		= "nearestCityRequest";
			var kTrack_ImageRequestS			= "imageRequest";
			var kTrack_ZipPostalRequestS		= "zipPostalRequest";
			var kTrack_tickerRSSRequestS		= "tickerRSSRequest";
			var kTrack_tickerLinkClickedS		= "tickerLink"; // make sure to pass in the name of the clicked link as the'data'
			var kTrack_openWebAppClickedS		= "openWebApp"; // make sure to pass in the URL of the clicked link as the'data'
			var kTrack_marketingLinkClickedS	= "marketingLink"; // make sure to pass in the URL of the clicked link as the'data'
			var kTrack_adDisplayedS				= "adDisplayed"; // make sure to pass in the URL of the advertisement (if possible) link as the'data'
		
			var contentCategoryName		= "/applets/";
			var pageName = (version.length > 0) ? version : this.consts.kUnknownVersionString; // default this to the version number
			
			var kSkyChartAppletName		= "/skyChart/";
			var kMoonExplorerAppletName	= "/moonExplorer/";
			var kBirthdaySkyAppletName	= "/birthday/";
			
			// Add the request
			switch (action)
			{
				case this.consts.kTrack_Initialized:
					contentCategoryName += kTrack_InitializedS;
					break;
				case this.consts.kTrack_ImageRequest:
					contentCategoryName += kTrack_ImageRequestS;
					break;
				case this.consts.kTrack_NearestCityRequest:
					contentCategoryName += kTrack_NearestCityRequestS;
					break;
				case this.consts.kTrack_ZipPostalRequest:
					contentCategoryName += kTrack_ZipPostalRequestS;
					break;
				case this.consts.kTrack_tickerRSSRequest:
					contentCategoryName += kTrack_tickerRSSRequestS;
					break;
				case this.consts.kTrack_openWebAppClicked:
					contentCategoryName += kTrack_openWebAppClickedS;
					break;
				case this.consts.kTrack_UnspecifiedError:
					contentCategoryName += kTrack_ERRORS;
					break;
				case this.consts.kTrack_adDisplayed:
					contentCategoryName += kTrack_adDisplayedS;
					break;
				case this.consts.kTrack_tickerLinkClicked:
					contentCategoryName += kTrack_tickerLinkClickedS;
					if (data.length > 0) {
						_hbLink(data); // also try to use the _hbLink
					}
					break;
				case this.consts.kTrack_marketingLinkClicked:
					contentCategoryName += kTrack_marketingLinkClickedS;
					if (data.length > 0) {
						_hbLink(data); // also try to use the _hbLink
					}
					break;
				default:		
					contentCategoryName += kTrack_ERRORS;
					break;
			}
	
	
			// note that these are ordered for speed
			switch (platform)
			{
				case this.consts.kDashboardWidget:
					contentCategoryName += kSkyChartAppletName;
					contentCategoryName += kDashboardWidgetFolder;
					break;
				case this.consts.kGoogleGadget:
					contentCategoryName += kSkyChartAppletName;
					contentCategoryName += kGoogleGadgetFolder;
					break;
				case this.consts.kYahooWidget:
					contentCategoryName += kSkyChartAppletName;
					contentCategoryName += kYahooWidgetFolder;
					break;
				case this.consts.kWebApplication:
					contentCategoryName += kSkyChartAppletName;
					contentCategoryName += kWebApplicationFolder;
					break;
				case this.consts.kBlackBerryApp:
					contentCategoryName += kSkyChartAppletName;
					contentCategoryName += kBlackBerryAppFolder;
					break;
				case this.consts.kVistaGadget:
					contentCategoryName += kSkyChartAppletName;
					contentCategoryName += kVistaGadgetFolder;
					break;
				case this.consts.kMEW_Dashboard:
					contentCategoryName += kMoonExplorerAppletName;
					contentCategoryName += kMEWDashboardFolder;
					break;
				case this.consts.kMEW_Yahoo:
					contentCategoryName += kMoonExplorerAppletName;
					contentCategoryName += kMEWYahooFolder;
					break;
				case this.consts.kBSW_Dashboard:
					contentCategoryName += kBirthdaySkyAppletName;
					contentCategoryName += kBSWDashboardFolder;
					break;
				case this.consts.kBSW_Google:
					contentCategoryName += kBirthdaySkyAppletName;
					contentCategoryName += kBSWGoogleFolder;
					break;
				case this.consts.kBSW_Facebook:
					contentCategoryName += kBirthdaySkyAppletName;
					contentCategoryName += kBSWFacebookFolder;
					break;
				case this.consts.kAppleMobile:
					contentCategoryName += kSkyChartAppletName;
					contentCategoryName += kAppleMobileFolder;
					break;
				case this.consts.kUnknownPlatform:
					contentCategoryName += kUnknownPlatformFolder;
					break;
				default:
					contentCategoryName += kUnknownPlatformFolder;
					break;
			}
			
			// If we're tracking an error, append that here
			// NOTE: for errors, the pageName actually gets set to the data and we append
			// the request type to the contentCategoryName so that we can more easily track
			if (isError === true) {
				contentCategoryName += (version.length > 0) ? ('/' + version) : this.consts.kUnknownVersionString;
				if (data.length > 0) {
					pageName = data;
				}
			}
			
			// we want to track the actual link clicked on for marketing and ticker, so fix those here
			if ((action === this.consts.kTrack_tickerLinkClicked) || (action === this.consts.kTrack_marketingLinkClicked))
			{
				contentCategoryName += (version.length > 0) ? ('/' + version) : this.consts.kUnknownVersionString;
				if (data.length > 0) {
					pageName = data;
				}
			}
			
			if (_hbPageView)
			{
				_hbPageView(pageName, contentCategoryName);
			}
		}
	    */
	});
	
	/*
	//----------------------------------------------------------------------
	//	Class:	SNOptions
	//
	//	Purpose:	Container for options 
	//
	//	Date			Initials	Version		Comments
	//  ----------	---------	----------	---------------------------
	//	2007/11/12		DWW			2.0.0		New
	//
	//----------------------------------------------------------------------
	*/
	var SNOptions = Class.create(
	{
		initialize: function(inDefaults)
		{
			this.planetLabels	= (inDefaults && inDefaults.planetLabels)	|| true;
			this.starLabels		= (inDefaults && inDefaults.starLabels)		|| true;
			this.constellations	= (inDefaults && inDefaults.constellations) || true;
			this.constBoundaries= (inDefaults && inDefaults.constBoundaries) || false;
			this.constImages	= (inDefaults && inDefaults.constImages) 	|| false;
			this.constStickFigs	= (inDefaults && inDefaults.constStickFigs) || true;
			this.horizon		= (inDefaults && inDefaults.horizon)		|| true;
			this.ecliptic		= (inDefaults && inDefaults.ecliptic)		|| false;
			this.daylight		= (inDefaults && inDefaults.daylight)		|| true;
			this.markersMoonApollo = (inDefaults && inDefaults.markersMoonApollo) || false;
			this.surfaceGuides	= (inDefaults && inDefaults.surfaceGuides)	|| false;
			this.otherSNFoptions= "";
		},
		getParamsString: function (situParams, getSNF)
		{
			if (getSNF)
			{
				situParams[kSNFFile] += snTag_Prefix + kSNFTag.showPlanets		+ snTag_Middle + (this.planetLabels ?	kYES : kNO) + snTag_Suffix;
				situParams[kSNFFile] += snTag_Prefix + kSNFTag.showStars			+ snTag_Middle + (this.starLabels ?	kYES : kNO) + snTag_Suffix;
				situParams[kSNFFile] += snTag_Prefix + kSNFTag.showConstLabels			+ snTag_Middle + (this.constellations ? kYES : kNO) + snTag_Suffix;
				situParams[kSNFFile] += snTag_Prefix + kSNFTag.showConstBoundaries		+ snTag_Middle + (this.constBoundaries ? kYES : kNO) + snTag_Suffix;
				situParams[kSNFFile] += snTag_Prefix + kSNFTag.showConstIllustrations	+ snTag_Middle + (this.constImages ?	kYES : kNO) + snTag_Suffix;
				situParams[kSNFFile] += snTag_Prefix + kSNFTag.showConstStickFigures 	+ snTag_Middle + (this.constStickFigs ?kYES : kNO) + snTag_Suffix;
				situParams[kSNFFile] += snTag_Prefix + kSNFTag.showHorizon 			+ snTag_Middle + (this.horizon ?		kYES : kNO) + snTag_Suffix;
				situParams[kSNFFile] += snTag_Prefix + kSNFTag.showEcliptic				+ snTag_Middle + (this.ecliptic ?		kYES : kNO) + snTag_Suffix;
				situParams[kSNFFile] += snTag_Prefix + kSNFTag.showEclipticEquator 			+ snTag_Middle + (this.ecliptic ?		kYES : kNO) + snTag_Suffix;
				situParams[kSNFFile] += snTag_Prefix + kSNFTag.showDaylight				+ snTag_Middle + (this.daylight ?		kYES : kNO) + snTag_Suffix;
				situParams[kSNFFile] += snTag_Prefix + kSNFTag.showSurfaceGuides 	+ snTag_Middle + (this.surfaceGuides ? kYES : kNO) + snTag_Suffix;
				
				if (this.otherSNFoptions !== "")
				{
					situParams[kSNFFile] += this.otherSNFoptions + "\n";
				}
			}
			else
			{
				situParams[kPlanetLabels]		= (this.planetLabels ?	kYES : kNO);
				situParams[kStarLabels]			= (this.starLabels ?	kYES : kNO);
				situParams[kConstellations]		= (this.constellations ? kYES : kNO);
				situParams[kConstBoundaries]	= (this.constBoundaries ? kYES : kNO);
				situParams[kConstImages]		= (this.constImages ?	kYES : kNO);
				situParams[kConstStickFigs]		= (this.constStickFigs ?kYES : kNO);
				situParams[kHorizon]			= (this.horizon ?		kYES : kNO);
				situParams[kEcliptic]			= (this.ecliptic ?		kYES : kNO);
				situParams[kShowDaylight]		= (this.daylight ?		kYES : kNO);
				situParams[kLocMarkersMoonApollo] = (this.markersMoonApollo ? kYES : kNO);
				situParams[kSurfaceGuides]		= (this.surfaceGuides ? kYES : kNO);
			}
			
		}
	});

	/*
	//----------------------------------------------------------------------
	//	Class:	SNLocation
	//
	//	Purpose:	Container for location params.
	//
	//	Date			Initials	Version		Comments
	//  ----------	---------	----------	---------------------------
	//	2007/11/12		DWW			2.0.0		New
	//
	//----------------------------------------------------------------------
	*/
	var SNLocation = Class.create(
	{
		initialize: function(inDefaults)
		{
			this.locName	= (inDefaults && inDefaults.locName)	|| kDefaultLocationName;  // this is the planet/body we are located on
			this.lat		= (inDefaults && inDefaults.lat)		|| kTorontoLat;
			this.lng		= (inDefaults && inDefaults.lng) 		|| kTorontoLong;
			this.timezone	= (inDefaults && inDefaults.timezone)	|| kTorontoTimezone;
			this.elevation	= (inDefaults && inDefaults.elevation)	|| kTorontoElevation;
			this.useDST		= (inDefaults && inDefaults.useDST)		|| false;
		},	
		
		// inIncludeTimezone should probably be removed
		getParamsString: function (situParams, getSNF, inIncludeTimezone)
		{			
			if (getSNF)
			{
				situParams[kSNFFile] += snTag_Prefix + kSNFTag.lat + snTag_Middle + this.lat + snTag_Suffix;
				situParams[kSNFFile] += snTag_Prefix + kSNFTag.lon + snTag_Middle + this.lng + snTag_Suffix;
				
				situParams[kSNFFile] += snTag_Prefix + kSNFTag.dst + snTag_Middle + (this.useDST ? kYES : kNO) + snTag_Suffix;
				
				situParams[kSNFFile] += snTag_Prefix + kSNFTag.planet + snTag_Middle + this.locName + snTag_Suffix;  // this is the planet/body we are located on
				situParams[kSNFFile] += snTag_Prefix + kSNFTag.elevation + snTag_Middle + this.elevation + snTag_Suffix;				
			}
			else
			{
				situParams[kLatitude] = this.lat;
				situParams[kLongitude] = this.lng;
				
				if (inIncludeTimezone === true)
				{
					situParams[kTimeZone] = this.timezone;
				}
	
				situParams[kDST] = (this.useDST ? kYES : kNO);
			
				situParams[kLocationName] = this.locName;  // this is the planet/body we are located on
				situParams[kElevation] = this.elevation;				
			}
		}
	});
	
	
	/*
	//----------------------------------------------------------------------
	//	Class:	SNGaze
	//
	//	Purpose:	Container for gaze params.
	//
	//	Date			Initials	Version		Comments
	//  ----------	---------	----------	---------------------------
	//	2007/11/12		DWW			2.0.0		New
	//
	//----------------------------------------------------------------------
	*/
	var SNGaze = Class.create(
	{
		initialize: function (inDefaults)
		{
			this.alt	= (inDefaults && inDefaults.alt)	|| kDefaultGaze.alt;
			this.az		= (inDefaults && inDefaults.az)		|| kDefaultGaze.az;
			this.FOV	= (inDefaults && inDefaults.FOV)	|| kDefaultGaze.FOV;
		},
		getParamsString: function (situParams, getSNF)
		{

			if (getSNF)
			{
				situParams[kSNFFile] += snTag_Prefix + kSNFTag.alt + snTag_Middle + this.alt + snTag_Suffix;
				situParams[kSNFFile] += snTag_Prefix + kSNFTag.az + snTag_Middle + (360 - this.az) + snTag_Suffix; //SN interprets az the opposite way
				situParams[kSNFFile] += snTag_Prefix + kSNFTag.fovWidth + snTag_Middle + this.FOV + snTag_Suffix;
				situParams[kSNFFile] += snTag_Prefix + kSNFTag.fovHeight + snTag_Middle + this.FOV + snTag_Suffix;	
			}
			else
			{
				situParams[kAltitude] = this.alt;
				situParams[kAzimuth] = this.az;
				situParams[kFOV] = this.FOV;
			}				
		}
	});
	
	
	/*
	//----------------------------------------------------------------------
	//	Class:	SNSituation
	//
	//	Purpose:	Container for the entire situation
	//
	//	Date			Initials	Version		Comments
	//  ----------	---------	----------	---------------------------
	//	2007/11/12		DWW			2.0.0		New
	//
	//----------------------------------------------------------------------
	*/
	var SNSituation = Class.create(
	{
		initialize: function (inDefaults)
		{
			this.sendSNF	= (inDefaults && inDefaults.sendSNF)	|| false;
			this.dateTime	= (inDefaults && inDefaults.dateTime)	|| new Date();
			this.location	= (inDefaults && inDefaults.location)	|| new SNLocation();
			this.options	= (inDefaults && inDefaults.options)	|| new SNOptions();
			this.gaze		= (inDefaults && inDefaults.gaze)		|| new SNGaze();	
			this.useTimezoneAndUTC = (inDefaults && inDefaults.useTimezoneAndUTC) || false; // default this to false, because timezone is defaulted to an incorrect (unknown) value
			
			// set this up for the defaults
			this.location.useDST = this.currentlyExperiencingDST();
		},
		getParamsString: function (situParams, getSNF)
		{						
			// Location, Gaze, Options
			this.location.getParamsString(situParams, getSNF, this.useTimezoneAndUTC);
			this.gaze.getParamsString(situParams, getSNF);	
			this.options.getParamsString(situParams, getSNF);
		
			// If useTimezoneAndUTC is true, we use the timezone info set in the location 
			// and we pass UTC info from the dateTime param here.
			var useUTC = this.useTimezoneAndUTC;
			var date = this.dateTime;
						
			if (getSNF)
			{
				var julianDate = SNWEB.utils.civil2Julian(
															(useUTC ? date.getUTCFullYear() : date.getFullYear() ),
															(useUTC ? (date.getUTCMonth()+1) : (date.getMonth()+1) ),
															(useUTC ? date.getUTCDate() : date.getDate()),
															(useUTC ? date.getUTCHours() : date.getHours()),
															(useUTC ? date.getUTCMinutes() : date.getMinutes()),
															(useUTC ? date.getUTCSeconds() : date.getSeconds())
														);
				situParams[kSNFFile] += snTag_Prefix + kSNFTag.julianDay + snTag_Middle + julianDate + snTag_Suffix;
			}
			else
			{
				// Date / Time
				situParams[kDay]	= (useUTC ? date.getUTCDate() : date.getDate());
				situParams[kMonth]	= (useUTC ? (date.getUTCMonth()+1) : (date.getMonth()+1) );
				situParams[kYear]	= (useUTC ? date.getUTCFullYear() : date.getFullYear() );
			
				var hours = (useUTC ? date.getUTCHours() : date.getHours());
				var ampm = "1";
				if (hours == 12)
				{
					ampm = "2";
				}
				else if (hours > 12)
				{
					ampm = "2";
					hours -= 12;
				}		
			
				situParams[kHours] = hours;
				situParams[kMinutes] = (useUTC ? date.getUTCMinutes() : date.getMinutes());
				situParams[kAmPm] = ampm;
			}

			return situParams;
		},
		loadScene: function (sceneSNFUrl, async,onCompleteCall)
		{
			var callBack = null;
			var options = { 
				method: 'GET',
				asynchronous: ((async === undefined) ? true : async),
				onSuccess: this.sceneLoadSuccess.bind(this),
				onFailure: this.sceneLoadFailure.bind(this)
			}
			if(onCompleteCall !== 'undefined' ){
				options.onComplete = onCompleteCall;
			}
			
			new Ajax.Request(sceneSNFUrl,options);			
		},
		sceneLoadSuccess: function(transport)
		{
			var fullSNF = { text: transport.responseText };
			
/*			TOO SLOW (besides, it seems that snf works ok without removing header + footer from SNFs)
			var re = new RegExp("(<SN_VALUE(.*\\\n*)*)</BODY>","i");
			var result = re.exec(fullSNF.text);
			if (result && (result.length > 1))
			{
				fullSNF.text = result[1];
			}
*/
			var width = SNWEB.utils.extractSNFInfo(fullSNF, kSNFTag.width);
			var height = SNWEB.utils.extractSNFInfo(fullSNF, kSNFTag.height);
			var lat = SNWEB.utils.extractSNFInfo(fullSNF, kSNFTag.lat);
			var lon = SNWEB.utils.extractSNFInfo(fullSNF, kSNFTag.lon);
			var dst = SNWEB.utils.extractSNFInfo(fullSNF, kSNFTag.dst);
			var planet = SNWEB.utils.extractSNFInfo(fullSNF, kSNFTag.planet);
			var elevation = SNWEB.utils.extractSNFInfo(fullSNF, kSNFTag.elevation);
			var alt = SNWEB.utils.extractSNFInfo(fullSNF, kSNFTag.alt);
			var az = SNWEB.utils.extractSNFInfo(fullSNF, kSNFTag.az);
			var fovWidth = SNWEB.utils.extractSNFInfo(fullSNF, kSNFTag.fovWidth);
			var fovHeight = SNWEB.utils.extractSNFInfo(fullSNF, kSNFTag.fovHeight);
			var showPlanets = SNWEB.utils.extractSNFInfo(fullSNF, kSNFTag.showPlanets);
			var showStars = SNWEB.utils.extractSNFInfo(fullSNF, kSNFTag.showStars);
			var showConstLabels = SNWEB.utils.extractSNFInfo(fullSNF, kSNFTag.showConstLabels);
			var showConstBoundaries = SNWEB.utils.extractSNFInfo(fullSNF, kSNFTag.showConstBoundaries);
			var showConstIllustrations = SNWEB.utils.extractSNFInfo(fullSNF, kSNFTag.showConstIllustrations);
			var showConstStickFigures = SNWEB.utils.extractSNFInfo(fullSNF, kSNFTag.showConstStickFigures); 
			var showHorizon = SNWEB.utils.extractSNFInfo(fullSNF, kSNFTag.showHorizon);
			var showEcliptic = SNWEB.utils.extractSNFInfo(fullSNF, kSNFTag.showEcliptic);
			var showEclipticEquator = SNWEB.utils.extractSNFInfo(fullSNF, kSNFTag.showEclipticEquator);
			var showDaylight = SNWEB.utils.extractSNFInfo(fullSNF, kSNFTag.showDaylight);
			var showSurfaceGuides = SNWEB.utils.extractSNFInfo(fullSNF, kSNFTag.showSurfaceGuides);
			var julianDay = SNWEB.utils.extractSNFInfo(fullSNF, kSNFTag.julianDay);
					
			if (lat !== null) this.location.lat = parseFloat(lat);
			if (lon !== null) this.location.lng = parseFloat(lon);
			if (dst !== null) this.location.useDST = dst;
			if (planet !== null) this.location.locName = planet;
			if (elevation !== null) this.location.elevation = parseFloat(elevation);
			if (alt !== null) this.gaze.alt = parseFloat(alt);
			if (az !== null) this.gaze.az = 360 - parseFloat(az);
			if (fovWidth !== null || fovHeight !== null) this.gaze.FOV = parseFloat(fovWidth);
			if (showPlanets !== null) this.options.planetLabels = ( showPlanets == kYES );
			if (showStars !== null) this.options.starLabels = ( showStars == kYES );
			if (showConstLabels !== null) this.options.constellations = ( showConstLabels == kYES );
			if (showConstBoundaries !== null) this.options.constBoundaries = ( showConstBoundaries == kYES );
			if (showConstIllustrations !== null) this.options.constImages = ( showConstIllustrations == kYES );
			if (showConstStickFigures !== null) this.options.constStickFigs = ( showConstStickFigures == kYES );
			if (showHorizon !== null) this.options.horizon = ( showHorizon == kYES );
			if (showEcliptic !== null && showEclipticEquator !== null) this.options.ecliptic = ( showEclipticEquator == kYES );
			if (showDaylight !== null) this.options.daylight = ( showDaylight == kYES );
			if (showSurfaceGuides !== null) this.options.surfaceGuides = ( showSurfaceGuides == kYES );			
			
			if (julianDay === null)
			{
				julianDay = SNWEB.utils.extractSNFInfo(fullSNF, kSNFTag.julianDayUTC);
			}
			
			if (julianDay !== null)
			{
				this.dateTime = SNWEB.utils.julian2Civil(julianDay);				
			}
			
			this.options.otherSNFoptions = fullSNF.text;
		},
		sceneLoadFailure: function()
		{
			return;
		},
		loadSceneFromSituationParams: function( situationParams, onCompleteCall )
		{			
			if (situationParams[kLatitude] !== undefined) this.location.lat = situationParams[kLatitude];
			if (situationParams[kLongitude] !== undefined) this.location.lng = situationParams[kLongitude];
			if (situationParams[kDST] !== undefined) this.location.useDST = situationParams[kDST];
			if (situationParams[kLocationName] !== undefined) this.location.locName = situationParams[kLocationName];
			if (situationParams[kElevation] !== undefined) this.location.elevation = situationParams[kElevation];
			if (situationParams[kAltitude] !== undefined) this.gaze.alt = situationParams[kAltitude];
			if (situationParams[kAzimuth] !== undefined) this.gaze.az = situationParams[kAzimuth];
			if (situationParams[kFOV] !== undefined) this.gaze.FOV = situationParams[kFOV];
			if (situationParams[kPlanetLabels] !== undefined) this.options.planetLabels = ( situationParams[kPlanetLabels] == kYES );
			if (situationParams[kStarLabels] !== undefined) this.options.starLabels = ( situationParams[kStarLabels] == kYES );
			if (situationParams[kConstellations] !== undefined) this.options.constellations = ( situationParams[kConstellations] == kYES );
			if (situationParams[kConstBoundaries] !== undefined) this.options.constBoundaries = ( situationParams[kConstBoundaries] == kYES );
			if (situationParams[kConstImages] !== undefined) this.options.constImages = ( situationParams[kConstImages] == kYES );
			if (situationParams[kConstStickFigs] !== undefined) this.options.constStickFigs = ( situationParams[kConstStickFigs] == kYES );
			if (situationParams[kHorizon] !== undefined) this.options.horizon = ( situationParams[kHorizon] == kYES );
			if (situationParams[kEcliptic] !== undefined) this.options.ecliptic = ( situationParams[kEcliptic] == kYES );
			if (situationParams[kShowDaylight] !== undefined) this.options.daylight = ( situationParams[kShowDaylight] == kYES );
			if (situationParams[kLocMarkersMoonApollo] !== undefined) this.options.markersMoonApollo = ( situationParams[kLocMarkersMoonApollo] == kYES );
			if (situationParams[kSurfaceGuides] !== undefined) this.options.surfaceGuides = ( situationParams[kSurfaceGuides] == kYES );
			
			if ( (situationParams[kMonth] !== undefined) &&
					(situationParams[kDay] !== undefined) &&
						(situationParams[kYear] !== undefined) &&
							(situationParams[kHours] !== undefined) &&
								(situationParams[kMinutes] !== undefined)
				)
			{
				hours = parseInt(situationParams[kHours]); 
				if (situationParams[kAmPm] !== undefined)
				{
					if (situationParams[kAmPm] == 2)
					{
						if (hours != 12)
						{
							hours += 12;
						}
					}
					else // if (situationParams[kAmPm] == 1)
					{
						if (hours == 12)
						{
							hours = 0;
						}
					}
				}
				
				this.dateTime = new Date(situationParams[kYear], situationParams[kMonth]-1, situationParams[kDay], hours, situationParams[kMinutes], 0);
			}
			
			if (onCompleteCall)
			{
				onCompleteCall();
			}
			
		},
		//----------------------------------------------------------------------
		//	Object:	currentlyExperiencingDST
		//
		//	Purpose:	Determine if the location and date defined in the situation
		//				are currently on Daylight Saving Time, based on the 
		//
		//	Date			Initials	Version		Comments
		//  ----------	---------	----------	---------------------------
		//	2007/07/17		DWW			1.0.2		New
		//
		//----------------------------------------------------------------------
		currentlyExperiencingDST: function ()
		{
			var theLat = this.location.lat;
			var theLong = this.location.lng;
			var theDate = this.dateTime;
			
			var dstStart;
			var dstEnd;
			var dayOfWeek;
			var daysToAdd;
			
			// North America
			if ((theLat > 19.7) && (theLong < -25))
			{
				// DST starts the 2nd sunday in March
				var marchNA = new Date(theDate.getFullYear(), 2,1);
				// get the 2nd Sunday
				dayOfWeek = marchNA.getDay();
				daysToAdd = (dayOfWeek === 0) ? 7 : 14 - dayOfWeek; // Sunday is day "0"
				dstStart = marchNA.setDate(daysToAdd +1); // +1 to take into account that we're starting on the first already
		
				// DST ends the 1st sunday in November
				var novemberNA = new Date(theDate.getFullYear(), 10, 1);
				// get the 1st Sunday
				dayOfWeek = novemberNA.getDay();
				daysToAdd = (dayOfWeek === 0) ? 0 : 7 - dayOfWeek; // Sunday is day "0"
				dstEnd = novemberNA.setDate(daysToAdd +1); // +1 to take into account that we're starting on the first already
				
				if ((theDate > dstStart) && (theDate < dstEnd))
				{
					return true;
				}
				else
				{
					return false;
				}
			}
			
			// Europe and Russia use the same dates
			if (((theLat > 36) && (theLong > -25) && (theLong < 46)) || 
				((theLat > 54) && (theLong > 46)))
			{
				// DST starts the last sunday in March
				var marchER = new Date(theDate.getFullYear(), 2, 31); // start from last day of March
				// get the last Sunday
				while (marchER.getDay() !== 0)
				{
					marchER.setDate(marchER.getDate() - 1); // go back one day and test again
				}
				dstStart = marchER;
		
				// DST ends the last Sunday in October
				var octoberER = new Date(theDate.getFullYear(), 9, 30); 
				// get the last Sunday
				while (octoberER.getDay() !== 0)
				{
					octoberER.setDate(octoberER.getDate() - 1); // go back one day and test again
				}
				dstEnd = octoberER;
				
				if ((theDate > dstStart) && (theDate < dstEnd))
				{
					return true;
				}
				else
				{
					return false;
				}
			}
			
			// Australia
			// In July of 2007 there was still some controversy about DST in Australia, so I just set true for the places that
			// consistently use DST
			if ((theLat > -44) && (theLat < -29) && (theLong > 129) && (theLong < 155))
			{
				// DST starts the last Sunday in October
				var octoberAU = new Date(theDate.getFullYear(), 9, 30); 
				// get the last Sunday
				while (octoberAU.getDay() !== 0)
				{
					octoberAU.setDate(octoberAU.getDate() - 1); // go back one day and test again
				}
				dstStart = octoberAU;
		
				// DST ends the last sunday in March
				var marchAU = new Date(theDate.getFullYear(), 2, 31); // start from last day of March
				// get the last Sunday
				while (marchAU.getDay() !== 0)
				{
					marchAU.setDate(marchAU.getDate() - 1); // go back one day and test again
				}
				dstEnd = marchAU;
				
				// need to use || here since DST spans the year boundary i.e. starts in October, ends in March the following year
				if ((theDate > dstStart) || (theDate < dstEnd))
				{
					return true;
				}
				else
				{
					return false;
				}
			}
			
			// Brasil
			// Just include the region around Rio, as not all provinces observe DST
			if ((theLat < -15) && (theLat > -26) && (theLong > -53) && (theLong < -38))
			{
				// DST starts the 1st sunday in October
				var octoberBR = new Date(theDate.getFullYear(), 9, 1); 
				// get the 1st Sunday
				dayOfWeek = octoberBR.getDay();
				daysToAdd = (dayOfWeek === 0) ? 7 : 7 - dayOfWeek; // Sunday is day "0"
				dstStart = octoberBR.setDate(daysToAdd +1); // +1 to take into account that we're starting on the first already
		
				// DST ends the 1st sunday in Feb with day >= 11
				var februaryBR = new Date(theDate.getFullYear(), 1, 11); 
				// get the 1st Sunday with Day >= 11
				dayOfWeek = februaryBR.getDay();
				daysToAdd = (dayOfWeek === 0) ? 7 : 7 - dayOfWeek; // Sunday is day "0"
				dstEnd = februaryBR.setDate(daysToAdd +11); // +11 to take into account that we're starting on the 11th
				
				// need to use || here since DST spans the year boundary i.e. starts in October, ends in Feb the following year
				if ((theDate > dstStart) || (theDate < dstEnd))
				{
					return true;
				}
				else
				{
					return false;
				}
			}
			
			return false;
		}
	});
	
	
	// ----------------------------------------------------------------------------------------
	// UTILITY FUNCTIONS
	// ----------------------------------------------------------------------------------------
	
	var Utilities = Class.create(
	{
		initialize: function ()
		{
		
		},
		// ----------------------------------------------------------------------------------------
		//	getDaysInMonth()
		//
		//	Purpose:	returns the number of days in the passed month/year
		//
		//	inYear:		year, 4 digit number
		//	inMonth:	month, number, 0 relative (Jan = 0)
		//
		//	Date		Initials	Version		Comments
		//  ----------	---------	----------	---------------------------
		//	2007/08/07	DWW			1.0			Adapted from Ivan's common widget code
		// ----------------------------------------------------------------------------------------
		getDaysInMonth: function (inYear, inMonth)
		{
	
			// calculate the number of days in the month
			var month = parseInt(inMonth, 10);
			var year = parseInt(inYear, 10);
			if ((month > 11) || (year < 0))
			{
				return -1; // signal an error
			}
			
			var kMonthDays= [31,28,31,30,31,30,31,31,30,31,30,31];
			var monthLength = kMonthDays[month];
			
			// adapt for a leap year
			if (month == 1 && isLeapYear(year))
			{
				monthLength = 29;
			}
			
			return monthLength;
		},
		// ----------------------------------------------------------------------------------------
		//	isLeapYear()
		//
		//	Purpose:	returns true if the passed year is a leap year
		//
		//	inYear:		year, 4 digit number
		//
		//	Date		Initials	Version		Comments
		//  ----------	---------	----------	---------------------------
		//	2007/08/07	DWW			1.0			Adapted from Ivan's common widget code
		// ----------------------------------------------------------------------------------------
		isLeapYear: function (inYear)
		{
			var year = parseInt(inYear, 10);
			if (year <= 0)
			{
				return false;
			}
		
			if (year%4 === 0)
			{
				if (year%100 !== 0)
				{
					return true;
				}
				else
				{
					if (year%400 === 0)
					{
						return true;
					}
					else
					{
						return false;
					}
				}
			}
			
			return false;
		},
		// ----------------------------------------------------------------------------------------
		//	civil2Julian()
		//
		//	Purpose:	returns Julian Date for year/month/day/hour/minute/second spec
		//
		//
		//	Date		Initials	Version		Comments
		//  ----------	---------	----------	---------------------------
		//	2008/25/07	MK			1.0			Ported from snclient.cgi
		// ----------------------------------------------------------------------------------------		
		civil2Julian: function (year, month, day, hour, minute, second)
		{	
			var jd, jd0;
			var jy, ja, jm;
			var tmpInt;
			var dayfrac = 0.0;
			var frac = 0.0;
		
			if ( year < 0 )
			{
				year++;
			}

			if ( month > 2 ) 
			{
				jy = year;
				jm = month + 1;
		    }
			else
			{
				jy = year - 1;
				jm = month + 13;
		    }
	
		   tmpInt = Math.floor(365.25*jy) + Math.floor(30.6001*jm) + day + 1720995;

		    // check for switch to Gregorian calendar
		    if ( day + 31*(month + 12*year) >= kGregorianCalendar )
		    {
        		ja = Math.floor(0.01*jy);
        		tmpInt += 2 - ja + Math.floor(0.25*ja);
		    }

		    // correct for half-day offset
    		dayfrac = hour/24.0 - 0.5;
		    if ( dayfrac < 0.0 ) 
		    {
				dayfrac += 1.0;
				tmpInt--;
    		}
    
		    // now set the fraction of a day
		    frac = dayfrac + (minute + second/60.0)/60.0/24.0;
    
		    // round to nearest second
		    jd0 = (tmpInt + frac)*100000;
		    jd  = Math.floor(jd0);
    
		    if ( (jd0 - jd) > 0.5 )
    		{
				jd++;
			}

		    jd = jd/100000;
    
		    return jd;    
		},
		// ----------------------------------------------------------------------------------------
		//	julian2DateObject()
		//
		//	Purpose:	returns date object set from julian date
		//
		//
		//	Date		Initials	Version		Comments
		//  ----------	---------	----------	---------------------------
		//	2008/12/08	MK			1.0			
		// ----------------------------------------------------------------------------------------		
		julian2Civil: function (jd)
		{
			var	j1, j2, j3, j4, j5;                     //scratch
	
			//
			// get the date from the Julian day number
			//
			var intgr   = Math.floor(jd);
			var frac    = jd - intgr;

			if( intgr >= kGregorianJD ) 
			{
				//Gregorian calendar correction
				var tmp = Math.floor(((intgr - 1867216.0) - 0.25)/36524.25);
				j1 = intgr + 1.0 + tmp - Math.floor(0.25*tmp);
			}
			else
			{
				j1 = intgr;
			}
			
			//correction for half day offset
			var dayfrac = frac + 0.5;
			if( dayfrac >= 1.0 ) 
			{
				dayfrac -= 1.0;
				++j1;
			}

			j2 = j1 + 1524.0;
			j3 = Math.floor( 6680.0 + ( (j2 - 2439870.0) - 122.1 )/365.25 );
			j4 = Math.floor(j3*365.25);
			j5 = Math.floor( (j2 - j4)/30.6001 );

			var d = Math.floor(j2 - j4 - Math.floor(j5*30.6001));
			var m = Math.floor(j5 - 1.0);
			
			if ( m > 12 ) m -= 12.0;
			
			var y = Math.floor(j3 - 4715.0);
			
			if( m > 2 )   --y;
			if( y <= 0 )  --y;

			//
			// get time of day from day fraction
			//
			// The US naval code has a bug, in that 1724683.83333 and many dates like it 
			// will convert to 7:59:60, the proper way to do it seems to be to calculate the 
			// number of seconds and work from there.
			var secondsLeft = parseInt(dayfrac*86400.0 + 0.5); // add 

			if (secondsLeft >= 86400)
				secondsLeft = 86399;  // we don't round up if 
	
			var hour = parseInt(secondsLeft/3600.0);
			secondsLeft = secondsLeft % 3600;
			var minutes = parseInt(secondsLeft/60.0);
			secondsLeft = secondsLeft % 60;

	
			return new Date(parseInt(y), parseInt(m-1), parseInt(d), parseInt(hour), parseInt(minutes), parseInt(secondsLeft));
		},		
		extractSNFInfo: function(fullSNF, tagToExtract)
		{
			if (fullSNF.text === undefined)
			{
				return null;
			}
			
			var re = new RegExp("<SN_VALUE\\\s*name\\\s*=\\\s*\"" + tagToExtract + "\"\\\s*value\\\s*=\\\s*\"(.+?)\"\\\s*>\\\n*","g");
			var myData = null;
			var result = re.exec(fullSNF.text);

			if (result && (result.length > 1))
			{
				myData = result[1];
				fullSNF.text = fullSNF.text.replace(result[0], "");
			}
			
			return myData;
		},
		//----------------------------------------------------------------------
		//	Object:	walkTheDOM
		//
		//	Purpose:	walks the DOM structure calling the passed func on each node
		//
		//	Date			Initials	Version		Comments
		//  ----------	---------	----------	---------------------------
		//	2007/08/17		DWW			1.0.2		New
		//
		//----------------------------------------------------------------------
		walkTheDOM: function (node, func, omitSelf)
		{
			omitSelf = omitSelf || false;
			if (omitSelf === false){
				func(node);
			}
			node = node.firstChild;
			while (node) {
				this.walkTheDOM(node, func, false); // don't pass on omitSelf! We only want to [optionally] omit the parent node
				node = node.nextSibling;
			}
		},
		//----------------------------------------------------------------------
		//	Object:	getElementsByClassName
		//
		//	Purpose:	returns an array of all DOM elements having the passed class name
		//
		//	Date			Initials	Version		Comments
		//  ----------	---------	----------	---------------------------
		//	2007/08/17		DWW			1.0.2		New
		//
		//----------------------------------------------------------------------
		getElementsByClassName: function (className)
		{
			var results = [];
			this.walkTheDOM(document.body, function(node) {
				var a, c = node.className, i;
				if (c) {
					a = c.split(' ');
					for (i = 0; i < a.length; i += 1) {
						if (a[i] === className) {
							results.push(node);
							break;
						}	
					}
				}
			});
			return results;
		}
	});

	// for backwards compatibility, extend the SNWebService with the SNSituation class constructor as a method.
	SNWebService.addMethods({
		SNSituation: SNSituation
	});

	// EXPORT constants and functions to the SNWEB namespace as an insance of SNWebService
	return new SNWebService();
})(); // end definition of anonymous function and invoke it.
