
	/////////////////////////////////////////////
	// panel_shading.js
	// Copyright 2008, 1995 Sustainable By Design
	/////////////////////////////////////////////
	

	/////////////////////////////////////////////////////////
	//
	//  constants
	//
	/////////////////////////////////////////////////////////	
			
		var       degreesToRadians =    3.1416 /  180.0000;
		var       radiansToDegrees =  180.0000 /    3.1416;
		var           feetToMeters =    1.0000 /    3.2800;
		var degreeMinutesToDecimal =    1.0000 /   60.0000;
		var degreeSecondsToDecimal =    1.0000 / 3600.0000;				
	

	/////////////////////////////////////////////////////////
	//
	//  initialization
	//
	/////////////////////////////////////////////////////////
	
 		var firstHour =  4;
 		var  lastHour = 20;
 		

	/////////////////////////////////////////////////////////
	//
	//  debugging
	//
	/////////////////////////////////////////////////////////
	
 		var debug = false;
 		
 		var debugMonth = 1;
 		var debugHour  = 12;
 		

	/////////////////////////////////////////////////////////
	//
	//  FUNCTION:  CheckInputs
	//
	/////////////////////////////////////////////////////////
	
		function CheckInputs () {
		
			var error = false;
		
			var error_message = "The following inputs must be corrected before the sun angles can be calculated:\n\n";
		
			var f = document.theForm;
		
			var inputLatitude        = f.inputLatitude.value       - 0;
			
			var inputPanelHeight     = f.inputPanelHeight.value    - 0;
			var inputPanelThickness  = f.inputPanelThickness.value - 0;
			var inputPanelSpacing    = f.inputPanelSpacing.value   - 0;
			var inputPanelTilt       = f.inputPanelTilt.value      - 0;
			var inputSurfaceSlope    = f.inputSurfaceSlope.value   - 0;
		
			// LATITUDE
		
			var latitudeOkay = false;
		
			if (inputLatitude.search ("^[0-9]+[dD][0-9]+[mM][0-9]+[sS]$") > -1) { latitudeOkay = true; }
			
			else { if ((inputLatitude >= 0 ) && (inputLatitude <= 90)) { latitudeOkay = true; } }
		
			if (! latitudeOkay) {
				
				error_message = error_message + "* The latitude must be between 0 and 90 degrees (click on the latitude label for details)\n";
				
				error = true;
			}
			
			// PANEL HEIGHT
		
			if (inputPanelHeight.search ("[^0-9. ]") > -1) {
		
				error_message = error_message + "* The panel height must be a number, with no units (click on the panel height label for details)\n";
				
				error = true;
			}
			
			if (inputPanelHeight == 0) {
		
				error_message = error_message + "* The panel height must be greater than zero (click on the panel height label for details)\n";
				
				error = true;
			}
			
			// PANEL THICKNESS
		
			if (inputPanelThickness.search ("[^0-9. ]") > -1) {
		
				error_message = error_message + "* The panel thickness must be a number, with no units (click on the panel thickness label for details)\n";
				
				error = true;
			}
			
			// PANEL SPACING
		
			if (inputPanelSpacing.search ("[^0-9. ]") > -1) {
		
				error_message = error_message + "* The panel spacing must be a number, with no units (click on the panel spacing label for details)\n";
				
				error = true;
			}
			
			else if (inputPanelSpacing < inputPanelThickness) {
		
				error_message = error_message + "* The panel rows must be spaced at least as far apart as their thickness (row spacing is measured from the front of one panel to the next, not the space in between)\n";
				
				error = true;
			}
			
			// PANEL TILT
		
			if (inputPanelTilt.search ("[^0-9. ]") > -1) {
		
				error_message = error_message + "* The panel tilt must be a number, with no units (click on the panel tilt label for details)\n";
				
				error = true;
			}
			
			// SURFACE SLOPE
		
			if (inputSurfaceSlope.search ("[^0-9. ]") > -1) {
		
				error_message = error_message + "* The surface slope must be a number, with no units (click on the surface slope label for details)\n";
				
				error = true;
			}
			
			// ALERT / RETURN
		
			if (error == true) { alert (error_message); }
		
			return (! error);
		}
		

	/////////////////////////////////////////////////////////
	//
	//  FUNCTION:  Compute
	//
	/////////////////////////////////////////////////////////	
 		
		function Compute () {
		
			if (1) {
			
				var feedback = '';
		
				var f = document.theForm;

		// ---------------------------------------------
		// get inputs
		// ---------------------------------------------
	
				var inputLatitude        = f.inputLatitude.value       - 0;
				var inputPanelHeight     = f.inputPanelHeight.value    - 0;
				var inputPanelThickness  = f.inputPanelThickness.value - 0;
				var inputPanelSpacing    = f.inputPanelSpacing.value   - 0;
				var inputPanelTilt       = f.inputPanelTilt.value      - 0;
				var inputSurfaceSlope    = f.inputSurfaceSlope.value   - 0;
	
				var inputOrientation = f.inputOrientation.options[f.inputOrientation.selectedIndex].value - 0;
				var inputNorthSouth  = f.inputNorthSouth.options[f.inputNorthSouth.selectedIndex].text;
				var inputLabels      = f.inputLabels.options[f.inputLabels.selectedIndex].value;

		// ---------------------------------------------
		// avoid math errors due to latitude = 0
		// ---------------------------------------------
 
				if (inputLatitude  == 0) { inputLatitude  = 0.000000001; }
		
		// ---------------------------------------------
		// correct tilt for measuring from horizontal
		// ---------------------------------------------
		
			// original calculations were from tilt from vertical, so we subtract from 90 degrees
 
				inputPanelTilt = 90 - inputPanelTilt;
		
		// ---------------------------------------------
		// latitude north-south adjustment
		// ---------------------------------------------
 
				var signedLatitude = inputLatitude;

				if (inputNorthSouth == "South") signedLatitude *= -1;

		// ---------------------------------------------
		// calculate value for each month and hour
		// ---------------------------------------------
 			
				var months = new Array ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
				
				for (var monthNum = 1; monthNum <= 12; monthNum ++) {
				
					var monthName = months[monthNum - 1];
					
					for (var hour = firstHour; hour <= lastHour; hour ++) {
					
						// get sun angles
				
						var sunAngles = new Object ();
						
						CalculateAltitudeAzimuth (sunAngles, signedLatitude, monthNum, hour);
						
						var altitudeAngle = sunAngles['altitude'] - 0;
						
						var azimuthAngle = sunAngles['azimuth'] - 0;
						
						// calculate solar-surface azimuth angle
						
						var solarSurfaceAzimuth = azimuthAngle + inputOrientation;
						
						if (solarSurfaceAzimuth > 180) {
						
							solarSurfaceAzimuth = solarSurfaceAzimuth - 360;
						}
						
						if (solarSurfaceAzimuth < -180) {
						
							solarSurfaceAzimuth = solarSurfaceAzimuth + 360;
						}
						
						// calculate angle of incidence
						
						var tiltFromHorizontal = 90 - inputPanelTilt;
						
						var angleOfIncidence = ArcCosine ((Cosine (tiltFromHorizontal) * Sine (altitudeAngle)) + (Sine (tiltFromHorizontal) * Cosine (altitudeAngle) * Cosine ((azimuthAngle + inputOrientation))));

						if (angleOfIncidence > 180) {
						
							angleOfIncidence = angleOfIncidence - 360;
						}
						
						if (angleOfIncidence < -180) {
						
							angleOfIncidence = angleOfIncidence + 360;
						}
						
							// -----------   ---------   ----------- //
							// -----------   DEBUGGING   ----------- //
							// -----------   ---------   ----------- //

						if (debug) {
						
							if ((monthNum == debugMonth) && (hour == debugHour)) {
							
								feedback = feedback 
								
									+ "<br><br><b>month: " + monthNum + "</b>" 
									+ "<br><b>hour: " + hour + "</b><br>"
								       
									+ "<br><b>altitude:</b> " + FormatFloatString (altitudeAngle) 
									+ "<br><b>azimuth:</b> " + FormatFloatString (azimuthAngle) 
									+ "<br><b>tilt</b>: " + FormatFloatString (90 - inputPanelTilt) 
									+ "<br><b>angle of incidence</b>: " + FormatFloatString (angleOfIncidence)
								;
							}
						}

						// calculate shading if sun is shining on panels
						
						if (altitudeAngle > 0) {
						
							if ((angleOfIncidence > -90) && (angleOfIncidence < 90)) {
						
								var projectedAltitudeAngle = ArcTangent (Tangent (altitudeAngle) / Cosine (solarSurfaceAzimuth));
	
								// calculate shading if shadow is from front corner of panel
								
								if (projectedAltitudeAngle < inputPanelTilt) {
								
									var sunnyPart = inputPanelSpacing * Sine (inputPanelTilt + inputSurfaceSlope) - inputPanelSpacing * Sine (90 - inputPanelTilt - inputSurfaceSlope) * Tangent (inputPanelTilt - projectedAltitudeAngle);
	
								}
								
								// calculate shading if shadow is from rear corner of panel
								
								else {
								
									var moduleRoofTilt = 90.0 - inputPanelTilt - inputSurfaceSlope;
									
									if (moduleRoofTilt == 0) {
									
										var sunnyPart = inputPanelHeight;
									}
									
									else {
									
										var frontToFront = inputPanelSpacing * Sine (moduleRoofTilt);
										
										var sunnyPart = ((frontToFront - inputPanelThickness) * Tangent (projectedAltitudeAngle - inputPanelTilt)) + (frontToFront / Tangent (moduleRoofTilt));
									}
									
									// -----------   ---------   ----------- //
									// -----------   DEBUGGING   ----------- //
									// -----------   ---------   ----------- //
	
	/*	
									if (debug) {
									
										if ((monthNum == debugMonth) && (hour == debugHour)) {
										
											feedback = feedback + "<br><b>surface slope:</b> " + FormatFloatString (inputSurfaceSlope) + "<br><b>front to front:</b> " + FormatFloatString (frontToFront) + "<br><b>sunny part:</b> " + FormatFloatString (sunnyPart) + "<br><b>module roof tilt:</b> " + FormatFloatString (moduleRoofTilt) + "<br><b>bob:</b> " + FormatFloatString (bob);
										}
									}
	*/
								}
								
								// calculate sun percentage
	
								var shadingFraction = (inputPanelHeight - sunnyPart) / inputPanelHeight;
	
								if (shadingFraction >= 1) {
								
									var sun = 0;
									
									var sunPercentage = 0;
								}
								
								else {
								
									if (shadingFraction <= 0) {
								
										var sunPercentage = 100;
									}
								
									else {
										
										var sunPercentage = 100 - ((shadingFraction + 0.005) * 100);
									}
								
									// incorporate power calculation, if requested
									
									if ((inputLabels == 'Power') || (inputLabels == 'Power_Glass')) {
	
										var powerMultiplier = Cosine (angleOfIncidence);
										
										sunPercentage = sunPercentage * powerMultiplier;
										
										// -----------   ---------   ----------- //
										// -----------   DEBUGGING   ----------- //
										// -----------   ---------   ----------- //
	
										if (debug) {
										
											if ((monthNum == debugMonth) && (hour == debugHour)) {
											
												feedback = feedback + "<br><b>sun percentage:</b> " + FormatFloatString (sunPercentage) + "<br><b>power multiplier:</b> " + FormatFloatString (powerMultiplier);
											}
										}
									
										// incorporate glass, if requested
										
										if (inputLabels == 'Power_Glass') {
										
											var glassMultiplier = Cosine (angleOfIncidence * angleOfIncidence * angleOfIncidence / 8100) - 0.05;
											
											if (glassMultiplier < 0) { glassMultiplier = 0; }
											
											sunPercentage = sunPercentage * glassMultiplier;
	
											// -----------   ---------   ----------- //
											// -----------   DEBUGGING   ----------- //
											// -----------   ---------   ----------- //
		
											if (debug) {
											
												if ((monthNum == debugMonth) && (hour == debugHour)) {
												
													feedback = feedback + "<br><b>glass multiplier:</b> " + FormatFloatString (glassMultiplier);
												}
											}
										}
									}
								}
								
								// format percentage value
								
								sunPercentage = Math.floor (sunPercentage + 0.5);
	
								// determine sun power of 10
								
								var sun = Math.floor ((sunPercentage + 5) / 10);
							}
						
							else {
							
								var sun = 'z';   // z = sun is above horizon but not shining on facade
	
								var sunPercentage = 0;
							}
						}
						
						// if sun is not shining on panels
						
						else {
						
							var sun = 'x';   // x = sun is below horizon

							var sunPercentage = 0;
						}

							// -----------   ---------   ----------- //
							// -----------   DEBUGGING   ----------- //
							// -----------   ---------   ----------- //

						if (debug) {
						
							if ((monthNum == debugMonth) && (hour == debugHour)) {
							
								feedback = feedback
								
									+ "<br><b>projected alt:</b> " + FormatFloatString (projectedAltitudeAngle) 
									+ "<br><b>shade fraction:</b> " + FormatFloatString (shadingFraction) 
									+ "<br><b>solar-surface azimuth:</b> " + FormatFloatString (solarSurfaceAzimuth)
								;
							}
						}

						// output shading to cell
						
						var id = monthName + "_" + hour;
							
						var newClass = 'sun_' + sun;
						
						ChangeClass (id, newClass);
						
						if ((sun == 'x') || (sun == 'z') || (inputLabels == 'None')) {
						
							SetDivContent (id, '&nbsp;');
						}
						
						else {

							if (inputLabels == 'Shading') {
							
								sunPercentage = 100 - sunPercentage;
							}

							sunPercentage = sunPercentage + '%';
							
							SetDivContent (id, sunPercentage);
						}
					}
				}

				if (debug) {
						
					SetDivContent ('debug', feedback);
				}
			}
		}


	/////////////////////////////////////////////////////////
	//
	//  FUNCTION:  CalculateAltitudeAzimuth
	//
	/////////////////////////////////////////////////////////
	
 		function CalculateAltitudeAzimuth (sunAngles, signedLatitude, monthNum, hour) {

			var UT = hour;

			if (monthNum > 2) {
				var correctedYear = 2007;
				var correctedMonth = monthNum - 3;
			}
			
			else {
				var correctedYear = 2007 - 1;
				var correctedMonth = monthNum  + 9;
			}
	
			var t = ((UT / 24.0) + 15 + Math.floor (30.6 * correctedMonth + 0.5) + Math.floor (365.25 * (correctedYear - 1976)) - 8707.5) / 36525.0;
	
			var G = 357.528 + 35999.05 * t;
			G = NormalizeTo360 (G);
		
			var C = (1.915 * Sine (G)) + (0.020 * Sine (2.0 * G));
			
			var L = 280.460 + (36000.770 * t) + C;
			L = NormalizeTo360 (L);
			
			var alpha = L - 2.466 * Sine (2.0 * L) + 0.053 *  Sine (4.0 * L);
			
			var GHA = UT * 15 - 180 - C + L - alpha;
			GHA = NormalizeTo360 (GHA);
	
			var obliquity = 23.4393 - 0.013 * t;
			
			var declination = ArcTangent (Tangent (obliquity) * Sine (alpha));

			var solarHourAngle = 15 * (UT - 12);
			
			var apparentSolarTime = UT;
			
			var solarMinutesAfterMidnight = hour * 60;

			var altitudeAngle = ArcSine (
				  (Sine  (signedLatitude         )  *
				   Sine  (declination            )) -
				(Cosine  (signedLatitude         )  *
				 Cosine  (declination            )  *
				 Cosine ((solarHourAngle + 180   ))));

			var preAzimuthAngle = ArcCosine (
				  (Cosine (declination           )   *
				 ((Cosine (signedLatitude        )   *
				  Tangent (declination           ))  +
				    (Sine (signedLatitude        )   *
				   Cosine ((solarHourAngle + 180 ))))) /
				   Cosine (altitudeAngle          ));
				 
			if ((solarHourAngle > 0) && (solarHourAngle < 180)) { 
			
				var azimuthAngle = (360.0 - preAzimuthAngle) + (0 - 180.0);
			}

			else { 
			
				var azimuthAngle = preAzimuthAngle + (0 - 180.0);
			}
			
 			sunAngles['altitude'] = altitudeAngle;
 			sunAngles['azimuth' ] =  azimuthAngle;
 			
 			return true;
 		}





