﻿// equationOfTime.js
//
// This class calculates the Equation Of Time for a given date.

//************************************* Private storage *********************************
var eotStorage = new Object();

// Utilities

// Converts Date object to day number (float)
eotStorage.dateToDay = function(date) {
	var janOne = new Date(date.getFullYear(), 0, 1);
	return ((date.valueOf() - janOne.valueOf()) / eotStorage.msPerDay) + 1;
}

// Converts Date object to day fraction
eotStorage.dateToDayFraction = function(date) {
	var result = (date.valueOf() % eotStorage.msPerDay) / eotStorage.msPerDay;
	return result;
}

// Converts day number to position in ellipse
eotStorage.dayToV = function(day) {
	var result;
	result = (eotStorage.anglePerDay * (day - eotStorage.perihelion)) 
			+ 360/Math.PI * eotStorage.eccentricity
				* Math.sin(eotStorage.degToRad(eotStorage.anglePerDay * day
												- eotStorage.perihelion));
	return result;
}

// Converts radians to degrees.
eotStorage.radToDeg = function(rad) {
	return rad * 180 / Math.PI;
}

// Converts degrees to radians.
eotStorage.degToRad = function(deg) {
	return deg * Math.PI / 180;
}

// Properties. Notes:
//		All angles are in degrees
//		Must define vAtEquinox last!
eotStorage.tilt = 23.45;							// Tilt of the equator
eotStorage.sinTilt = Math.sin(eotStorage.degToRad(eotStorage.tilt));	// Sine of tilt
eotStorage.cosTilt = Math.cos(eotStorage.degToRad(eotStorage.tilt));		// Cosine of tilt
eotStorage.eccentricity = 0.016713					// Orbital eccentricity
eotStorage.msPerDay = 1000 * 60 * 60 * 24;			// Milliseconds per day
eotStorage.perihelion = 2;							// Day number at perihelion
eotStorage.vernalEquinox = 80;						// Day number at vernal equinox
eotStorage.anglePerDay = 360/365.25;				// Mean motion per day around orbit
eotStorage.minutesPerDegree = (60 * 24) / (360 + 360/365.25);	// Rotation speed
eotStorage.vAtEquinox = eotStorage.dayToV(eotStorage.vernalEquinox);	// Position in ellipse
																		// at vernal equinox

//************************************* Public functions *********************************

// Given date, returns subsolar point as an object with lat and lon properties.
// Argument:
//		date: Date object
function eotSubsolar(date) {
	
	var day = eotStorage.dateToDay(date);
	var dayFraction = eotStorage.dateToDayFraction(date);
	
	// EOT calculated in terms of degrees, not minutes
	var eotEccentric = eotStorage.anglePerDay * (day - eotStorage.perihelion) 
						- eotStorage.dayToV(day);
	
	var meanAngle = (day - eotStorage.vernalEquinox) * eotStorage.anglePerDay;
	if (meanAngle >= 270) {
		meanAngle -= 360;
	} else if (meanAngle >= 90) {
		meanAngle -= 180;
	}
	var eotTilt = meanAngle 
				- eotStorage.radToDeg(Math.atan(eotStorage.cosTilt 
									* Math.tan(eotStorage.degToRad(meanAngle))));
	var eot = eotEccentric + eotTilt;
	var lon = 180 - (dayFraction * 360 + eot);
	while (lon >= 180) {
		lon -= 360;
	}
	
	var w = eotStorage.dayToV(day) - eotStorage.vAtEquinox;
	var lat = eotStorage.radToDeg(Math.asin(Math.sin(eotStorage.degToRad(w)) 
												* eotStorage.sinTilt));

	var result = new Object();
	result.lat = lat;
	result.lon = lon;
	result.day = day;
	result.dayFraction = dayFraction;
	result.eotEccentric = eotEccentric;
	result.meanAngle = meanAngle;
	result.eotTilt = eotTilt;
	result.eot = eot;
	result.w = w;

	return result;
} 

// Given date, lat and lon, returns angle of sun above horizon. Arguments:
//		date: Date object
//		lat, lon: Position of query
function eotSolarAngle(date, lat, lon) {
	// Get subsolar point
	var ss = eotSubsolar(date);
	// Get angular distance between subsolar and given lat/lon
	var lat1 = eotStorage.degToRad(lat);
	var lon1 = eotStorage.degToRad(lon);
	var lat2 = eotStorage.degToRad(ss.lat);
	var lon2 = eotStorage.degToRad(ss.lon);
	var angle = eotStorage.radToDeg(Math.sin(lat1) * Math.sin(lat2)
					+ Math.cos(lat1) * Math.cos(lat2) * cos(lon2 - lon1));
	// Convert to sun altitude
	var altitude = 90 - angle;
	return altitude;
}
	
// This tests the class, tracing the results.
function eotTestSubsolar() {
	document.writeln("Here we go...<br>\n");
	var result = eotSubsolar(new Date());
	document.writeln("Subsolar for now: lat " + result.lat + ", lon " + result.lon + "<br>\n");
	document.writeln("Day by day:<br>\n");
	for (var day = 1; day <= 365; day++) {
		var date = new Date();
		date.setTime(0);			// Reset all fields to zero
		date.setUTCFullYear(2007);
		date.setUTCDate(day);
		date.setUTCHours(12);		// Noon on given day
		var result = eotSubsolar(date);
 		document.writeln("For day " + day 
 							+ ", dayFraction " + result.dayFraction
 							+ ", eotEccentric " + result.eotEccentric
 							+ ", meanAngle " + result.meanAngle
 							+ ", eotTilt " + result.eotTilt
 							+ ", eot " + result.eot
 							+ ", w " + result.w
 							+ ", lat " + result.lat + ", lon " + result.lon + "<br>\n");
		document.writeln(day + "\t" + result.lon + "\t" + result.lat + "<br>\n");
	}
 	document.writeln("Hour by hour:<br>\n");
	for (var hour = 0; hour <= 24; hour++) {
		var date = new Date();
		date.setTime(0);			// Reset all fields to zero
		date.setUTCFullYear(2007);
		date.setUTCDate(1);
		date.setUTCHours(hour);
		var result = eotSubsolar(date);
 		document.writeln("For hour " + hour 
 							+ ", dayFraction " + result.dayFraction
 							+ ", eotEccentric " + result.eotEccentric
 							+ ", meanAngle " + result.meanAngle
 							+ ", eotTilt " + result.eotTilt
 							+ ", eot " + result.eot
 							+ ", w " + result.w
 							+ ", lat " + result.lat + ", lon " + result.lon + "<br>\n");
		document.writeln(hour + "\t" + result.lon + "\t" + result.lat + "<br>\n");
	}
}
