main.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. /****************************************************************************
  2. *
  3. * MODULE: r.sunhours
  4. * AUTHOR(S): Markus Metz
  5. * PURPOSE: Calculates solar azimuth and angle, and
  6. * sunshine hours (also called daytime period)
  7. * Uses NREL SOLPOS
  8. * COPYRIGHT: (C) 2010-2013 by the GRASS Development Team
  9. *
  10. * This program is free software under the GNU General Public
  11. * License (>=v2). Read the file COPYING that comes with GRASS
  12. * for details.
  13. *
  14. *****************************************************************************/
  15. /* TODO: always use solpos if time is Greenwich standard time */
  16. #define MAIN
  17. #include <stdio.h>
  18. #include <stdlib.h>
  19. #include <string.h>
  20. #include <math.h>
  21. #include <grass/gis.h>
  22. #include <grass/raster.h>
  23. #include <grass/gprojects.h>
  24. #include <grass/glocale.h>
  25. #include "solpos00.h"
  26. void set_solpos_time(struct posdata *, int, int, int, int, int, int);
  27. void set_solpos_longitude(struct posdata *, double );
  28. int roundoff(double *);
  29. int main(int argc, char *argv[])
  30. {
  31. struct GModule *module;
  32. struct
  33. {
  34. struct Option *elev, *azimuth, *sunhours, *year,
  35. *month, *day, *hour, *minutes, *seconds;
  36. struct Flag *lst_time, *no_solpos;
  37. } parm;
  38. struct Cell_head window;
  39. FCELL *elevbuf, *azimuthbuf, *sunhourbuf;
  40. struct History hist;
  41. /* projection information of input map */
  42. struct Key_Value *in_proj_info, *in_unit_info;
  43. struct pj_info iproj; /* input map proj parameters */
  44. struct pj_info oproj; /* output map proj parameters */
  45. char *elev_name, *azimuth_name, *sunhour_name;
  46. int elev_fd, azimuth_fd, sunhour_fd;
  47. double ha, ha_cos, s_gamma, s_elevation, s_azimuth;
  48. double s_declination, sd_sin, sd_cos;
  49. double se_sin, sa_cos;
  50. double east, east_ll, north, north_ll;
  51. double north_gc, north_gc_sin, north_gc_cos; /* geocentric latitude */
  52. double ba2;
  53. int year, month, day, hour, minutes, seconds;
  54. int doy; /* day of year */
  55. int row, col, nrows, ncols;
  56. int do_reproj = 0;
  57. int lst_time = 1;
  58. int use_solpos = 0;
  59. struct posdata pd;
  60. G_gisinit(argv[0]);
  61. module = G_define_module();
  62. G_add_keyword(_("raster"));
  63. G_add_keyword(_("solar"));
  64. G_add_keyword(_("sun energy"));
  65. G_add_keyword(_("sun position"));
  66. module->label = _("Calculates solar elevation, solar azimuth, and sun hours.");
  67. module->description = _("Solar elevation: the angle between the direction of the geometric center "
  68. "of the sun's apparent disk and the (idealized) horizon. "
  69. "Solar azimuth: the angle from due north in clockwise direction.");
  70. parm.elev = G_define_standard_option(G_OPT_R_OUTPUT);
  71. parm.elev->key = "elevation";
  72. parm.elev->label = _("Output raster map with solar elevation angle");
  73. parm.elev->required = NO;
  74. parm.azimuth = G_define_standard_option(G_OPT_R_OUTPUT);
  75. parm.azimuth->key = "azimuth";
  76. parm.azimuth->label = _("Output raster map with solar azimuth angle");
  77. parm.azimuth->required = NO;
  78. parm.sunhours = G_define_standard_option(G_OPT_R_OUTPUT);
  79. parm.sunhours->key = "sunhour";
  80. parm.sunhours->label = _("Output raster map with sunshine hours");
  81. parm.sunhours->description = _("Sunshine hours require SOLPOS use and Greenwich standard time");
  82. parm.sunhours->required = NO;
  83. parm.year = G_define_option();
  84. parm.year->key = "year";
  85. parm.year->type = TYPE_INTEGER;
  86. parm.year->required = YES;
  87. parm.year->description = _("Year");
  88. parm.year->options = "1950-2050";
  89. parm.year->guisection = _("Time");
  90. parm.month = G_define_option();
  91. parm.month->key = "month";
  92. parm.month->type = TYPE_INTEGER;
  93. parm.month->required = NO;
  94. parm.month->label = _("Month");
  95. parm.month->description = _("If not given, day is interpreted as day of the year");
  96. parm.month->options = "1-12";
  97. parm.month->guisection = _("Time");
  98. parm.day = G_define_option();
  99. parm.day->key = "day";
  100. parm.day->type = TYPE_INTEGER;
  101. parm.day->required = YES;
  102. parm.day->description = _("Day");
  103. parm.day->options = "1-366";
  104. parm.day->guisection = _("Time");
  105. parm.hour = G_define_option();
  106. parm.hour->key = "hour";
  107. parm.hour->type = TYPE_INTEGER;
  108. parm.hour->required = NO;
  109. parm.hour->description = _("Hour");
  110. parm.hour->options = "0-24";
  111. parm.hour->answer = "12";
  112. parm.hour->guisection = _("Time");
  113. parm.minutes = G_define_option();
  114. parm.minutes->key = "minute";
  115. parm.minutes->type = TYPE_INTEGER;
  116. parm.minutes->required = NO;
  117. parm.minutes->description = _("Minutes");
  118. parm.minutes->options = "0-60";
  119. parm.minutes->answer = "0";
  120. parm.minutes->guisection = _("Time");
  121. parm.seconds = G_define_option();
  122. parm.seconds->key = "second";
  123. parm.seconds->type = TYPE_INTEGER;
  124. parm.seconds->required = NO;
  125. parm.seconds->description = _("Seconds");
  126. parm.seconds->options = "0-60";
  127. parm.seconds->answer = "0";
  128. parm.seconds->guisection = _("Time");
  129. parm.lst_time = G_define_flag();
  130. parm.lst_time->key = 't';
  131. parm.lst_time->description = _("Time is local sidereal time, not Greenwich standard time");
  132. parm.no_solpos = G_define_flag();
  133. parm.no_solpos->key = 's';
  134. parm.no_solpos->description = _("Do not use SOLPOS algorithm of NREL");
  135. if (G_parser(argc, argv))
  136. exit(EXIT_FAILURE);
  137. G_get_window(&window);
  138. /* require at least one output */
  139. elev_name = parm.elev->answer;
  140. azimuth_name = parm.azimuth->answer;
  141. sunhour_name = parm.sunhours->answer;
  142. if (!elev_name && !azimuth_name && !sunhour_name)
  143. G_fatal_error(_("No output requested, exiting."));
  144. year = atoi(parm.year->answer);
  145. if (parm.month->answer)
  146. month = atoi(parm.month->answer);
  147. else
  148. month = -1;
  149. day = atoi(parm.day->answer);
  150. hour = atoi(parm.hour->answer);
  151. minutes = atoi(parm.minutes->answer);
  152. seconds = atoi(parm.seconds->answer);
  153. lst_time = (parm.lst_time->answer != 0);
  154. use_solpos = (parm.no_solpos->answer == 0);
  155. /* init variables */
  156. ha = 180;
  157. ha_cos = 0;
  158. sd_cos = 0;
  159. sd_sin = 1;
  160. north_gc_cos = 0;
  161. north_gc_sin = 1;
  162. se_sin = 0;
  163. if (use_solpos && lst_time) {
  164. G_warning(_("NREL SOLPOS algorithm uses Greenwich standard time."));
  165. G_warning(_("Time will be interpreted as Greenwich standard time."));
  166. lst_time = 0;
  167. }
  168. if (!use_solpos) {
  169. if (lst_time)
  170. G_message(_("Time will be interpreted as local sidereal time."));
  171. else
  172. G_message(_("Time will be interpreted as Greenwich standard time."));
  173. if (sunhour_name)
  174. G_fatal_error(_("Sunshine hours require NREL SOLPOS."));
  175. }
  176. if ((G_projection() != PROJECTION_LL)) {
  177. if (window.proj == 0)
  178. G_fatal_error(_("Current projection is x,y (undefined)."));
  179. do_reproj = 1;
  180. /* read current projection info */
  181. if ((in_proj_info = G_get_projinfo()) == NULL)
  182. G_fatal_error(_("Cannot get projection info of current location"));
  183. if ((in_unit_info = G_get_projunits()) == NULL)
  184. G_fatal_error(_("Cannot get projection units of current location"));
  185. if (pj_get_kv(&iproj, in_proj_info, in_unit_info) < 0)
  186. G_fatal_error(_("Cannot get projection key values of current location"));
  187. G_free_key_value(in_proj_info);
  188. G_free_key_value(in_unit_info);
  189. /* output projection to lat/long w/ same ellipsoid as input */
  190. oproj.zone = 0;
  191. oproj.meters = 1.;
  192. sprintf(oproj.proj, "ll");
  193. if ((oproj.pj = pj_latlong_from_proj(iproj.pj)) == NULL)
  194. G_fatal_error(_("Unable to update lat/long projection parameters"));
  195. }
  196. /* always init pd */
  197. S_init(&pd);
  198. pd.function = S_GEOM;
  199. if (use_solpos) {
  200. pd.function = S_ZENETR;
  201. if (azimuth_name)
  202. pd.function = S_SOLAZM;
  203. if (sunhour_name)
  204. pd.function |= S_SRSS;
  205. }
  206. if (month == -1)
  207. doy = day;
  208. else
  209. doy = dom2doy2(year, month, day);
  210. set_solpos_time(&pd, year, 1, doy, hour, minutes, seconds);
  211. set_solpos_longitude(&pd, 0);
  212. pd.latitude = 0;
  213. S_solpos(&pd);
  214. if (lst_time) {
  215. /* hour angle */
  216. /***************************************************************
  217. * The hour angle of a point on the Earth's surface is the angle
  218. * through which the earth would turn to bring the meridian of
  219. * the point directly under the sun. This angular displacement
  220. * represents time (1 hour = 15 degrees).
  221. * The hour angle is negative in the morning, zero at 12:00,
  222. * and positive in the afternoon
  223. ***************************************************************/
  224. ha = 15.0 * (hour + minutes / 60.0 + seconds / 3600.0) - 180.;
  225. G_debug(1, "Solar hour angle, degrees: %.2f", ha);
  226. ha *= DEG2RAD;
  227. ha_cos = cos(ha);
  228. roundoff(&ha_cos);
  229. }
  230. if (!use_solpos) {
  231. /* sun declination */
  232. /***************************************************************
  233. * The declination of the sun is the angle between
  234. * the rays of the sun and the plane of the Earth's equator.
  235. ***************************************************************/
  236. s_gamma = (2 * M_PI * (doy - 1)) / 365;
  237. G_debug(1, "fractional year in radians: %.2f", s_gamma);
  238. /* sun declination for day of year with Fourier series representation
  239. * NOTE: based on 1950, override with solpos */
  240. s_declination = (0.006918 - 0.399912 * cos(s_gamma) + 0.070257 * sin(s_gamma) -
  241. 0.006758 * cos(2 * s_gamma) + 0.000907 * sin(2 * s_gamma) -
  242. 0.002697 * cos(3 * s_gamma) + 0.00148 * sin(3 * s_gamma));
  243. G_debug(1, "sun declination: %.5f", s_declination * RAD2DEG);
  244. G_debug(1, "sun declination (solpos): %.5f", pd.declin);
  245. if (lst_time) {
  246. north_ll = (window.north + window.south) / 2;
  247. east_ll = (window.east + window.west) / 2;
  248. if (do_reproj) {
  249. if (pj_do_proj(&east_ll, &north_ll, &iproj, &oproj) < 0)
  250. G_fatal_error(_("Error in pj_do_proj (projection of input coordinate pair)"));
  251. }
  252. pd.timezone = east_ll / 15.;
  253. pd.time_updated = 1;
  254. set_solpos_longitude(&pd, east_ll);
  255. G_debug(1, "fake timezone: %.2f", pd.timezone);
  256. S_solpos(&pd);
  257. G_debug(1, "Solar hour angle (solpos), degrees: %.2f", pd.hrang);
  258. }
  259. /* always use solpos sun declination */
  260. s_declination = pd.declin * DEG2RAD;
  261. sd_sin = sin(s_declination);
  262. roundoff(&sd_sin);
  263. sd_cos = cos(s_declination);
  264. roundoff(&sd_cos);
  265. G_debug(1, "sun declination (solpos): %.5f", s_declination * RAD2DEG);
  266. if (0 && lst_time) {
  267. pd.timezone = 0;
  268. pd.time_updated = pd.longitude_updated = 1;
  269. S_solpos(&pd);
  270. }
  271. }
  272. if (elev_name) {
  273. if ((elev_fd = Rast_open_new(elev_name, FCELL_TYPE)) < 0)
  274. G_fatal_error(_("Unable to create raster map <%s>"), elev_name);
  275. elevbuf = Rast_allocate_f_buf();
  276. }
  277. else {
  278. elevbuf = NULL;
  279. elev_fd = -1;
  280. }
  281. if (azimuth_name) {
  282. if ((azimuth_fd = Rast_open_new(azimuth_name, FCELL_TYPE)) < 0)
  283. G_fatal_error(_("Unable to create raster map <%s>"), azimuth_name);
  284. azimuthbuf = Rast_allocate_f_buf();
  285. }
  286. else {
  287. azimuthbuf = NULL;
  288. azimuth_fd = -1;
  289. }
  290. if (sunhour_name) {
  291. if ((sunhour_fd = Rast_open_new(sunhour_name, FCELL_TYPE)) < 0)
  292. G_fatal_error(_("Unable to create raster map <%s>"), sunhour_name);
  293. sunhourbuf = Rast_allocate_f_buf();
  294. }
  295. else {
  296. sunhourbuf = NULL;
  297. sunhour_fd = -1;
  298. }
  299. if (elev_name && azimuth_name) {
  300. G_message(_("Calculating solar elevation and azimuth..."));
  301. }
  302. else if (elev_name) {
  303. G_message(_("Calculating solar elevation..."));
  304. }
  305. else if (azimuth_name) {
  306. G_message(_("Calculating solar azimuth..."));
  307. }
  308. nrows = Rast_window_rows();
  309. ncols = Rast_window_cols();
  310. ba2 = 6356752.3142 / 6378137.0;
  311. ba2 = ba2 * ba2;
  312. for (row = 0; row < nrows; row++) {
  313. G_percent(row, nrows, 2);
  314. /* get cell center northing */
  315. north = window.north - (row + 0.5) * window.ns_res;
  316. north_ll = north;
  317. for (col = 0; col < ncols; col++) {
  318. long int retval;
  319. s_elevation = s_azimuth = -1.;
  320. /* get cell center easting */
  321. east = window.west + (col + 0.5) * window.ew_res;
  322. east_ll = east;
  323. if (do_reproj) {
  324. north_ll = north;
  325. if (pj_do_proj(&east_ll, &north_ll, &iproj, &oproj) < 0)
  326. G_fatal_error(_("Error in pj_do_proj (projection of input coordinate pair)"));
  327. }
  328. /* geocentric latitude */
  329. north_gc = atan(ba2 * tan(DEG2RAD * north_ll));
  330. north_gc_sin = sin(north_gc);
  331. roundoff(&north_gc_sin);
  332. north_gc_cos = cos(north_gc);
  333. roundoff(&north_gc_cos);
  334. if (!lst_time) {
  335. set_solpos_longitude(&pd, east_ll);
  336. pd.latitude = north_gc * RAD2DEG;
  337. retval = S_solpos(&pd);
  338. S_decode(retval, &pd);
  339. G_debug(3, "solpos hour angle: %.5f", pd.hrang);
  340. }
  341. /* solar elevation angle */
  342. if (!use_solpos) {
  343. if (!lst_time) {
  344. ha = pd.hrang;
  345. ha_cos = cos(ha * DEG2RAD);
  346. roundoff(&ha_cos);
  347. }
  348. se_sin = ha_cos * sd_cos * north_gc_cos + sd_sin * north_gc_sin;
  349. roundoff(&se_sin);
  350. s_elevation = RAD2DEG * asin(se_sin);
  351. }
  352. else /* use_solpos && !lst_time */
  353. s_elevation = pd.elevetr;
  354. if (elev_name)
  355. elevbuf[col] = s_elevation;
  356. if (azimuth_name) {
  357. /* solar azimuth angle */
  358. if (!use_solpos) {
  359. sa_cos = (se_sin * north_gc_sin - sd_sin) /
  360. (cos(DEG2RAD * s_elevation) * north_gc_cos);
  361. roundoff(&sa_cos);
  362. s_azimuth = RAD2DEG * acos(sa_cos);
  363. /* morning */
  364. s_azimuth = 180. - RAD2DEG * acos(sa_cos);
  365. if (ha > 0) /* afternoon */
  366. s_azimuth = 360.0 - s_azimuth;
  367. }
  368. else
  369. s_azimuth = pd.azim;
  370. azimuthbuf[col] = s_azimuth;
  371. }
  372. if (sunhour_name) {
  373. sunhourbuf[col] = (pd.ssetr - pd.sretr) / 60.;
  374. if (sunhourbuf[col] > 24.)
  375. sunhourbuf[col] = 24.;
  376. if (sunhourbuf[col] < 0.)
  377. sunhourbuf[col] = 0.;
  378. }
  379. }
  380. if (elev_name)
  381. Rast_put_f_row(elev_fd, elevbuf);
  382. if (azimuth_name)
  383. Rast_put_f_row(azimuth_fd, azimuthbuf);
  384. if (sunhour_name)
  385. Rast_put_f_row(sunhour_fd, sunhourbuf);
  386. }
  387. G_percent(1, 1, 2);
  388. if (elev_name) {
  389. Rast_close(elev_fd);
  390. /* writing history file */
  391. Rast_short_history(elev_name, "raster", &hist);
  392. Rast_command_history(&hist);
  393. Rast_write_history(elev_name, &hist);
  394. }
  395. if (azimuth_name) {
  396. Rast_close(azimuth_fd);
  397. /* writing history file */
  398. Rast_short_history(azimuth_name, "raster", &hist);
  399. Rast_command_history(&hist);
  400. Rast_write_history(azimuth_name, &hist);
  401. }
  402. if (sunhour_name) {
  403. Rast_close(sunhour_fd);
  404. /* writing history file */
  405. Rast_short_history(sunhour_name, "raster", &hist);
  406. Rast_command_history(&hist);
  407. Rast_write_history(sunhour_name, &hist);
  408. }
  409. G_done_msg(" ");
  410. exit(EXIT_SUCCESS);
  411. }
  412. void set_solpos_time(struct posdata *pdat, int year, int month, int day,
  413. int hour, int minute, int second)
  414. {
  415. pdat->year = year;
  416. pdat->month = month;
  417. pdat->day = day;
  418. pdat->daynum = day;
  419. pdat->hour = hour;
  420. pdat->minute = minute;
  421. pdat->second = second;
  422. pdat->timezone = 0;
  423. pdat->time_updated = 1;
  424. pdat->longitude_updated = 1;
  425. }
  426. void set_solpos_longitude(struct posdata *pdat, double longitude)
  427. {
  428. pdat->longitude = longitude;
  429. pdat->longitude_updated = 1;
  430. }
  431. int roundoff(double *x)
  432. {
  433. /* watch out for the roundoff errors */
  434. if (fabs(*x) > 1.0) {
  435. if (*x > 0.0)
  436. *x = 1.0;
  437. else
  438. *x = -1.0;
  439. return 1;
  440. }
  441. return 0;
  442. }