////////////////////////////////////////////////////////////////////////////////////
//  OTHER FUNCTIONS  
////////////////////////////////////////////////////////////////////////////////////

function NoEnter () {

	return !(window.event && window.event.keyCode == 13);
}

function MinutesToClockTime (totalMinutes, amPM) {

	var theHours = Math.floor (totalMinutes / 60);
	
	var theMinutes = Math.floor (totalMinutes % 60);

	if (theMinutes < 10) theMinutes = "0" + theMinutes;  // add leading "0" if necessary
	
	if (amPM == "24 hr") {
		if (theHours < 10) theHours = "0" + theHours;  // add leading "0" if necessary
		returnString = theHours + "" + theMinutes;
	}
	
	else {
		if (theHours < 12) {
			if (theHours == 0) theHours = 12;
			returnString = theHours + ":" + theMinutes + "am";
		}
		else {
			if (theHours == 12) theHours = 24;
			returnString = (theHours - 12) + ":" + theMinutes + "pm"
		}
	}
	
	return (returnString);
}


function NormalizeTo360 (theThing) {

	return (theThing - Math.floor (theThing / 360.0) * 360);
}


function NormalizeTo180 (theThing) {

	theThing = NormalizeTo360 (theThing);

	if (theThing > 180) { theThing = theThing - 360; }

	return (theThing);
}


