solpos00.c 36 KB

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