solpos00.c 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997
  1. /*============================================================================
  2. * Contains:
  3. * S_solpos (computes solar position and intensity
  4. * from time and place)
  5. *
  6. * INPUTS: (via posdata struct) year, daynum, hour,
  7. * minute, second, latitude, longitude, timezone,
  8. * intervl
  9. * OPTIONAL: (via posdata struct) month, day, press, temp, tilt,
  10. * aspect, function
  11. * OUTPUTS: EVERY variable in the struct posdata
  12. * (defined in solpos.h)
  13. *
  14. * NOTE: Certain conditions exist during which some of
  15. * the output variables are undefined or cannot be
  16. * calculated. In these cases, the variables are
  17. * returned with flag values indicating such. In other
  18. * cases, the variables may return a realistic, though
  19. * invalid, value. These variables and the flag values
  20. * or invalid conditions are listed below:
  21. *
  22. * amass -1.0 at zenetr angles greater than 93.0
  23. * degrees
  24. * ampress -1.0 at zenetr angles greater than 93.0
  25. * degrees
  26. * azim invalid at zenetr angle 0.0 or latitude
  27. * +/-90.0 or at night
  28. * elevetr limited to -9 degrees at night
  29. * etr 0.0 at night
  30. * etrn 0.0 at night
  31. * etrtilt 0.0 when cosinc is less than 0
  32. * prime invalid at zenetr angles greater than 93.0
  33. * degrees
  34. * sretr +/- 2999.0 during periods of 24 hour sunup or
  35. * sundown
  36. * ssetr +/- 2999.0 during periods of 24 hour sunup or
  37. * sundown
  38. * ssha invalid at the North and South Poles
  39. * unprime invalid at zenetr angles greater than 93.0
  40. * degrees
  41. * zenetr limited to 99.0 degrees at night
  42. *
  43. * S_init (optional initialization for all input parameters in
  44. * the posdata struct)
  45. * INPUTS: struct posdata*
  46. * OUTPUTS: struct posdata*
  47. *
  48. * (Note: initializes the required S_solpos INPUTS above
  49. * to out-of-bounds conditions, forcing the user to
  50. * supply the parameters; initializes the OPTIONAL
  51. * S_solpos inputs above to nominal values.)
  52. *
  53. * S_decode (optional utility for decoding the S_solpos return code)
  54. * INPUTS: long integer S_solpos return value, struct posdata*
  55. * OUTPUTS: text to stderr
  56. *
  57. * Usage:
  58. * In calling program, just after other 'includes', insert:
  59. *
  60. * #include "solpos.h"
  61. *
  62. * Function calls:
  63. * S_init(struct posdata*) [optional]
  64. * .
  65. * .
  66. * [set time and location parameters before S_solpos call]
  67. * .
  68. * .
  69. * int retval = S_solpos(struct posdata*)
  70. * S_decode(int retval, struct posdata*) [optional]
  71. * (Note: you should always look at the S_solpos return
  72. * value, which contains error codes. S_decode is one option
  73. * for examining these codes. It can also serve as a
  74. * template for building your own application-specific
  75. * decoder.)
  76. *
  77. * Martin Rymes
  78. * National Renewable Energy Laboratory
  79. * 25 March 1998
  80. *
  81. * 27 April 1999 REVISION: Corrected leap year in S_date.
  82. * 13 January 2000 REVISION: SMW converted to structure posdata parameter
  83. * and subdivided into functions.
  84. * 01 February 2001 REVISION: SMW corrected ecobli calculation
  85. * (changed sign). Error is small (max 0.015 deg
  86. * in calculation of declination angle)
  87. * 11 April 2001 REVISION: SMW corrected parenthesis grouping in
  88. * validation() function for pressure and
  89. * shadowband. As it was, the validation
  90. * routine would have checked for and reported
  91. * high limit errors when the functions had not
  92. * been selected.
  93. *----------------------------------------------------------------------------*/
  94. #include <math.h>
  95. #include <string.h>
  96. #include <stdio.h>
  97. #include <grass/gis.h>
  98. #include <grass/glocale.h>
  99. #include "solpos00.h"
  100. /*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  101. *
  102. * Structures defined for this module
  103. *
  104. *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
  105. struct trigdata /* used to pass calculated values locally */
  106. {
  107. float cd; /* cosine of the declination */
  108. float ch; /* cosine of the hour angle */
  109. float cl; /* cosine of the latitude */
  110. float sd; /* sine of the declination */
  111. float sl; /* sine of the latitude */
  112. };
  113. /*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  114. *
  115. * Temporary global variables used only in this file:
  116. *
  117. *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
  118. static int month_days[2][13] = { {0, 0, 31, 59, 90, 120, 151,
  119. 181, 212, 243, 273, 304, 334},
  120. {0, 0, 31, 60, 91, 121, 152,
  121. 182, 213, 244, 274, 305, 335}
  122. };
  123. /* cumulative number of days prior to beginning of month */
  124. /* TODO: the "degrad" - "raddeg" variable names are flipped - typo or severe bug?? */
  125. static float degrad = 57.295779513; /* converts from radians to degrees */
  126. static float raddeg = 0.0174532925; /* converts from degrees to radians */
  127. /*============================================================================
  128. * Local function prototypes
  129. ============================================================================*/
  130. static long int validate(struct posdata *pdat);
  131. static void dom2doy(struct posdata *pdat);
  132. static void doy2dom(struct posdata *pdat);
  133. static void geometry(struct posdata *pdat);
  134. static void zen_no_ref(struct posdata *pdat, struct trigdata *tdat);
  135. static void ssha(struct posdata *pdat, struct trigdata *tdat);
  136. static void sbcf(struct posdata *pdat, struct trigdata *tdat);
  137. static void tst(struct posdata *pdat);
  138. static void srss(struct posdata *pdat);
  139. static void sazm(struct posdata *pdat, struct trigdata *tdat);
  140. static void refrac(struct posdata *pdat);
  141. static void amass(struct posdata *pdat);
  142. static void prime(struct posdata *pdat);
  143. static void etr(struct posdata *pdat);
  144. static void tilt(struct posdata *pdat);
  145. static void localtrig(struct posdata *pdat, struct trigdata *tdat);
  146. /*============================================================================
  147. * Long integer function S_solpos, adapted from the VAX solar libraries
  148. *
  149. * This function calculates the apparent solar position and the
  150. * intensity of the sun (theoretical maximum solar energy) from
  151. * time and place on Earth.
  152. *
  153. * Requires (from the struct posdata parameter):
  154. * Date and time:
  155. * year
  156. * daynum (requirement depends on the S_DOY switch)
  157. * month (requirement depends on the S_DOY switch)
  158. * day (requirement depends on the S_DOY switch)
  159. * hour
  160. * minute
  161. * second
  162. * interval DEFAULT 0
  163. * Location:
  164. * latitude
  165. * longitude
  166. * Location/time adjuster:
  167. * timezone
  168. * Atmospheric pressure and temperature:
  169. * press DEFAULT 1013.0 mb
  170. * temp DEFAULT 10.0 degrees C
  171. * Tilt of flat surface that receives solar energy:
  172. * aspect DEFAULT 180 (South)
  173. * tilt DEFAULT 0 (Horizontal)
  174. * Function Switch (codes defined in solpos.h)
  175. * function DEFAULT S_ALL
  176. *
  177. * Returns (via the struct posdata parameter):
  178. * everything defined in the struct posdata in solpos.h.
  179. *----------------------------------------------------------------------------*/
  180. long S_solpos(struct posdata *pdat)
  181. {
  182. long int retval;
  183. struct trigdata trigdat, *tdat;
  184. tdat = &trigdat; /* point to the structure */
  185. /* initialize the trig structure */
  186. tdat->sd = -999.0; /* flag to force calculation of trig data */
  187. tdat->cd = 1.0;
  188. tdat->ch = 1.0; /* set the rest of these to something safe */
  189. tdat->cl = 1.0;
  190. tdat->sl = 1.0;
  191. if ((retval = validate(pdat)) != 0) /* validate the inputs */
  192. return retval;
  193. if (pdat->function & L_DOY)
  194. doy2dom(pdat); /* convert input doy to month-day */
  195. else
  196. dom2doy(pdat); /* convert input month-day to doy */
  197. if (pdat->function & L_GEOM)
  198. geometry(pdat); /* do basic geometry calculations */
  199. if (pdat->function & L_ZENETR) /* etr at non-refracted zenith angle */
  200. zen_no_ref(pdat, tdat);
  201. if (pdat->function & L_SSHA) /* Sunset hour calculation */
  202. ssha(pdat, tdat);
  203. if (pdat->function & L_SBCF) /* Shadowband correction factor */
  204. sbcf(pdat, tdat);
  205. if (pdat->function & L_TST) /* true solar time */
  206. tst(pdat);
  207. if (pdat->function & L_SRSS) /* sunrise/sunset calculations */
  208. srss(pdat);
  209. if (pdat->function & L_SOLAZM) /* solar azimuth calculations */
  210. sazm(pdat, tdat);
  211. if (pdat->function & L_REFRAC) /* atmospheric refraction calculations */
  212. refrac(pdat);
  213. if (pdat->function & L_AMASS) /* airmass calculations */
  214. amass(pdat);
  215. if (pdat->function & L_PRIME) /* kt-prime/unprime calculations */
  216. prime(pdat);
  217. if (pdat->function & L_ETR) /* ETR and ETRN (refracted) */
  218. etr(pdat);
  219. if (pdat->function & L_TILT) /* tilt calculations */
  220. tilt(pdat);
  221. return 0;
  222. }
  223. /*============================================================================
  224. * Void function S_init
  225. *
  226. * This function initiates all of the input parameters in the struct
  227. * posdata passed to S_solpos(). Initialization is either to nominal
  228. * values or to out of range values, which forces the calling program to
  229. * specify parameters.
  230. *
  231. * NOTE: This function is optional if you initialize ALL input parameters
  232. * in your calling code. Note that the required parameters of date
  233. * and location are deliberately initialized out of bounds to force
  234. * the user to enter real-world values.
  235. *
  236. * Requires: Pointer to a posdata structure, members of which are
  237. * initialized.
  238. *
  239. * Returns: Void
  240. *----------------------------------------------------------------------------*/
  241. void S_init(struct posdata *pdat)
  242. {
  243. pdat->day = -99; /* Day of month (May 27 = 27, etc.) */
  244. pdat->daynum = -999; /* Day number (day of year; Feb 1 = 32 ) */
  245. pdat->hour = -99; /* Hour of day, 0 - 23 */
  246. pdat->minute = -99; /* Minute of hour, 0 - 59 */
  247. pdat->month = -99; /* Month number (Jan = 1, Feb = 2, etc.) */
  248. pdat->second = -99; /* Second of minute, 0 - 59 */
  249. pdat->year = -99; /* 4-digit year */
  250. pdat->interval = 0; /* instantaneous measurement interval */
  251. pdat->aspect = 180.0; /* Azimuth of panel surface (direction it
  252. faces) N=0, E=90, S=180, W=270 */
  253. pdat->latitude = -99.0; /* Latitude, degrees north (south negative) */
  254. pdat->longitude = -999.0; /* Longitude, degrees east (west negative) */
  255. pdat->press = 1013.0; /* Surface pressure, millibars */
  256. pdat->solcon = 1367.0; /* Solar constant, 1367 W/sq m */
  257. pdat->temp = 15.0; /* Ambient dry-bulb temperature, degrees C */
  258. pdat->tilt = 0.0; /* Degrees tilt from horizontal of panel */
  259. pdat->timezone = -99.0; /* Time zone, east (west negative). */
  260. pdat->sbwid = 7.6; /* Eppley shadow band width */
  261. pdat->sbrad = 31.7; /* Eppley shadow band radius */
  262. pdat->sbsky = 0.04; /* Drummond factor for partly cloudy skies */
  263. pdat->function = S_ALL; /* compute all parameters */
  264. }
  265. /*============================================================================
  266. * Local long int function validate
  267. *
  268. * Validates the input parameters
  269. *----------------------------------------------------------------------------*/
  270. static long int validate(struct posdata *pdat)
  271. {
  272. long int retval = 0; /* start with no errors */
  273. /* No absurd dates, please. */
  274. if (pdat->function & L_GEOM) {
  275. if ((pdat->year < 1950) || (pdat->year > 2050)) /* limits of algoritm */
  276. retval |= (1L << S_YEAR_ERROR);
  277. if (!(pdat->function & S_DOY) &&
  278. ((pdat->month < 1) || (pdat->month > 12)))
  279. retval |= (1L << S_MONTH_ERROR);
  280. if (!(pdat->function & S_DOY) &&
  281. ((pdat->day < 1) || (pdat->day > 31)))
  282. retval |= (1L << S_DAY_ERROR);
  283. if ((pdat->function & S_DOY) &&
  284. ((pdat->daynum < 1) || (pdat->daynum > 366)))
  285. retval |= (1L << S_DOY_ERROR);
  286. /* No absurd times, please. */
  287. if ((pdat->hour < 0) || (pdat->hour > 24))
  288. retval |= (1L << S_HOUR_ERROR);
  289. if ((pdat->minute < 0) || (pdat->minute > 59))
  290. retval |= (1L << S_MINUTE_ERROR);
  291. if ((pdat->second < 0) || (pdat->second > 59))
  292. retval |= (1L << S_SECOND_ERROR);
  293. if ((pdat->hour == 24) && (pdat->minute > 0)) /* no more than 24 hrs */
  294. retval |= ((1L << S_HOUR_ERROR) | (1L << S_MINUTE_ERROR));
  295. if ((pdat->hour == 24) && (pdat->second > 0)) /* no more than 24 hrs */
  296. retval |= ((1L << S_HOUR_ERROR) | (1L << S_SECOND_ERROR));
  297. if (fabs(pdat->timezone) > 12.0)
  298. retval |= (1L << S_TZONE_ERROR);
  299. if ((pdat->interval < 0) || (pdat->interval > 28800))
  300. retval |= (1L << S_INTRVL_ERROR);
  301. /* No absurd locations, please. */
  302. if (fabs(pdat->longitude) > 180.0)
  303. retval |= (1L << S_LON_ERROR);
  304. if (fabs(pdat->latitude) > 90.0)
  305. retval |= (1L << S_LAT_ERROR);
  306. }
  307. /* No silly temperatures or pressures, please. */
  308. if ((pdat->function & L_REFRAC) && (fabs(pdat->temp) > 100.0))
  309. retval |= (1L << S_TEMP_ERROR);
  310. if ((pdat->function & L_REFRAC) &&
  311. ((pdat->press < 0.0) || (pdat->press > 2000.0)))
  312. retval |= (1L << S_PRESS_ERROR);
  313. /* No out of bounds tilts, please */
  314. if ((pdat->function & L_TILT) && (fabs(pdat->tilt) > 180.0))
  315. retval |= (1L << S_TILT_ERROR);
  316. if ((pdat->function & L_TILT) && (fabs(pdat->aspect) > 360.0))
  317. retval |= (1L << S_ASPECT_ERROR);
  318. /* No oddball shadowbands, please */
  319. if ((pdat->function & L_SBCF) &&
  320. ((pdat->sbwid < 1.0) || (pdat->sbwid > 100.0)))
  321. retval |= (1L << S_SBWID_ERROR);
  322. if ((pdat->function & L_SBCF) &&
  323. ((pdat->sbrad < 1.0) || (pdat->sbrad > 100.0)))
  324. retval |= (1L << S_SBRAD_ERROR);
  325. if ((pdat->function & L_SBCF) && (fabs(pdat->sbsky) > 1.0))
  326. retval |= (1L << S_SBSKY_ERROR);
  327. return retval;
  328. }
  329. /*============================================================================
  330. * Local Void function dom2doy
  331. *
  332. * Converts day-of-month to day-of-year
  333. *
  334. * Requires (from struct posdata parameter):
  335. * year
  336. * month
  337. * day
  338. *
  339. * Returns (via the struct posdata parameter):
  340. * year
  341. * daynum
  342. *----------------------------------------------------------------------------*/
  343. static void dom2doy(struct posdata *pdat)
  344. {
  345. pdat->daynum = pdat->day + month_days[0][pdat->month];
  346. /* (adjust for leap year) */
  347. if (((pdat->year % 4) == 0) &&
  348. (((pdat->year % 100) != 0) || ((pdat->year % 400) == 0)) &&
  349. (pdat->month > 2))
  350. pdat->daynum += 1;
  351. }
  352. /*============================================================================
  353. * Local void function doy2dom
  354. *
  355. * This function computes the month/day from the day number.
  356. *
  357. * Requires (from struct posdata parameter):
  358. * Year and day number:
  359. * year
  360. * daynum
  361. *
  362. * Returns (via the struct posdata parameter):
  363. * year
  364. * month
  365. * day
  366. *----------------------------------------------------------------------------*/
  367. static void doy2dom(struct posdata *pdat)
  368. {
  369. int imon; /* Month (month_days) array counter */
  370. int leap; /* leap year switch */
  371. /* Set the leap year switch */
  372. if (((pdat->year % 4) == 0) &&
  373. (((pdat->year % 100) != 0) || ((pdat->year % 400) == 0)))
  374. leap = 1;
  375. else
  376. leap = 0;
  377. /* Find the month */
  378. imon = 12;
  379. while (pdat->daynum <= month_days[leap][imon])
  380. --imon;
  381. /* Set the month and day of month */
  382. pdat->month = imon;
  383. pdat->day = pdat->daynum - month_days[leap][imon];
  384. }
  385. /*============================================================================
  386. * Local Void function geometry
  387. *
  388. * Does the underlying geometry for a given time and location
  389. *----------------------------------------------------------------------------*/
  390. static void geometry(struct posdata *pdat)
  391. {
  392. float bottom; /* denominator (bottom) of the fraction */
  393. float c2; /* cosine of d2 */
  394. float cd; /* cosine of the day angle or delination */
  395. float d2; /* pdat->dayang times two */
  396. float delta; /* difference between current year and 1949 */
  397. float s2; /* sine of d2 */
  398. float sd; /* sine of the day angle */
  399. float top; /* numerator (top) of the fraction */
  400. int leap; /* leap year counter */
  401. /* Day angle */
  402. /* Iqbal, M. 1983. An Introduction to Solar Radiation.
  403. Academic Press, NY., page 3 */
  404. pdat->dayang = 360.0 * (pdat->daynum - 1) / 365.0;
  405. /* Earth radius vector * solar constant = solar energy */
  406. /* Spencer, J. W. 1971. Fourier series representation of the
  407. position of the sun. Search 2 (5), page 172 */
  408. sd = sin(raddeg * pdat->dayang);
  409. cd = cos(raddeg * pdat->dayang);
  410. d2 = 2.0 * pdat->dayang;
  411. c2 = cos(raddeg * d2);
  412. s2 = sin(raddeg * d2);
  413. pdat->erv = 1.000110 + 0.034221 * cd + 0.001280 * sd;
  414. pdat->erv += 0.000719 * c2 + 0.000077 * s2;
  415. /* Universal Coordinated (Greenwich standard) time */
  416. /* Michalsky, J. 1988. The Astronomical Almanac's algorithm for
  417. approximate solar position (1950-2050). Solar Energy 40 (3),
  418. pp. 227-235. */
  419. pdat->utime =
  420. pdat->hour * 3600.0 +
  421. pdat->minute * 60.0 + pdat->second - (float)pdat->interval / 2.0;
  422. pdat->utime = pdat->utime / 3600.0 - pdat->timezone;
  423. /* Julian Day minus 2,400,000 days (to eliminate roundoff errors) */
  424. /* Michalsky, J. 1988. The Astronomical Almanac's algorithm for
  425. approximate solar position (1950-2050). Solar Energy 40 (3),
  426. pp. 227-235. */
  427. /* No adjustment for century non-leap years since this function is
  428. bounded by 1950 - 2050 */
  429. delta = pdat->year - 1949;
  430. leap = (int)(delta / 4.0);
  431. pdat->julday =
  432. 32916.5 + delta * 365.0 + leap + pdat->daynum + pdat->utime / 24.0;
  433. /* Time used in the calculation of ecliptic coordinates */
  434. /* Noon 1 JAN 2000 = 2,400,000 + 51,545 days Julian Date */
  435. /* Michalsky, J. 1988. The Astronomical Almanac's algorithm for
  436. approximate solar position (1950-2050). Solar Energy 40 (3),
  437. pp. 227-235. */
  438. pdat->ectime = pdat->julday - 51545.0;
  439. /* Mean longitude */
  440. /* Michalsky, J. 1988. The Astronomical Almanac's algorithm for
  441. approximate solar position (1950-2050). Solar Energy 40 (3),
  442. pp. 227-235. */
  443. pdat->mnlong = 280.460 + 0.9856474 * pdat->ectime;
  444. /* (dump the multiples of 360, so the answer is between 0 and 360) */
  445. pdat->mnlong -= 360.0 * (int)(pdat->mnlong / 360.0);
  446. if (pdat->mnlong < 0.0)
  447. pdat->mnlong += 360.0;
  448. /* Mean anomaly */
  449. /* Michalsky, J. 1988. The Astronomical Almanac's algorithm for
  450. approximate solar position (1950-2050). Solar Energy 40 (3),
  451. pp. 227-235. */
  452. pdat->mnanom = 357.528 + 0.9856003 * pdat->ectime;
  453. /* (dump the multiples of 360, so the answer is between 0 and 360) */
  454. pdat->mnanom -= 360.0 * (int)(pdat->mnanom / 360.0);
  455. if (pdat->mnanom < 0.0)
  456. pdat->mnanom += 360.0;
  457. /* Ecliptic longitude */
  458. /* Michalsky, J. 1988. The Astronomical Almanac's algorithm for
  459. approximate solar position (1950-2050). Solar Energy 40 (3),
  460. pp. 227-235. */
  461. pdat->eclong = pdat->mnlong + 1.915 * sin(pdat->mnanom * raddeg) +
  462. 0.020 * sin(2.0 * pdat->mnanom * raddeg);
  463. /* (dump the multiples of 360, so the answer is between 0 and 360) */
  464. pdat->eclong -= 360.0 * (int)(pdat->eclong / 360.0);
  465. if (pdat->eclong < 0.0)
  466. pdat->eclong += 360.0;
  467. /* Obliquity of the ecliptic */
  468. /* Michalsky, J. 1988. The Astronomical Almanac's algorithm for
  469. approximate solar position (1950-2050). Solar Energy 40 (3),
  470. pp. 227-235. */
  471. /* 02 Feb 2001 SMW corrected sign in the following line */
  472. /* pdat->ecobli = 23.439 + 4.0e-07 * pdat->ectime; */
  473. pdat->ecobli = 23.439 - 4.0e-07 * pdat->ectime;
  474. /* Declination */
  475. /* Michalsky, J. 1988. The Astronomical Almanac's algorithm for
  476. approximate solar position (1950-2050). Solar Energy 40 (3),
  477. pp. 227-235. */
  478. pdat->declin = degrad * asin(sin(pdat->ecobli * raddeg) *
  479. sin(pdat->eclong * raddeg));
  480. /* Right ascension */
  481. /* Michalsky, J. 1988. The Astronomical Almanac's algorithm for
  482. approximate solar position (1950-2050). Solar Energy 40 (3),
  483. pp. 227-235. */
  484. top = cos(raddeg * pdat->ecobli) * sin(raddeg * pdat->eclong);
  485. bottom = cos(raddeg * pdat->eclong);
  486. pdat->rascen = degrad * atan2(top, bottom);
  487. /* (make it a positive angle) */
  488. if (pdat->rascen < 0.0)
  489. pdat->rascen += 360.0;
  490. /* Greenwich mean sidereal time */
  491. /* Michalsky, J. 1988. The Astronomical Almanac's algorithm for
  492. approximate solar position (1950-2050). Solar Energy 40 (3),
  493. pp. 227-235. */
  494. pdat->gmst = 6.697375 + 0.0657098242 * pdat->ectime + pdat->utime;
  495. /* (dump the multiples of 24, so the answer is between 0 and 24) */
  496. pdat->gmst -= 24.0 * (int)(pdat->gmst / 24.0);
  497. if (pdat->gmst < 0.0)
  498. pdat->gmst += 24.0;
  499. /* Local mean sidereal time */
  500. /* Michalsky, J. 1988. The Astronomical Almanac's algorithm for
  501. approximate solar position (1950-2050). Solar Energy 40 (3),
  502. pp. 227-235. */
  503. pdat->lmst = pdat->gmst * 15.0 + pdat->longitude;
  504. /* (dump the multiples of 360, so the answer is between 0 and 360) */
  505. pdat->lmst -= 360.0 * (int)(pdat->lmst / 360.0);
  506. if (pdat->lmst < 0.)
  507. pdat->lmst += 360.0;
  508. /* Hour angle */
  509. /* Michalsky, J. 1988. The Astronomical Almanac's algorithm for
  510. approximate solar position (1950-2050). Solar Energy 40 (3),
  511. pp. 227-235. */
  512. pdat->hrang = pdat->lmst - pdat->rascen;
  513. /* (force it between -180 and 180 degrees) */
  514. if (pdat->hrang < -180.0)
  515. pdat->hrang += 360.0;
  516. else if (pdat->hrang > 180.0)
  517. pdat->hrang -= 360.0;
  518. }
  519. /*============================================================================
  520. * Local Void function zen_no_ref
  521. *
  522. * ETR solar zenith angle
  523. * Iqbal, M. 1983. An Introduction to Solar Radiation.
  524. * Academic Press, NY., page 15
  525. *----------------------------------------------------------------------------*/
  526. static void zen_no_ref(struct posdata *pdat, struct trigdata *tdat)
  527. {
  528. float cz; /* cosine of the solar zenith angle */
  529. localtrig(pdat, tdat);
  530. cz = tdat->sd * tdat->sl + tdat->cd * tdat->cl * tdat->ch;
  531. /* (watch out for the roundoff errors) */
  532. if (fabs(cz) > 1.0) {
  533. if (cz >= 0.0)
  534. cz = 1.0;
  535. else
  536. cz = -1.0;
  537. }
  538. pdat->zenetr = acos(cz) * degrad;
  539. /* (limit the degrees below the horizon to 9 [+90 -> 99]) */
  540. if (pdat->zenetr > 99.0)
  541. pdat->zenetr = 99.0;
  542. pdat->elevetr = 90.0 - pdat->zenetr;
  543. }
  544. /*============================================================================
  545. * Local Void function ssha
  546. *
  547. * Sunset hour angle, degrees
  548. * Iqbal, M. 1983. An Introduction to Solar Radiation.
  549. * Academic Press, NY., page 16
  550. *----------------------------------------------------------------------------*/
  551. static void ssha(struct posdata *pdat, struct trigdata *tdat)
  552. {
  553. float cssha; /* cosine of the sunset hour angle */
  554. float cdcl; /* ( cd * cl ) */
  555. localtrig(pdat, tdat);
  556. cdcl = tdat->cd * tdat->cl;
  557. if (fabs(cdcl) >= 0.001) {
  558. cssha = -tdat->sl * tdat->sd / cdcl;
  559. /* This keeps the cosine from blowing on roundoff */
  560. if (cssha < -1.0)
  561. pdat->ssha = 180.0;
  562. else if (cssha > 1.0)
  563. pdat->ssha = 0.0;
  564. else
  565. pdat->ssha = degrad * acos(cssha);
  566. }
  567. else if (((pdat->declin >= 0.0) && (pdat->latitude > 0.0)) ||
  568. ((pdat->declin < 0.0) && (pdat->latitude < 0.0)))
  569. pdat->ssha = 180.0;
  570. else
  571. pdat->ssha = 0.0;
  572. }
  573. /*============================================================================
  574. * Local Void function sbcf
  575. *
  576. * Shadowband correction factor
  577. * Drummond, A. J. 1956. A contribution to absolute pyrheliometry.
  578. * Q. J. R. Meteorol. Soc. 82, pp. 481-493
  579. *----------------------------------------------------------------------------*/
  580. static void sbcf(struct posdata *pdat, struct trigdata *tdat)
  581. {
  582. float p, t1, t2; /* used to compute sbcf */
  583. localtrig(pdat, tdat);
  584. p = 0.6366198 * pdat->sbwid / pdat->sbrad * pow(tdat->cd, 3);
  585. t1 = tdat->sl * tdat->sd * pdat->ssha * raddeg;
  586. t2 = tdat->cl * tdat->cd * sin(pdat->ssha * raddeg);
  587. pdat->sbcf = pdat->sbsky + 1.0 / (1.0 - p * (t1 + t2));
  588. }
  589. /*============================================================================
  590. * Local Void function tst
  591. *
  592. * TST -> True Solar Time = local standard time + TSTfix, time
  593. * in minutes from midnight.
  594. * Iqbal, M. 1983. An Introduction to Solar Radiation.
  595. * Academic Press, NY., page 13
  596. *----------------------------------------------------------------------------*/
  597. static void tst(struct posdata *pdat)
  598. {
  599. pdat->tst = (180.0 + pdat->hrang) * 4.0;
  600. pdat->tstfix = pdat->tst - (float)pdat->hour * 60.0 - pdat->minute - (float)pdat->second / 60.0 + (float)pdat->interval / 120.0; /* add back half of the interval */
  601. /* bound tstfix to this day */
  602. while (pdat->tstfix > 720.0)
  603. pdat->tstfix -= 1440.0;
  604. while (pdat->tstfix < -720.0)
  605. pdat->tstfix += 1440.0;
  606. pdat->eqntim =
  607. pdat->tstfix + 60.0 * pdat->timezone - 4.0 * pdat->longitude;
  608. }
  609. /*============================================================================
  610. * Local Void function srss
  611. *
  612. * Sunrise and sunset times (minutes from midnight)
  613. *----------------------------------------------------------------------------*/
  614. static void srss(struct posdata *pdat)
  615. {
  616. if (pdat->ssha <= 1.0) {
  617. pdat->sretr = 2999.0;
  618. pdat->ssetr = -2999.0;
  619. }
  620. else if (pdat->ssha >= 179.0) {
  621. pdat->sretr = -2999.0;
  622. pdat->ssetr = 2999.0;
  623. }
  624. else {
  625. pdat->sretr = 720.0 - 4.0 * pdat->ssha - pdat->tstfix;
  626. pdat->ssetr = 720.0 + 4.0 * pdat->ssha - pdat->tstfix;
  627. }
  628. }
  629. /*============================================================================
  630. * Local Void function sazm
  631. *
  632. * Solar azimuth angle
  633. * Iqbal, M. 1983. An Introduction to Solar Radiation.
  634. * Academic Press, NY., page 15
  635. *----------------------------------------------------------------------------*/
  636. static void sazm(struct posdata *pdat, struct trigdata *tdat)
  637. {
  638. float ca; /* cosine of the solar azimuth angle */
  639. float ce; /* cosine of the solar elevation */
  640. float cecl; /* ( ce * cl ) */
  641. float se; /* sine of the solar elevation */
  642. localtrig(pdat, tdat);
  643. ce = cos(raddeg * pdat->elevetr);
  644. se = sin(raddeg * pdat->elevetr);
  645. pdat->azim = 180.0;
  646. cecl = ce * tdat->cl;
  647. if (fabs(cecl) >= 0.001) {
  648. ca = (se * tdat->sl - tdat->sd) / cecl;
  649. if (ca > 1.0)
  650. ca = 1.0;
  651. else if (ca < -1.0)
  652. ca = -1.0;
  653. pdat->azim = 180.0 - acos(ca) * degrad;
  654. if (pdat->hrang > 0)
  655. pdat->azim = 360.0 - pdat->azim;
  656. }
  657. }
  658. /*============================================================================
  659. * Local Int function refrac
  660. *
  661. * Refraction correction, degrees
  662. * Zimmerman, John C. 1981. Sun-pointing programs and their
  663. * accuracy.
  664. * SAND81-0761, Experimental Systems Operation Division 4721,
  665. * Sandia National Laboratories, Albuquerque, NM.
  666. *----------------------------------------------------------------------------*/
  667. static void refrac(struct posdata *pdat)
  668. {
  669. float prestemp; /* temporary pressure/temperature correction */
  670. float refcor; /* temporary refraction correction */
  671. float tanelev; /* tangent of the solar elevation angle */
  672. /* If the sun is near zenith, the algorithm bombs; refraction near 0 */
  673. if (pdat->elevetr > 85.0)
  674. refcor = 0.0;
  675. /* Otherwise, we have refraction */
  676. else {
  677. tanelev = tan(raddeg * pdat->elevetr);
  678. if (pdat->elevetr >= 5.0)
  679. refcor = 58.1 / tanelev -
  680. 0.07 / (pow(tanelev, 3)) + 0.000086 / (pow(tanelev, 5));
  681. else if (pdat->elevetr >= -0.575)
  682. refcor = 1735.0 +
  683. pdat->elevetr * (-518.2 + pdat->elevetr * (103.4 +
  684. pdat->elevetr *
  685. (-12.79 +
  686. pdat->elevetr *
  687. 0.711)));
  688. else
  689. refcor = -20.774 / tanelev;
  690. prestemp = (pdat->press * 283.0) / (1013.0 * (273.0 + pdat->temp));
  691. refcor *= prestemp / 3600.0;
  692. }
  693. /* Refracted solar elevation angle */
  694. pdat->elevref = pdat->elevetr + refcor;
  695. /* (limit the degrees below the horizon to 9) */
  696. if (pdat->elevref < -9.0)
  697. pdat->elevref = -9.0;
  698. /* Refracted solar zenith angle */
  699. pdat->zenref = 90.0 - pdat->elevref;
  700. pdat->coszen = cos(raddeg * pdat->zenref);
  701. }
  702. /*============================================================================
  703. * Local Void function amass
  704. *
  705. * Airmass
  706. * Kasten, F. and Young, A. 1989. Revised optical air mass
  707. * tables and approximation formula. Applied Optics 28 (22),
  708. * pp. 4735-4738
  709. *----------------------------------------------------------------------------*/
  710. static void amass(struct posdata *pdat)
  711. {
  712. if (pdat->zenref > 93.0) {
  713. pdat->amass = -1.0;
  714. pdat->ampress = -1.0;
  715. }
  716. else {
  717. pdat->amass =
  718. 1.0 / (cos(raddeg * pdat->zenref) + 0.50572 *
  719. pow((96.07995 - pdat->zenref), -1.6364));
  720. pdat->ampress = pdat->amass * pdat->press / 1013.0;
  721. }
  722. }
  723. /*============================================================================
  724. * Local Void function prime
  725. *
  726. * Prime and Unprime
  727. * Prime converts Kt to normalized Kt', etc.
  728. * Unprime deconverts Kt' to Kt, etc.
  729. * Perez, R., P. Ineichen, Seals, R., & Zelenka, A. 1990. Making
  730. * full use of the clearness index for parameterizing hourly
  731. * insolation conditions. Solar Energy 45 (2), pp. 111-114
  732. *----------------------------------------------------------------------------*/
  733. static void prime(struct posdata *pdat)
  734. {
  735. pdat->unprime = 1.031 * exp(-1.4 / (0.9 + 9.4 / pdat->amass)) + 0.1;
  736. pdat->prime = 1.0 / pdat->unprime;
  737. }
  738. /*============================================================================
  739. * Local Void function etr
  740. *
  741. * Extraterrestrial (top-of-atmosphere) solar irradiance
  742. *----------------------------------------------------------------------------*/
  743. static void etr(struct posdata *pdat)
  744. {
  745. if (pdat->coszen > 0.0) {
  746. pdat->etrn = pdat->solcon * pdat->erv;
  747. pdat->etr = pdat->etrn * pdat->coszen;
  748. }
  749. else {
  750. pdat->etrn = 0.0;
  751. pdat->etr = 0.0;
  752. }
  753. }
  754. /*============================================================================
  755. * Local Void function localtrig
  756. *
  757. * Does trig on internal variable used by several functions
  758. *----------------------------------------------------------------------------*/
  759. static void localtrig(struct posdata *pdat, struct trigdata *tdat)
  760. {
  761. /* define masks to prevent calculation of uninitialized variables */
  762. #define SD_MASK ( L_ZENETR | L_SSHA | S_SBCF | S_SOLAZM )
  763. #define SL_MASK ( L_ZENETR | L_SSHA | S_SBCF | S_SOLAZM )
  764. #define CL_MASK ( L_ZENETR | L_SSHA | S_SBCF | S_SOLAZM )
  765. #define CD_MASK ( L_ZENETR | L_SSHA | S_SBCF )
  766. #define CH_MASK ( L_ZENETR )
  767. if (tdat->sd < -900.0) { /* sd was initialized -999 as flag */
  768. tdat->sd = 1.0; /* reflag as having completed calculations */
  769. if (pdat->function | CD_MASK)
  770. tdat->cd = cos(raddeg * pdat->declin);
  771. if (pdat->function | CH_MASK)
  772. tdat->ch = cos(raddeg * pdat->hrang);
  773. if (pdat->function | CL_MASK)
  774. tdat->cl = cos(raddeg * pdat->latitude);
  775. if (pdat->function | SD_MASK)
  776. tdat->sd = sin(raddeg * pdat->declin);
  777. if (pdat->function | SL_MASK)
  778. tdat->sl = sin(raddeg * pdat->latitude);
  779. }
  780. }
  781. /*============================================================================
  782. * Local Void function tilt
  783. *
  784. * ETR on a tilted surface
  785. *----------------------------------------------------------------------------*/
  786. static void tilt(struct posdata *pdat)
  787. {
  788. float ca; /* cosine of the solar azimuth angle */
  789. float cp; /* cosine of the panel aspect */
  790. float ct; /* cosine of the panel tilt */
  791. float sa; /* sine of the solar azimuth angle */
  792. float sp; /* sine of the panel aspect */
  793. float st; /* sine of the panel tilt */
  794. float sz; /* sine of the refraction corrected solar zenith angle */
  795. /* Cosine of the angle between the sun and a tipped flat surface,
  796. useful for calculating solar energy on tilted surfaces */
  797. ca = cos(raddeg * pdat->azim);
  798. cp = cos(raddeg * pdat->aspect);
  799. ct = cos(raddeg * pdat->tilt);
  800. sa = sin(raddeg * pdat->azim);
  801. sp = sin(raddeg * pdat->aspect);
  802. st = sin(raddeg * pdat->tilt);
  803. sz = sin(raddeg * pdat->zenref);
  804. pdat->cosinc = pdat->coszen * ct + sz * st * (ca * cp + sa * sp);
  805. if (pdat->cosinc > 0.0)
  806. pdat->etrtilt = pdat->etrn * pdat->cosinc;
  807. else
  808. pdat->etrtilt = 0.0;
  809. }
  810. /*============================================================================
  811. * Void function S_decode
  812. *
  813. * This function decodes the error codes from S_solpos return value
  814. *
  815. * Requires the long integer return value from S_solpos
  816. *
  817. * Returns descriptive text to stderr
  818. *----------------------------------------------------------------------------*/
  819. void S_decode(long code, struct posdata *pdat)
  820. {
  821. if (code & (1L << S_YEAR_ERROR))
  822. G_warning(_("S_decode ==> Please fix the year: %d [1950-2050]"),
  823. pdat->year);
  824. if (code & (1L << S_MONTH_ERROR))
  825. G_warning(_("S_decode ==> Please fix the month: %d"), pdat->month);
  826. if (code & (1L << S_DAY_ERROR))
  827. G_warning(_("S_decode ==> Please fix the day-of-month: %d"),
  828. pdat->day);
  829. if (code & (1L << S_DOY_ERROR))
  830. G_warning(_("S_decode ==> Please fix the day-of-year: %d"),
  831. pdat->daynum);
  832. if (code & (1L << S_HOUR_ERROR))
  833. G_warning(_("S_decode ==> Please fix the hour: %d"), pdat->hour);
  834. if (code & (1L << S_MINUTE_ERROR))
  835. G_warning(_("S_decode ==> Please fix the minute: %d"), pdat->minute);
  836. if (code & (1L << S_SECOND_ERROR))
  837. G_warning(_("S_decode ==> Please fix the second: %d"), pdat->second);
  838. if (code & (1L << S_TZONE_ERROR))
  839. G_warning(_("S_decode ==> Please fix the time zone: %f"),
  840. pdat->timezone);
  841. if (code & (1L << S_INTRVL_ERROR))
  842. G_warning(_("S_decode ==> Please fix the interval: %d"),
  843. pdat->interval);
  844. if (code & (1L << S_LAT_ERROR))
  845. G_warning(_("S_decode ==> Please fix the latitude: %f"),
  846. pdat->latitude);
  847. if (code & (1L << S_LON_ERROR))
  848. G_warning(_("S_decode ==> Please fix the longitude: %f"),
  849. pdat->longitude);
  850. if (code & (1L << S_TEMP_ERROR))
  851. G_warning(_("S_decode ==> Please fix the temperature: %f"),
  852. pdat->temp);
  853. if (code & (1L << S_PRESS_ERROR))
  854. G_warning(_("S_decode ==> Please fix the pressure: %f"), pdat->press);
  855. if (code & (1L << S_TILT_ERROR))
  856. G_warning(_("S_decode ==> Please fix the tilt: %f"), pdat->tilt);
  857. if (code & (1L << S_ASPECT_ERROR))
  858. G_warning(_("S_decode ==> Please fix the aspect: %f"), pdat->aspect);
  859. if (code & (1L << S_SBWID_ERROR))
  860. G_warning(_("S_decode ==> Please fix the shadowband width: %f"),
  861. pdat->sbwid);
  862. if (code & (1L << S_SBRAD_ERROR))
  863. G_warning(_("S_decode ==> Please fix the shadowband radius: %f"),
  864. pdat->sbrad);
  865. if (code & (1L << S_SBSKY_ERROR))
  866. G_warning(_("S_decode ==> Please fix the shadowband sky factor: %f"),
  867. pdat->sbsky);
  868. }