function NormalizeTo24 (theThing) {

	return (theThing - Math.floor (theThing / 24.0) * 24);
}


function MonthStringToDays (whichMonth) {

	if (whichMonth == "Jan") return   (0);
	if (whichMonth == "Feb") return  (31);
	if (whichMonth == "Mar") return  (59);
	if (whichMonth == "Apr") return  (90);
	if (whichMonth == "May") return (120);
	if (whichMonth == "Jun") return (151);
	if (whichMonth == "Jul") return (181);
	if (whichMonth == "Aug") return (212);
	if (whichMonth == "Sep") return (243);
	if (whichMonth == "Oct") return (273);
	if (whichMonth == "Nov") return (304);
	if (whichMonth == "Dec") return (334);
		
	return ("Zeke the Solar Cat"); // you can't get here but I swear it made me do this
}


function MonthStringToMonthNum (whichMonth) {

	if (whichMonth == "Jan") return  (1);
	if (whichMonth == "Feb") return  (2);
	if (whichMonth == "Mar") return  (3);
	if (whichMonth == "Apr") return  (4);
	if (whichMonth == "May") return  (5);
	if (whichMonth == "Jun") return  (6);
	if (whichMonth == "Jul") return  (7);
	if (whichMonth == "Aug") return  (8);
	if (whichMonth == "Sep") return  (9);
	if (whichMonth == "Oct") return (10);
	if (whichMonth == "Nov") return (11);
	if (whichMonth == "Dec") return (12);
		
	return ("Zeke the Solar Cat"); // you can't get here but I swear it made me do this
}

