00001 00004 #include "complex.h" 00005 #include "solar_angles.h" 00006 00007 00008 00009 SolarAngles::SolarAngles() 00010 { 00011 } 00012 00013 SolarAngles::~SolarAngles() 00014 { 00015 } 00016 00017 /******************function eq_time****************************/ 00018 // PURPOSE: Compute equation of time (a measure of the earth's 00019 // "wobble" thoughout the year). 00020 // EXPECTS: Day of year from Jan 1. 00021 // RETURNS: Equation of time in hours. 00022 // SOURCE: Prof. Bill Murphy, Texas A&M U. 00023 // 00024 double SolarAngles::eq_time(short day_of_yr) 00025 { 00026 double rad = (2.0 * PI * day_of_yr) / 365.0; 00027 return( 00028 ( 0.5501*cos(rad) - 3.0195*cos(2*rad) - 0.0771*cos(3*rad) 00029 -7.3403*sin(rad) - 9.4583*sin(2*rad) - 0.3284*sin(3*rad) ) / 60.0 00030 ); 00031 } 00032 00033 /******************function solar_time*************************/ 00034 // PURPOSE: Compute apparent solar time given local standard (not 00035 // daylight savings) time and location. 00036 // EXPECTS: Local standard time, day of year, local standard 00037 // meridian, and local longitude. 00038 // RETURNS: Local solar time in hours (military time) 00039 // SOURCE: Duffie & Beckman. 00040 // Corrected for West negative longitudes 00041 // 00042 double SolarAngles::solar_time( 00043 double std_time, // Local standard time in decimal hours (e.g., 7:30 is 7.5) 00044 short day_of_yr, // Day of year from Jan 1 00045 double std_meridian, // Standard meridian (longitude) of local time zone (radians---e.g., Pacific timezone is 120 degrees times pi/180) 00046 double longitude // Local longitude (radians east) 00047 ) 00048 { 00049 return std_time + eq_time(day_of_yr) + HR_PER_RADIAN * (longitude-std_meridian); 00050 } 00051 00052 00053 /*****************function local_time*************************/ 00054 // PURPOSE: Compute local standard (not daylight savings) time from 00055 // apparent solar time and location. 00056 // EXPECTS: Apparent solar time, day of year, local standard 00057 // meridian, and local longitude. 00058 // RETURNS: Local solar time in decimal hours. 00059 // SOURCE: Duffie & Beckman. 00060 // 00061 double SolarAngles::local_time( 00062 double sol_time, // Local solar time (decimal hours--7:30 is 7.5) 00063 short day_of_yr, // Day of year from Jan 1 00064 double std_meridian, // Standard meridian (longitude) of local time zone (radians---e.g., Pacific timezone is 120 times pi/180) 00065 double longitude // Local longitude (radians east) 00066 ) 00067 { 00068 //std_meridian and longitude swapped to account for negative west convention (see solar_time above) 00069 //Unchecked since nothing uses local_time 00070 return sol_time - eq_time(day_of_yr) - HR_PER_RADIAN * (longitude-std_meridian); 00071 } 00072 00073 00074 /*****************function declination***********************/ 00075 // PURPOSE: Compute the solar declination angle (radians) 00076 // EXPECTS: Day of year from Jan 1. 00077 // RETURNS: Declination angle in radians. 00078 // SOURCE: Duffie & Beckman. 00079 // 00080 double SolarAngles::declination(short day_of_yr) 00081 { 00082 return( 0.409280*sin(2.0*PI*(284+day_of_yr)/365) ); 00083 } 00084 00085 00086 /******************function cos_incident******************/ 00087 // PURPOSE: Compute cosine of angle of incidence of solar beam radiation 00088 // on a surface 00089 // EXPECTS: latitude, surface slope, 00090 // surface azimuth angle, solar time, day of year. 00091 // RETURNS: Cosine of angle of incidence (angle between solar beam and 00092 // surface normal) 00093 // !!! Note that this function is ignorant of the presence !!! 00094 // !!! of the horizon. !!! 00095 // SOURCE: Duffie & Beckman. 00096 // 00097 double SolarAngles::cos_incident( 00098 double latitude, // Latitude (radians north) 00099 double slope, // Slope of surface relative to horizontal (radians) 00100 double az, // Azimuth angle of surface rel. to South (E+, W-) (radians) 00101 double sol_time, // Solar time (decimal hours) 00102 short day_of_yr // Day of year from Jan 1 00103 ) 00104 { 00105 double sindecl,cosdecl,sinlat,coslat,sinslope,cosslope,sinaz,cosaz,sinhr,coshr; // For efficiency 00106 00107 double hr_ang = -(15.0 * PI_OVER_180)*(sol_time-12.0); // morning +, afternoon - 00108 00109 double decl = declination(day_of_yr); 00110 00111 // Precalculate 00112 sindecl = sin(decl); cosdecl = cos(decl); 00113 sinlat = sin(latitude); coslat = cos(latitude); 00114 sinslope = sin(slope); cosslope = cos(slope); 00115 sinaz = sin(az); cosaz = cos(az); 00116 sinhr = sin(hr_ang); coshr = cos(hr_ang); 00117 00118 // The answer... 00119 double answer = sindecl*sinlat*cosslope 00120 -sindecl*coslat*sinslope*cosaz 00121 +cosdecl*coslat*cosslope*coshr 00122 +cosdecl*sinlat*sinslope*cosaz*coshr 00123 +cosdecl*sinslope*sinaz*sinhr; 00124 00125 // Deal with the sun being below the horizon... 00126 return(answer<0.0 ? (double)0.0 : answer); 00127 } 00128 00129 00130 00131 /******************function incident******************/ 00132 // PURPOSE: Compute the angle of incidence of solar 00133 // beam radiation on a surface. 00134 // EXPECTS: latitude, surface slope, 00135 // surface azimuth angle, solar time, day of year. 00136 // RETURNS: Angle of incidence (angle between solar beam and 00137 // surface normal) in radians. 00138 // SOURCE: Duffie & Beckman. 00139 // 00140 double SolarAngles::incident( 00141 double latitude, // Latitude (radians) 00142 double slope, // Slope of surface relative to horizontal (radians) 00143 double az, // Azimuth angle of surface rel. to South (radians, E+, W-) 00144 double sol_time, // Solar time (decimal hours) 00145 short day_of_yr // Day of year from Jan 1 00146 ) 00147 { 00148 return acos(cos_incident(latitude, slope, az, sol_time, day_of_yr)); 00149 } 00150 00151 00152 /*****************function zenith*****************************/ 00153 // PURPOSE: Compute the solar zenith angle (radians, the angle between solar beam 00154 // and the veritical. This could be calculated as the incident 00155 // angle on a horizontal surface, but is more efficient with 00156 // this dedicated function. 00157 // EXPECTS: Day of year, latitude, solar time. 00158 // RETURNS: Zenith angle in radians. 00159 // SOURCE: Duffy & Beckman. 00160 // 00161 double SolarAngles::zenith( 00162 short day_of_yr, // day of year from Jan 1 00163 double latitude, // latitude (radians) 00164 double sol_time // solar time (decimal hours) 00165 ) 00166 { 00167 double hr_ang = -(15.0 * PI_OVER_180)*(sol_time-12.0); // morning +, afternoon - 00168 00169 double decl = declination(day_of_yr); 00170 00171 return acos(sin(decl)*sin(latitude) + cos(decl)*cos(latitude)*cos(hr_ang)); 00172 } 00173 00174 00175 00176 /*****************function altitude*****************************/ 00177 // PURPOSE: Compute the solar altitude angle (radians, angle between solar beam 00178 // and the veritical. This could be calculated as the incident 00179 // angle on a vertical surface, but is more efficient with 00180 // this dedicated function. 00181 // EXPECTS: Day of year, latitude, solar time. 00182 // RETURNS: Altitude angle (angle above horizon) in radians. 00183 // SOURCE: Duffy & Beckman. 00184 // 00185 double SolarAngles::altitude( 00186 short day_of_yr, // day of year from Jan 1 00187 double latitude, // latitude (radians) 00188 double sol_time // solar time (hours) 00189 ) 00190 { 00191 return (90.0 * PI_OVER_180) - zenith(day_of_yr,latitude,sol_time); 00192 } 00193 00194 00195 00196 /*****************function hr_sunrise*****************************/ 00197 // PURPOSE: Compute the hour of sunrise. 00198 // EXPECTS: Declination angle, latitude. 00199 // RETURNS: Solar hour of sunrise. 00200 // SOURCE: Duffy & Beckman. 00201 // 00202 double SolarAngles::hr_sunrise( 00203 short day_of_yr, // solar declination angle (radians) 00204 double latitude // latitude (radians) 00205 ) 00206 { 00207 // Calculate hour angle of sunrise (converted to degrees) 00208 double hr_ang = acos( -tan(latitude)*tan(declination(day_of_yr)) ) / PI_OVER_180; 00209 00210 // Convert hour angle to solar time 00211 return( (-hr_ang/15.0) + 12.0 ); 00212 } 00213 00214 00215 00216 /*****************function day_len********************************/ 00217 // PURPOSE: Compute the length of a given day. 00218 // EXPECTS: Day of year, latitude. 00219 // RETURNS: Day length (hours). 00220 // SOURCE: Duffy & Beckman. 00221 // 00222 double SolarAngles::day_len( 00223 short day_of_yr, // Day of year from Jan 1 00224 double latitude // latitude (radians) 00225 ) 00226 { 00227 return 2.0 * acos( -tan(latitude)*tan(declination(day_of_yr)) ) / (15.0 * PI_OVER_180); 00228 } 00229 00230 00231 00232 /*****************function day_of_yr********************************/ 00233 // PURPOSE: Compute julian day of year. 00234 // EXPECTS: Month, day, year, all as integers. 00235 // RETURNS: Day length (hours). 00236 // 00237 // This ignores leap years but that's okay...the other functions always 00238 // assume a year has only 365 days. 00239 // 00240 short SolarAngles::day_of_yr( 00241 short month, // Month of year (1-12) 00242 short day // Day of month (1-31) 00243 ) 00244 { 00245 00246 // TODO: Error checking on the 1-12 and 1-31... 00247 00248 return days_thru_month[month-1] + day; 00249 } 00250 00251 //sjin: add solar elevation funcion 00252 double SolarAngles::elevation( 00253 short day_of_yr, // day of year from Jan 1 00254 double latitude, // latitude (radians) 00255 double sol_time // solar time (decimal hours) 00256 ) 00257 { 00258 double hr_ang = -(15.0 * PI_OVER_180)*(sol_time-12.0); // morning +, afternoon - 00259 00260 double decl = declination(day_of_yr); 00261 00262 return asin(sin(decl)*sin(latitude) + cos(decl)*cos(latitude)*cos(hr_ang)); 00263 } 00264 00265 // TODO: CDC - Fix this function; this function does not compute azimuth correctly 00266 double SolarAngles::azimuth( 00267 short day_of_yr, // day of year from Jan 1 00268 double latitude, // latitude (radians) 00269 double sol_time // solar time (decimal hours) 00270 ) 00271 { 00272 00273 double hr_ang = -(15.0 * PI_OVER_180)*(sol_time-12.0); // morning +, afternoon - 00274 00275 double decl = declination(day_of_yr); 00276 00277 double alpha = (90.0 * PI_OVER_180) - latitude + decl; 00278 00279 double rs = (sin(decl)*cos(latitude) - cos(decl)*sin(latitude)*cos(hr_ang))/cos(alpha); 00280 if(rs > 1.0) { 00281 rs = 1.0; 00282 } else if(rs < -1.0) { 00283 rs = -1.0; 00284 } 00285 return acos( rs ); 00286 } 00287 00288 //Most additions below here are from the NREL Solar Position algorithm 2.0 00289 //http://rredc.nrel.gov/solar/codesandalgorithms/solpos/aboutsolpos.html 00290 //Perez model function at the end (perez_tilt) extracted from referenced paper 00291 /*============================================================================ 00292 * Contains: 00293 * S_solpos (computes solar position and intensity 00294 * from time and place) 00295 * 00296 * INPUTS: (via posdata struct) year, daynum, hour, 00297 * minute, second, latitude, longitude, timezone, 00298 * intervl 00299 * OPTIONAL: (via posdata struct) month, day, press, temp, tilt, 00300 * aspect, function 00301 * OUTPUTS: EVERY variable in the SOLPOS_POSDATA 00302 * (defined in solpos.h) 00303 * 00304 * NOTE: Certain conditions exist during which some of 00305 * the output variables are undefined or cannot be 00306 * calculated. In these cases, the variables are 00307 * returned with flag values indicating such. In other 00308 * cases, the variables may return a realistic, though 00309 * invalid, value. These variables and the flag values 00310 * or invalid conditions are listed below: 00311 * 00312 * amass -1.0 at zenetr angles greater than 93.0 00313 * degrees 00314 * ampress -1.0 at zenetr angles greater than 93.0 00315 * degrees 00316 * azim invalid at zenetr angle 0.0 or latitude 00317 * +/-90.0 or at night 00318 * elevetr limited to -9 degrees at night 00319 * etr 0.0 at night 00320 * etrn 0.0 at night 00321 * etrtilt 0.0 when cosinc is less than 0 00322 * prime invalid at zenetr angles greater than 93.0 00323 * degrees 00324 * sretr +/- 2999.0 during periods of 24 hour sunup or 00325 * sundown 00326 * ssetr +/- 2999.0 during periods of 24 hour sunup or 00327 * sundown 00328 * ssha invalid at the North and South Poles 00329 * unprime invalid at zenetr angles greater than 93.0 00330 * degrees 00331 * zenetr limited to 99.0 degrees at night 00332 * 00333 * S_init (optional initialization for all input parameters in 00334 * the posdata struct) 00335 * INPUTS: SOLPOS_POSDATA* 00336 * OUTPUTS: SOLPOS_POSDATA* 00337 * 00338 * (Note: initializes the required S_solpos INPUTS above 00339 * to out-of-bounds conditions, forcing the user to 00340 * supply the parameters; initializes the OPTIONAL 00341 * S_solpos inputs above to nominal values.) 00342 * 00343 * S_decode (optional utility for decoding the S_solpos return code) 00344 * INPUTS: long integer S_solpos return value, SOLPOS_POSDATA* 00345 * OUTPUTS: text to stderr 00346 * 00347 * Usage: 00348 * In calling program, just after other 'includes', insert: 00349 * 00350 * #include "solpos00.h" 00351 * 00352 * Function calls: 00353 * S_init(SOLPOS_POSDATA*) [optional] 00354 * . 00355 * . 00356 * [set time and location parameters before S_solpos call] 00357 * . 00358 * . 00359 * int retval = S_solpos(SOLPOS_POSDATA*) 00360 * S_decode(int retval, SOLPOS_POSDATA*) [optional] 00361 * (Note: you should always look at the S_solpos return 00362 * value, which contains error codes. S_decode is one option 00363 * for examining these codes. It can also serve as a 00364 * template for building your own application-specific 00365 * decoder.) 00366 * 00367 * Martin Rymes 00368 * National Renewable Energy Laboratory 00369 * 25 March 1998 00370 * 00371 * 27 April 1999 REVISION: Corrected leap year in S_date. 00372 * 13 January 2000 REVISION: SMW converted to structure posdata parameter 00373 * and subdivided into functions. 00374 * 01 February 2001 REVISION: SMW corrected ecobli calculation 00375 * (changed sign). Error is small (max 0.015 deg 00376 * in calculation of declination angle) 00377 *----------------------------------------------------------------------------*/ 00378 00379 /*============================================================================ 00380 * Long integer function S_solpos, adapted from the VAX solar libraries 00381 * 00382 * This function calculates the apparent solar position and the 00383 * intensity of the sun (theoretical maximum solar energy) from 00384 * time and place on Earth. 00385 * 00386 * Requires (from the SOLPOS_POSDATA parameter): 00387 * Date and time: 00388 * year 00389 * daynum (requirement depends on the S_DOY switch) 00390 * month (requirement depends on the S_DOY switch) 00391 * day (requirement depends on the S_DOY switch) 00392 * hour 00393 * minute 00394 * second 00395 * interval DEFAULT 0 00396 * Location: 00397 * latitude 00398 * longitude 00399 * Location/time adjuster: 00400 * timezone 00401 * Atmospheric pressure and temperature: 00402 * press DEFAULT 1013.0 mb 00403 * temp DEFAULT 10.0 degrees C 00404 * Tilt of flat surface that receives solar energy: 00405 * aspect DEFAULT 180 (South) 00406 * tilt DEFAULT 0 (Horizontal) 00407 * Function Switch (codes defined in solpos.h) 00408 * function DEFAULT S_ALL 00409 * 00410 * Returns (via the SOLPOS_POSDATA parameter): 00411 * everything defined in the SOLPOS_POSDATA in solpos.h. 00412 *----------------------------------------------------------------------------*/ 00413 int SolarAngles::S_solpos(SOLPOS_POSDATA *pdat) 00414 { 00415 SOLPOS_TRIGDATA trigdat, *tdat; 00416 00417 tdat = &trigdat; // point to the structure 00418 00419 // initialize the trig structure 00420 trigdat.sd = -999.0; // flag to force calculation of trig data 00421 trigdat.cd = 1.0; 00422 trigdat.ch = 1.0; // set the rest of these to something safe 00423 trigdat.cl = 1.0; 00424 trigdat.sl = 1.0; 00425 00426 doy2dom(pdat); /* convert input doy to month-day */ 00427 geometry(pdat); /* do basic geometry calculations */ 00428 zen_no_ref(pdat,tdat); /* etr at non-refracted zenith angle */ 00429 ssha(pdat,tdat); /* Sunset hour calculation */ 00430 sbcf(pdat,tdat); /* Shadowband correction factor */ 00431 tst(pdat); /* true solar time */ 00432 srss(pdat); /* sunrise/sunset calculations */ 00433 sazm(pdat,tdat); /* solar azimuth calculations */ 00434 refrac(pdat); /* atmospheric refraction calculations */ 00435 amass(pdat); /* airmass calculations */ 00436 prime(pdat); /* kt-prime/unprime calculations */ 00437 etr(pdat); /* ETR and ETRN (refracted) */ 00438 tilt(pdat); /* tilt calculations */ 00439 perez_tilt(pdat); /* Perez tilt calculations */ 00440 00441 return 0; 00442 } 00443 00444 /*============================================================================ 00445 * Void function S_init 00446 * 00447 * This function initiates all of the input parameters in the struct 00448 * posdata passed to S_solpos(). Initialization is either to nominal 00449 * values or to out of range values, which forces the calling program to 00450 * specify parameters. 00451 * 00452 * NOTE: This function is optional if you initialize ALL input parameters 00453 * in your calling code. Note that the required parameters of date 00454 * and location are deliberately initialized out of bounds to force 00455 * the user to enter real-world values. 00456 * 00457 * Requires: Pointer to a posdata structure, members of which are 00458 * initialized. 00459 * 00460 * Returns: Void 00461 *----------------------------------------------------------------------------*/ 00462 void SolarAngles::S_init(SOLPOS_POSDATA *pdat) 00463 { 00464 pdat->day = -99; // Day of month (May 27 = 27, etc.) 00465 pdat->daynum = -999; // Day number (day of year; Feb 1 = 32 ) 00466 pdat->hour = -99; // Hour of day, 0 - 23 00467 pdat->minute = -99; // Minute of hour, 0 - 59 00468 pdat->month = -99; // Month number (Jan = 1, Feb = 2, etc.) 00469 pdat->second = -99; // Second of minute, 0 - 59 00470 pdat->year = -99; // 4-digit year 00471 pdat->interval = 0; // instantaneous measurement interval 00472 pdat->aspect = PI; // Azimuth of panel surface (direction it 00473 // faces) N=0, E=90, S=180, W=270 - converted to radians 00474 pdat->latitude = -99.0; // Latitude, degrees north (south negative) 00475 pdat->longitude = -999.0; // Longitude, degrees east (west negative) 00476 pdat->press = 1013.0; // Surface pressure, millibars 00477 pdat->solcon = 126.998456; // Solar constant, 1367 W/sq m - 126.998456; W/sq ft 00478 pdat->temp = 15.0; // Ambient dry-bulb temperature, degrees C 00479 pdat->tilt = 0.0; // Radians tilt from horizontal of panel 00480 pdat->timezone = -99.0; // Time zone, east (west negative). 00481 pdat->sbwid = 7.6; // Eppley shadow band width 00482 pdat->sbrad = 31.7; // Eppley shadow band radius 00483 pdat->sbsky = 0.04; // Drummond factor for partly cloudy skies 00484 pdat->perez_horz = 1.0; // By default, perez_horz is full diffuse 00485 } 00486 00487 /*============================================================================ 00488 * Local void function doy2dom 00489 * 00490 * This function computes the month/day from the day number. 00491 * 00492 * Requires (from SOLPOS_POSDATA parameter): 00493 * Year and day number: 00494 * year 00495 * daynum 00496 * 00497 * Returns (via the SOLPOS_POSDATA parameter): 00498 * year 00499 * month 00500 * day 00501 *----------------------------------------------------------------------------*/ 00502 void SolarAngles::doy2dom(SOLPOS_POSDATA *pdat) 00503 { 00504 int imon; // Month (month_days) array counter 00505 int leap; // leap year switch 00506 00507 // Set the leap year switch 00508 if ( ((pdat->year % 4) == 0) && 00509 ( ((pdat->year % 100) != 0) || ((pdat->year % 400) == 0) ) ) 00510 leap = 1; 00511 else 00512 leap = 0; 00513 00514 // Find the month 00515 imon = 12; 00516 while ( pdat->daynum <= month_days [leap][imon] ) 00517 --imon; 00518 00519 // Set the month and day of month 00520 pdat->month = imon; 00521 pdat->day = pdat->daynum - month_days[leap][imon]; 00522 } 00523 00524 00525 /*============================================================================ 00526 * Local Void function geometry 00527 * 00528 * Does the underlying geometry for a given time and location 00529 *----------------------------------------------------------------------------*/ 00530 void SolarAngles::geometry ( SOLPOS_POSDATA *pdat ) 00531 { 00532 double bottom; // denominator (bottom) of the fraction 00533 double c2; // cosine of d2 00534 double cd; // cosine of the day angle or delination 00535 double d2; // pdat->dayang times two 00536 double delta; // difference between current year and 1949 00537 double s2; // sine of d2 00538 double sd; // sine of the day angle 00539 double top; // numerator (top) of the fraction 00540 int leap; // leap year counter 00541 00542 /* Day angle */ 00543 /* Iqbal, M. 1983. An Introduction to Solar Radiation. 00544 Academic Press, NY., page 3 */ 00545 pdat->dayang = 2.0 * PI * ( pdat->daynum - 1 ) / 365.0; 00546 00547 /* Earth radius vector * solar constant = solar energy */ 00548 /* Spencer, J. W. 1971. Fourier series representation of the 00549 position of the sun. Search 2 (5), page 172 */ 00550 sd = sin (pdat->dayang); 00551 cd = cos (pdat->dayang); 00552 d2 = 2.0 * pdat->dayang; 00553 c2 = cos (d2); 00554 s2 = sin (d2); 00555 00556 pdat->erv = 1.000110 + 0.034221 * cd + 0.001280 * sd; 00557 pdat->erv += 0.000719 * c2 + 0.000077 * s2; 00558 00559 /* Universal Coordinated (Greenwich standard) time */ 00560 /* Michalsky, J. 1988. The Astronomical Almanac's algorithm for 00561 approximate solar position (1950-2050). Solar Energy 40 (3), 00562 pp. 227-235. */ 00563 pdat->utime = 00564 pdat->hour * 3600.0 + 00565 pdat->minute * 60.0 + 00566 pdat->second - 00567 (double)pdat->interval / 2.0; 00568 pdat->utime = pdat->utime / 3600.0 - pdat->timezone; 00569 00570 /* Julian Day minus 2,400,000 days (to eliminate roundoff errors) */ 00571 /* Michalsky, J. 1988. The Astronomical Almanac's algorithm for 00572 approximate solar position (1950-2050). Solar Energy 40 (3), 00573 pp. 227-235. */ 00574 00575 /* No adjustment for century non-leap years since this function is 00576 bounded by 1950 - 2050 */ 00577 delta = pdat->year - 1949; 00578 leap = (int) ( delta / 4.0 ); 00579 pdat->julday = 00580 32916.5 + delta * 365.0 + leap + pdat->daynum + pdat->utime / 24.0; 00581 00582 /* Time used in the calculation of ecliptic coordinates */ 00583 /* Noon 1 JAN 2000 = 2,400,000 + 51,545 days Julian Date */ 00584 /* Michalsky, J. 1988. The Astronomical Almanac's algorithm for 00585 approximate solar position (1950-2050). Solar Energy 40 (3), 00586 pp. 227-235. */ 00587 pdat->ectime = pdat->julday - 51545.0; 00588 00589 /* Mean longitude */ 00590 /* Michalsky, J. 1988. The Astronomical Almanac's algorithm for 00591 approximate solar position (1950-2050). Solar Energy 40 (3), 00592 pp. 227-235. */ 00593 pdat->mnlong = 280.460 + 0.9856474 * pdat->ectime; 00594 00595 /* (dump the multiples of 360, so the answer is between 0 and 360) */ 00596 pdat->mnlong -= 360.0 * (int) ( pdat->mnlong / 360.0 ); 00597 if ( pdat->mnlong < 0.0 ) 00598 pdat->mnlong += 360.0; 00599 00600 /* Mean anomaly */ 00601 /* Michalsky, J. 1988. The Astronomical Almanac's algorithm for 00602 approximate solar position (1950-2050). Solar Energy 40 (3), 00603 pp. 227-235. */ 00604 pdat->mnanom = 357.528 + 0.9856003 * pdat->ectime; 00605 00606 /* (dump the multiples of 360, so the answer is between 0 and 360) */ 00607 pdat->mnanom -= 360.0 * (int) ( pdat->mnanom / 360.0 ); 00608 if ( pdat->mnanom < 0.0 ) 00609 pdat->mnanom += 360.0; 00610 00611 /* Ecliptic longitude */ 00612 /* Michalsky, J. 1988. The Astronomical Almanac's algorithm for 00613 approximate solar position (1950-2050). Solar Energy 40 (3), 00614 pp. 227-235. */ 00615 pdat->eclong = pdat->mnlong + 1.915 * sin ( pdat->mnanom * raddeg ) + 00616 0.020 * sin ( 2.0 * pdat->mnanom * raddeg ); 00617 00618 /* (dump the multiples of 360, so the answer is between 0 and 360) */ 00619 pdat->eclong -= 360.0 * (int) ( pdat->eclong / 360.0 ); 00620 if ( pdat->eclong < 0.0 ) 00621 pdat->eclong += 360.0; 00622 00623 /* Obliquity of the ecliptic */ 00624 /* Michalsky, J. 1988. The Astronomical Almanac's algorithm for 00625 approximate solar position (1950-2050). Solar Energy 40 (3), 00626 pp. 227-235. */ 00627 00628 /* 02 Feb 2001 SMW corrected sign in the following line */ 00629 /* pdat->ecobli = 23.439 + 4.0e-07 * pdat->ectime; */ 00630 pdat->ecobli = 23.439 - 4.0e-07 * pdat->ectime; 00631 00632 /* Declination */ 00633 /* Michalsky, J. 1988. The Astronomical Almanac's algorithm for 00634 approximate solar position (1950-2050). Solar Energy 40 (3), 00635 pp. 227-235. */ 00636 pdat->declin = asin(sin(pdat->ecobli*raddeg)*sin(pdat->eclong*raddeg)); 00637 00638 /* Right ascension */ 00639 /* Michalsky, J. 1988. The Astronomical Almanac's algorithm for 00640 approximate solar position (1950-2050). Solar Energy 40 (3), 00641 pp. 227-235. */ 00642 top = cos ( raddeg * pdat->ecobli ) * sin ( raddeg * pdat->eclong ); 00643 bottom = cos ( raddeg * pdat->eclong ); 00644 00645 pdat->rascen = degrad * atan2 ( top, bottom ); 00646 00647 /* (make it a positive angle) */ 00648 if ( pdat->rascen < 0.0 ) 00649 pdat->rascen += 360.0; 00650 00651 /* Greenwich mean sidereal time */ 00652 /* Michalsky, J. 1988. The Astronomical Almanac's algorithm for 00653 approximate solar position (1950-2050). Solar Energy 40 (3), 00654 pp. 227-235. */ 00655 pdat->gmst = 6.697375 + 0.0657098242 * pdat->ectime + pdat->utime; 00656 00657 /* (dump the multiples of 24, so the answer is between 0 and 24) */ 00658 pdat->gmst -= 24.0 * (int) ( pdat->gmst / 24.0 ); 00659 if ( pdat->gmst < 0.0 ) 00660 pdat->gmst += 24.0; 00661 00662 /* Local mean sidereal time */ 00663 /* Michalsky, J. 1988. The Astronomical Almanac's algorithm for 00664 approximate solar position (1950-2050). Solar Energy 40 (3), 00665 pp. 227-235. */ 00666 pdat->lmst = pdat->gmst * 15.0 + pdat->longitude; 00667 00668 /* (dump the multiples of 360, so the answer is between 0 and 360) */ 00669 pdat->lmst -= 360.0 * (int) ( pdat->lmst / 360.0 ); 00670 if ( pdat->lmst < 0.) 00671 pdat->lmst += 360.0; 00672 00673 /* Hour angle */ 00674 /* Michalsky, J. 1988. The Astronomical Almanac's algorithm for 00675 approximate solar position (1950-2050). Solar Energy 40 (3), 00676 pp. 227-235. */ 00677 pdat->hrang = pdat->lmst - pdat->rascen; 00678 00679 /* (force it between -180 and 180 degrees) */ 00680 if ( pdat->hrang < -180.0 ) 00681 pdat->hrang += 360.0; 00682 else if ( pdat->hrang > 180.0 ) 00683 pdat->hrang -= 360.0; 00684 } 00685 00686 00687 /*============================================================================ 00688 * Local Void function zen_no_ref 00689 * 00690 * ETR solar zenith angle 00691 * Iqbal, M. 1983. An Introduction to Solar Radiation. 00692 * Academic Press, NY., page 15 00693 *----------------------------------------------------------------------------*/ 00694 void SolarAngles::zen_no_ref ( SOLPOS_POSDATA *pdat, SOLPOS_TRIGDATA *tdat ) 00695 { 00696 double cz; // cosine of the solar zenith angle 00697 00698 tdat->cd = cos(pdat->declin); 00699 tdat->ch = cos(raddeg*pdat->hrang); 00700 tdat->cl = cos(pdat->latitude); 00701 tdat->sd = sin(pdat->declin); 00702 tdat->sl = sin(pdat->latitude); 00703 00704 cz = tdat->sd * tdat->sl + tdat->cd * tdat->cl * tdat->ch; 00705 00706 // (watch out for the roundoff errors) 00707 if ( fabs (cz) > 1.0 ) { 00708 if ( cz >= 0.0 ) 00709 cz = 1.0; 00710 else 00711 cz = -1.0; 00712 } 00713 00714 pdat->zenetr = acos ( cz ) * degrad; 00715 00716 // (limit the degrees below the horizon to 9 [+90 -> 99]) 00717 if ( pdat->zenetr > 99.0 ) 00718 pdat->zenetr = 99.0; 00719 00720 pdat->elevetr = 90.0 - pdat->zenetr; 00721 } 00722 00723 00724 /*============================================================================ 00725 * Local Void function ssha 00726 * 00727 * Sunset hour angle, degrees 00728 * Iqbal, M. 1983. An Introduction to Solar Radiation. 00729 * Academic Press, NY., page 16 00730 *----------------------------------------------------------------------------*/ 00731 void SolarAngles::ssha( SOLPOS_POSDATA *pdat, SOLPOS_TRIGDATA *tdat ) 00732 { 00733 double cssha; // cosine of the sunset hour angle 00734 double cdcl; // ( cd * cl ) 00735 00736 cdcl = tdat->cd * tdat->cl; 00737 00738 if ( fabs ( cdcl ) >= 0.001 ) { 00739 cssha = -tdat->sl * tdat->sd / cdcl; 00740 00741 // This keeps the cosine from blowing on roundoff 00742 if ( cssha < -1.0 ) 00743 pdat->ssha = 180.0; 00744 else if ( cssha > 1.0 ) 00745 pdat->ssha = 0.0; 00746 else 00747 pdat->ssha = degrad * acos ( cssha ); 00748 } 00749 else if ( ((pdat->declin >= 0.0) && (pdat->latitude > 0.0 )) || 00750 ((pdat->declin < 0.0) && (pdat->latitude < 0.0 )) ) 00751 pdat->ssha = 180.0; 00752 else 00753 pdat->ssha = 0.0; 00754 } 00755 00756 00757 /*============================================================================ 00758 * Local Void function sbcf 00759 * 00760 * Shadowband correction factor 00761 * Drummond, A. J. 1956. A contribution to absolute pyrheliometry. 00762 * Q. J. R. Meteorol. Soc. 82, pp. 481-493 00763 *----------------------------------------------------------------------------*/ 00764 void SolarAngles::sbcf( SOLPOS_POSDATA *pdat, SOLPOS_TRIGDATA *tdat ) 00765 { 00766 double p, t1, t2; // used to compute sbcf 00767 00768 p = 0.6366198 * pdat->sbwid / pdat->sbrad * pow (tdat->cd,3); 00769 t1 = tdat->sl * tdat->sd * pdat->ssha * raddeg; 00770 t2 = tdat->cl * tdat->cd * sin ( pdat->ssha * raddeg ); 00771 pdat->sbcf = pdat->sbsky + 1.0 / ( 1.0 - p * ( t1 + t2 ) ); 00772 00773 } 00774 00775 00776 /*============================================================================ 00777 * Local Void function tst 00778 * 00779 * TST -> True Solar Time = local standard time + TSTfix, time 00780 * in minutes from midnight. 00781 * Iqbal, M. 1983. An Introduction to Solar Radiation. 00782 * Academic Press, NY., page 13 00783 *----------------------------------------------------------------------------*/ 00784 void SolarAngles::tst( SOLPOS_POSDATA *pdat ) 00785 { 00786 pdat->tst = ( 180.0 + pdat->hrang ) * 4.0; 00787 pdat->tstfix = 00788 pdat->tst - 00789 (double)pdat->hour * 60.0 - 00790 pdat->minute - 00791 (double)pdat->second / 60.0 + 00792 (double)pdat->interval / 120.0; // add back half of the interval 00793 00794 // bound tstfix to this day 00795 while ( pdat->tstfix > 720.0 ) 00796 pdat->tstfix -= 1440.0; 00797 while ( pdat->tstfix < -720.0 ) 00798 pdat->tstfix += 1440.0; 00799 00800 pdat->eqntim = 00801 pdat->tstfix + 60.0 * pdat->timezone - 4.0 * pdat->longitude; 00802 00803 } 00804 00805 00806 /*============================================================================ 00807 * Local Void function srss 00808 * 00809 * Sunrise and sunset times (minutes from midnight) 00810 *----------------------------------------------------------------------------*/ 00811 void SolarAngles::srss( SOLPOS_POSDATA *pdat ) 00812 { 00813 if ( pdat->ssha <= 1.0 ) { 00814 pdat->sretr = 2999.0; 00815 pdat->ssetr = -2999.0; 00816 } 00817 else if ( pdat->ssha >= 179.0 ) { 00818 pdat->sretr = -2999.0; 00819 pdat->ssetr = 2999.0; 00820 } 00821 else { 00822 pdat->sretr = 720.0 - 4.0 * pdat->ssha - pdat->tstfix; 00823 pdat->ssetr = 720.0 + 4.0 * pdat->ssha - pdat->tstfix; 00824 } 00825 } 00826 00827 00828 /*============================================================================ 00829 * Local Void function sazm 00830 * 00831 * Solar azimuth angle 00832 * Iqbal, M. 1983. An Introduction to Solar Radiation. 00833 * Academic Press, NY., page 15 00834 *----------------------------------------------------------------------------*/ 00835 void SolarAngles::sazm( SOLPOS_POSDATA *pdat, SOLPOS_TRIGDATA *tdat ) 00836 { 00837 double ca; // cosine of the solar azimuth angle 00838 double ce; // cosine of the solar elevation 00839 double cecl; // ( ce * cl ) 00840 double se; // sine of the solar elevation 00841 00842 ce = cos ( raddeg * pdat->elevetr ); 00843 se = sin ( raddeg * pdat->elevetr ); 00844 00845 pdat->azim = 180.0; 00846 cecl = ce * tdat->cl; 00847 if ( fabs ( cecl ) >= 0.001 ) { 00848 ca = ( se * tdat->sl - tdat->sd ) / cecl; 00849 if ( ca > 1.0 ) 00850 ca = 1.0; 00851 else if ( ca < -1.0 ) 00852 ca = -1.0; 00853 00854 pdat->azim = 180.0 - acos ( ca ) * degrad; 00855 if ( pdat->hrang > 0 ) 00856 pdat->azim = 360.0 - pdat->azim; 00857 } 00858 } 00859 00860 00861 /*============================================================================ 00862 * Local Int function refrac 00863 * 00864 * Refraction correction, degrees 00865 * Zimmerman, John C. 1981. Sun-pointing programs and their 00866 * accuracy. 00867 * SAND81-0761, Experimental Systems Operation Division 4721, 00868 * Sandia National Laboratories, Albuquerque, NM. 00869 *----------------------------------------------------------------------------*/ 00870 void SolarAngles::refrac( SOLPOS_POSDATA *pdat ) 00871 { 00872 double prestemp; // temporary pressure/temperature correction 00873 double refcor; // temporary refraction correction 00874 double tanelev; // tangent of the solar elevation angle 00875 00876 // If the sun is near zenith, the algorithm bombs; refraction near 0 00877 if ( pdat->elevetr > 85.0 ) 00878 refcor = 0.0; 00879 00880 // Otherwise, we have refraction 00881 else { 00882 tanelev = tan ( raddeg * pdat->elevetr ); 00883 if ( pdat->elevetr >= 5.0 ) 00884 refcor = 58.1 / tanelev - 00885 0.07 / ( pow (tanelev,3) ) + 00886 0.000086 / ( pow (tanelev,5) ); 00887 else if ( pdat->elevetr >= -0.575 ) 00888 refcor = 1735.0 + 00889 pdat->elevetr * ( -518.2 + pdat->elevetr * ( 103.4 + 00890 pdat->elevetr * ( -12.79 + pdat->elevetr * 0.711 ) ) ); 00891 else 00892 refcor = -20.774 / tanelev; 00893 00894 prestemp = 00895 ( pdat->press * 283.0 ) / ( 1013.0 * ( 273.0 + pdat->temp ) ); 00896 refcor *= prestemp / 3600.0; 00897 } 00898 00899 // Refracted solar elevation angle 00900 pdat->elevref = pdat->elevetr + refcor; 00901 00902 // (limit the degrees below the horizon to 9) 00903 if ( pdat->elevref < -9.0 ) 00904 pdat->elevref = -9.0; 00905 00906 // Refracted solar zenith angle 00907 pdat->zenref = 90.0 - pdat->elevref; 00908 pdat->coszen = cos( raddeg * pdat->zenref ); 00909 } 00910 00911 00912 /*============================================================================ 00913 * Local Void function amass 00914 * 00915 * Airmass 00916 * Kasten, F. and Young, A. 1989. Revised optical air mass 00917 * tables and approximation formula. Applied Optics 28 (22), 00918 * pp. 4735-4738 00919 *----------------------------------------------------------------------------*/ 00920 void SolarAngles::amass( SOLPOS_POSDATA *pdat ) 00921 { 00922 if ( pdat->zenref > 93.0 ) 00923 { 00924 pdat->amass = -1.0; 00925 pdat->ampress = -1.0; 00926 } 00927 else 00928 { 00929 pdat->amass = 00930 1.0 / ( cos (raddeg * pdat->zenref) + 0.50572 * 00931 pow ((96.07995 - pdat->zenref),-1.6364) ); 00932 00933 pdat->ampress = pdat->amass * pdat->press / 1013.0; 00934 } 00935 } 00936 00937 /*============================================================================ 00938 * Local Void function prime 00939 * 00940 * Prime and Unprime 00941 * Prime converts Kt to normalized Kt', etc. 00942 * Unprime deconverts Kt' to Kt, etc. 00943 * Perez, R., P. Ineichen, Seals, R., & Zelenka, A. 1990. Making 00944 * full use of the clearness index for parameterizing hourly 00945 * insolation conditions. Solar Energy 45 (2), pp. 111-114 00946 *----------------------------------------------------------------------------*/ 00947 void SolarAngles::prime( SOLPOS_POSDATA *pdat ) 00948 { 00949 pdat->unprime = 1.031 * exp ( -1.4 / ( 0.9 + 9.4 / pdat->amass ) ) + 0.1; 00950 pdat->prime = 1.0 / pdat->unprime; 00951 } 00952 00953 /*============================================================================ 00954 * Local Void function etr 00955 * 00956 * Extraterrestrial (top-of-atmosphere) solar irradiance 00957 *----------------------------------------------------------------------------*/ 00958 void SolarAngles::etr( SOLPOS_POSDATA *pdat ) 00959 { 00960 if ( pdat->coszen > 0.0 ) { 00961 pdat->etrn = pdat->solcon * pdat->erv; 00962 pdat->etr = pdat->etrn * pdat->coszen; 00963 } 00964 else { 00965 pdat->etrn = 0.0; 00966 pdat->etr = 0.0; 00967 } 00968 } 00969 00970 /*============================================================================ 00971 * Local Void function tilt 00972 * 00973 * ETR on a tilted surface 00974 *----------------------------------------------------------------------------*/ 00975 void SolarAngles::tilt( SOLPOS_POSDATA *pdat ) 00976 { 00977 double ca; // cosine of the solar azimuth angle 00978 double cp; // cosine of the panel aspect 00979 double ct; // cosine of the panel tilt 00980 double sa; // sine of the solar azimuth angle 00981 double sp; // sine of the panel aspect 00982 double st; // sine of the panel tilt 00983 double sz; // sine of the refraction corrected solar zenith angle 00984 00985 00986 // Cosine of the angle between the sun and a tipped flat surface, 00987 // useful for calculating solar energy on tilted surfaces 00988 ca = cos ( raddeg * pdat->azim ); 00989 cp = cos (pdat->aspect); 00990 ct = cos (pdat->tilt); 00991 sa = sin ( raddeg * pdat->azim ); 00992 sp = sin (pdat->aspect); 00993 st = sin (pdat->tilt); 00994 sz = sin ( raddeg * pdat->zenref ); 00995 pdat->cosinc = pdat->coszen * ct + sz * st * ( ca * cp + sa * sp ); 00996 00997 if ( pdat->cosinc > 0.0 ) 00998 pdat->etrtilt = pdat->etrn * pdat->cosinc; 00999 else 01000 pdat->etrtilt = 0.0; 01001 01002 } 01003 01004 //Perez diffuse radiation tilt model 01005 //Extracted from Perez et al., "Modeling Dayling Availability and Irradiance Components from Direct and 01006 //Global Irradiance," Solar Energy, vol. 44, no. 7, pp. 271-289, 1990. 01007 void SolarAngles::perez_tilt( SOLPOS_POSDATA *pdat ) 01008 { 01009 double zen_rad, zen_rad_cubed, a_val, b_val; 01010 short index; 01011 01012 //See if there's even any diffuse radiation to care about - i.e., is it not night 01013 if (pdat->diff_horz != 0.0) 01014 { 01015 //Put the zenith angle into radians so it works with the Perez model 01016 zen_rad = pdat->zenref * PI_OVER_180; 01017 01018 //Make a cube of it and scale it since we'll need it in a couple places 01019 //Uses constant of 1.041 for zenith in radians (per Perez et al. 1990) 01020 zen_rad_cubed = zen_rad * zen_rad * zen_rad * 1.041; 01021 01022 //Compute sky clearness - if there's no direct on the surface, it's 1.0 (save some calculations) 01023 if (pdat->dir_norm != 0.0) 01024 pdat->perez_skyclear = ((pdat->diff_horz + pdat->dir_norm)/pdat->diff_horz + zen_rad_cubed)/(1 + zen_rad_cubed); 01025 else 01026 pdat->perez_skyclear = 1.0; 01027 01028 //Flag the index 01029 pdat->perez_skyclear_idx = -1; 01030 01031 //Index out the sky clearness 01032 for (index=0; index<7; index++) 01033 { 01034 if ((pdat->perez_skyclear>=perez_clearness_limits[index]) && (pdat->perez_skyclear<perez_clearness_limits[index+1])) 01035 { 01036 pdat->perez_skyclear_idx = index; //Indices are offset by one since they'll be used as indices later 01037 break; //Get us out once we're assigned 01038 } 01039 } 01040 01041 //If exited and is still -1, means it must be "clear" (index region 8) 01042 if (pdat->perez_skyclear_idx == -1) 01043 pdat->perez_skyclear_idx = 7; //0 referened - 8th bin 01044 01045 //Calculate the sky brightness 01046 if (pdat->etrn == 0.0) 01047 pdat->perez_brightness = 0.0; 01048 else 01049 pdat->perez_brightness = (pdat->diff_horz*pdat->amass/pdat->etrn); 01050 01051 //Now calculate the F1 and F2 coefficients 01052 pdat->perez_F1 = perez_tilt_coeff_F1[0][pdat->perez_skyclear_idx] + perez_tilt_coeff_F1[1][pdat->perez_skyclear_idx]*pdat->perez_brightness + perez_tilt_coeff_F1[2][pdat->perez_skyclear_idx]*zen_rad; 01053 pdat->perez_F2 = perez_tilt_coeff_F2[0][pdat->perez_skyclear_idx] + perez_tilt_coeff_F2[1][pdat->perez_skyclear_idx]*pdat->perez_brightness + perez_tilt_coeff_F2[2][pdat->perez_skyclear_idx]*zen_rad; 01054 01055 //Maximum check 01056 if (pdat->perez_F1 < 0) 01057 pdat->perez_F1 = 0.0; 01058 01059 //Now compute the Perez horizontal scalar (to be applied to diffuse) - get a & b values first 01060 if (pdat->cosinc > 0.0) 01061 a_val = pdat->cosinc; 01062 else 01063 a_val = 0.0; 01064 01065 if (pdat->coszen > COS85DEG) 01066 b_val = pdat->coszen; 01067 else 01068 b_val = COS85DEG; 01069 01070 //Compute the scalar 01071 pdat->perez_horz = ((1 - pdat->perez_F1)*(1 + cos(pdat->tilt))/2.0 + pdat->perez_F1*a_val/b_val + pdat->perez_F2*sin(pdat->tilt)); 01072 } 01073 else //Must be night, just assign a zero constant 01074 pdat->perez_horz = 0.0; 01075 01076 }