function RemoveSemicolon (inputString) {

	// if second or third character is a semicolon, remove it (need to
	// do better error checking on input soon)
	
	if (inputString.substring (1,2) == ":") {
		return (inputString.substring(0,1) + inputString.substring(2,4));
	}

	if (inputString.substring (2,3) == ":") {
		return (inputString.substring(0,2) + inputString.substring(3,5));
	}

	return (inputString);
}

function FormatFloatString (theInput) {

	var negativeNumber = false;
	if (theInput < 0) {
		negativeNumber = true;
		theInput *= -1;
	}

	integerPortion = Math.floor (theInput); 
	decimalPortion = Math.round (theInput * 100) % 100;

	// added 7/17/99 to correct problem with 0.999 being rounded to 0.000 in decimal conversion:
	if ((decimalPortion == 0) && ((theInput - integerPortion) > 0.5)) {
		integerPortion += 1;
	}
	
	if (integerPortion < 10) integerPortionString = " " + integerPortion;   // add a space at beginning if necessary
 	else integerPortionString = "" + integerPortion;
        
	if (decimalPortion < 10) decimalPortionString = "0" + decimalPortion;   // add a leading zero if necessary
	else decimalPortionString = "" + decimalPortion;
	
	if (negativeNumber == true) return ("-" + integerPortionString + "." + decimalPortionString);
	else return (" " + integerPortionString + "." + decimalPortionString);
}



