2
/****************************************************************************
5
* AUTHOR(S): Markus Metz
6
* PURPOSE: Calculates solar azimuth and angle, and
7
* sunshine hours (also called daytime period)
9
* COPYRIGHT: (C) 2010-2013 by the GRASS Development Team
11
* This program is free software under the GNU General Public
12
* License (>=v2). Read the file COPYING that comes with GRASS
15
*****************************************************************************/
17
/* TODO: always use solpos if time is Greenwich standard time */
25
#include <grass/gis.h>
26
#include <grass/raster.h>
27
#include <grass/gprojects.h>
28
#include <grass/glocale.h>
31
void set_solpos_time(struct posdata *, int, int, int, int, int, int);
32
void set_solpos_longitude(struct posdata *, double );
33
int roundoff(double *);
35
int main(int argc, char *argv[])
37
struct GModule *module;
40
struct Option *elev, *azimuth, *sunhours, *year,
41
*month, *day, *hour, *minutes, *seconds;
42
struct Flag *lst_time, *no_solpos;
44
struct Cell_head window;
45
FCELL *elevbuf, *azimuthbuf, *sunhourbuf;
48
/* projection information of input map */
49
struct Key_Value *in_proj_info, *in_unit_info;
50
struct pj_info iproj; /* input map proj parameters */
51
struct pj_info oproj; /* output map proj parameters */
53
char *elev_name, *azimuth_name, *sunhour_name;
54
int elev_fd, azimuth_fd, sunhour_fd;
55
double ha, ha_cos, s_gamma, s_elevation, s_azimuth;
56
double s_declination, sd_sin, sd_cos;
57
double se_sin, sa_cos;
58
double east, east_ll, north, north_ll;
59
double north_gc, north_gc_sin, north_gc_cos; /* geocentric latitude */
61
int year, month, day, hour, minutes, seconds;
62
int doy; /* day of year */
63
int row, col, nrows, ncols;
71
module = G_define_module();
72
G_add_keyword(_("raster"));
73
G_add_keyword(_("solar"));
74
module->label = _("Calculates solar elevation, solar azimuth, and sun hours.");
75
module->description = _("Solar elevation: the angle between the direction of the geometric center "
76
"of the sun's apparent disk and the (idealized) horizon. "
77
"Solar azimuth: the angle from due north in clockwise direction.");
79
parm.elev = G_define_standard_option(G_OPT_R_OUTPUT);
80
parm.elev->key = "elevation";
81
parm.elev->label = _("Output raster map with solar elevation angle");
82
parm.elev->required = NO;
84
parm.azimuth = G_define_standard_option(G_OPT_R_OUTPUT);
85
parm.azimuth->key = "azimuth";
86
parm.azimuth->label = _("Output raster map with solar azimuth angle");
87
parm.azimuth->required = NO;
89
parm.sunhours = G_define_standard_option(G_OPT_R_OUTPUT);
90
parm.sunhours->key = "sunhour";
91
parm.sunhours->label = _("Output raster map with sunshine hours");
92
parm.sunhours->description = _("Sunshine hours require SOLPOS use and Greenwich standard time");
93
parm.sunhours->required = NO;
95
parm.year = G_define_option();
96
parm.year->key = "year";
97
parm.year->type = TYPE_INTEGER;
98
parm.year->required = YES;
99
parm.year->description = _("Year");
100
parm.year->options = "1950-2050";
101
parm.year->guisection = _("Time");
103
parm.month = G_define_option();
104
parm.month->key = "month";
105
parm.month->type = TYPE_INTEGER;
106
parm.month->required = NO;
107
parm.month->label = _("Month");
108
parm.month->description = _("If not given, day is interpreted as day of the year");
109
parm.month->options = "1-12";
110
parm.month->guisection = _("Time");
112
parm.day = G_define_option();
113
parm.day->key = "day";
114
parm.day->type = TYPE_INTEGER;
115
parm.day->required = YES;
116
parm.day->description = _("Day");
117
parm.day->options = "1-366";
118
parm.day->guisection = _("Time");
120
parm.hour = G_define_option();
121
parm.hour->key = "hour";
122
parm.hour->type = TYPE_INTEGER;
123
parm.hour->required = NO;
124
parm.hour->description = _("Hour");
125
parm.hour->options = "0-24";
126
parm.hour->answer = "12";
127
parm.hour->guisection = _("Time");
129
parm.minutes = G_define_option();
130
parm.minutes->key = "minute";
131
parm.minutes->type = TYPE_INTEGER;
132
parm.minutes->required = NO;
133
parm.minutes->description = _("Minutes");
134
parm.minutes->options = "0-60";
135
parm.minutes->answer = "0";
136
parm.minutes->guisection = _("Time");
138
parm.seconds = G_define_option();
139
parm.seconds->key = "second";
140
parm.seconds->type = TYPE_INTEGER;
141
parm.seconds->required = NO;
142
parm.seconds->description = _("Seconds");
143
parm.seconds->options = "0-60";
144
parm.seconds->answer = "0";
145
parm.seconds->guisection = _("Time");
147
parm.lst_time = G_define_flag();
148
parm.lst_time->key = 't';
149
parm.lst_time->description = _("Time is local sidereal time, not Greenwich standard time");
151
parm.no_solpos = G_define_flag();
152
parm.no_solpos->key = 's';
153
parm.no_solpos->description = _("Do not use SOLPOS algorithm of NREL");
155
if (G_parser(argc, argv))
158
G_get_window(&window);
160
/* require at least one output */
161
elev_name = parm.elev->answer;
162
azimuth_name = parm.azimuth->answer;
163
sunhour_name = parm.sunhours->answer;
164
if (!elev_name && !azimuth_name && !sunhour_name)
165
G_fatal_error(_("No output requested, exiting."));
167
year = atoi(parm.year->answer);
168
if (parm.month->answer)
169
month = atoi(parm.month->answer);
173
day = atoi(parm.day->answer);
174
hour = atoi(parm.hour->answer);
175
minutes = atoi(parm.minutes->answer);
176
seconds = atoi(parm.seconds->answer);
178
lst_time = (parm.lst_time->answer != 0);
179
use_solpos = (parm.no_solpos->answer == 0);
190
if (use_solpos && lst_time) {
191
G_warning(_("NREL SOLPOS algorithm uses Greenwich standard time."));
192
G_warning(_("Time will be interpreted as Greenwich standard time."));
198
G_message(_("Time will be interpreted as local sidereal time."));
200
G_message(_("Time will be interpreted as Greenwich standard time."));
203
G_fatal_error(_("Sunshine hours require NREL SOLPOS."));
206
if ((G_projection() != PROJECTION_LL)) {
207
if (window.proj == 0)
208
G_fatal_error(_("Current projection is x,y (undefined)."));
212
/* read current projection info */
213
if ((in_proj_info = G_get_projinfo()) == NULL)
214
G_fatal_error(_("Cannot get projection info of current location"));
216
if ((in_unit_info = G_get_projunits()) == NULL)
217
G_fatal_error(_("Cannot get projection units of current location"));
219
if (pj_get_kv(&iproj, in_proj_info, in_unit_info) < 0)
220
G_fatal_error(_("Cannot get projection key values of current location"));
222
G_free_key_value(in_proj_info);
223
G_free_key_value(in_unit_info);
225
/* output projection to lat/long w/ same ellipsoid as input */
228
sprintf(oproj.proj, "ll");
229
if ((oproj.pj = pj_latlong_from_proj(iproj.pj)) == NULL)
230
G_fatal_error(_("Unable to update lat/long projection parameters"));
235
pd.function = S_GEOM;
237
pd.function = S_ZENETR;
239
pd.function = S_SOLAZM;
241
pd.function |= S_SRSS;
246
doy = dom2doy2(year, month, day);
248
set_solpos_time(&pd, year, 1, doy, hour, minutes, seconds);
249
set_solpos_longitude(&pd, 0);
255
/***************************************************************
256
* The hour angle of a point on the Earth's surface is the angle
257
* through which the earth would turn to bring the meridian of
258
* the point directly under the sun. This angular displacement
259
* represents time (1 hour = 15 degrees).
260
* The hour angle is negative in the morning, zero at 12:00,
261
* and positive in the afternoon
262
***************************************************************/
264
ha = 15.0 * (hour + minutes / 60.0 + seconds / 3600.0) - 180.;
265
G_debug(1, "Solar hour angle, degrees: %.2f", ha);
272
/* sun declination */
273
/***************************************************************
274
* The declination of the sun is the angle between
275
* the rays of the sun and the plane of the Earth's equator.
276
***************************************************************/
278
s_gamma = (2 * M_PI * (doy - 1)) / 365;
279
G_debug(1, "fractional year in radians: %.2f", s_gamma);
280
/* sun declination for day of year with Fourier series representation
281
* NOTE: based on 1950, override with solpos */
282
s_declination = (0.006918 - 0.399912 * cos(s_gamma) + 0.070257 * sin(s_gamma) -
283
0.006758 * cos(2 * s_gamma) + 0.000907 * sin(2 * s_gamma) -
284
0.002697 * cos(3 * s_gamma) + 0.00148 * sin(3 * s_gamma));
286
G_debug(1, "sun declination: %.5f", s_declination * RAD2DEG);
287
G_debug(1, "sun declination (solpos): %.5f", pd.declin);
290
north_ll = (window.north + window.south) / 2;
291
east_ll = (window.east + window.west) / 2;
293
if (pj_do_proj(&east_ll, &north_ll, &iproj, &oproj) < 0)
294
G_fatal_error(_("Error in pj_do_proj (projection of input coordinate pair)"));
296
pd.timezone = east_ll / 15.;
298
set_solpos_longitude(&pd, east_ll);
299
G_debug(1, "fake timezone: %.2f", pd.timezone);
301
G_debug(1, "Solar hour angle (solpos), degrees: %.2f", pd.hrang);
304
/* always use solpos sun declination */
305
s_declination = pd.declin * DEG2RAD;
306
sd_sin = sin(s_declination);
308
sd_cos = cos(s_declination);
311
G_debug(1, "sun declination (solpos): %.5f", s_declination * RAD2DEG);
315
pd.time_updated = pd.longitude_updated = 1;
321
if ((elev_fd = Rast_open_new(elev_name, FCELL_TYPE)) < 0)
322
G_fatal_error(_("Unable to create raster map <%s>"), elev_name);
324
elevbuf = Rast_allocate_f_buf();
332
if ((azimuth_fd = Rast_open_new(azimuth_name, FCELL_TYPE)) < 0)
333
G_fatal_error(_("Unable to create raster map <%s>"), azimuth_name);
335
azimuthbuf = Rast_allocate_f_buf();
343
if ((sunhour_fd = Rast_open_new(sunhour_name, FCELL_TYPE)) < 0)
344
G_fatal_error(_("Unable to create raster map <%s>"), sunhour_name);
346
sunhourbuf = Rast_allocate_f_buf();
353
if (elev_name && azimuth_name) {
354
G_message(_("Calculating solar elevation and azimuth..."));
356
else if (elev_name) {
357
G_message(_("Calculating solar elevation..."));
359
else if (azimuth_name) {
360
G_message(_("Calculating solar azimuth..."));
363
nrows = Rast_window_rows();
364
ncols = Rast_window_cols();
366
ba2 = 6356752.3142 / 6378137.0;
369
for (row = 0; row < nrows; row++) {
371
G_percent(row, nrows, 2);
373
/* get cell center northing */
374
north = window.north - (row + 0.5) * window.ns_res;
377
for (col = 0; col < ncols; col++) {
379
s_elevation = s_azimuth = -1.;
381
/* get cell center easting */
382
east = window.west + (col + 0.5) * window.ew_res;
388
if (pj_do_proj(&east_ll, &north_ll, &iproj, &oproj) < 0)
389
G_fatal_error(_("Error in pj_do_proj (projection of input coordinate pair)"));
392
/* geocentric latitude */
393
north_gc = atan(ba2 * tan(DEG2RAD * north_ll));
394
north_gc_sin = sin(north_gc);
395
roundoff(&north_gc_sin);
396
north_gc_cos = cos(north_gc);
397
roundoff(&north_gc_cos);
400
set_solpos_longitude(&pd, east_ll);
401
pd.latitude = north_gc * RAD2DEG;
402
retval = S_solpos(&pd);
403
S_decode(retval, &pd);
404
G_debug(3, "solpos hour angle: %.5f", pd.hrang);
407
/* solar elevation angle */
411
ha_cos = cos(ha * DEG2RAD);
414
se_sin = ha_cos * sd_cos * north_gc_cos + sd_sin * north_gc_sin;
416
s_elevation = RAD2DEG * asin(se_sin);
418
else /* use_solpos && !lst_time */
419
s_elevation = pd.elevetr;
422
elevbuf[col] = s_elevation;
425
/* solar azimuth angle */
427
sa_cos = (se_sin * north_gc_sin - sd_sin) /
428
(cos(DEG2RAD * s_elevation) * north_gc_cos);
430
s_azimuth = RAD2DEG * acos(sa_cos);
433
s_azimuth = 180. - RAD2DEG * acos(sa_cos);
434
if (ha > 0) /* afternoon */
435
s_azimuth = 360.0 - s_azimuth;
440
azimuthbuf[col] = s_azimuth;
444
sunhourbuf[col] = (pd.ssetr - pd.sretr) / 60.;
445
if (sunhourbuf[col] > 24.)
446
sunhourbuf[col] = 24.;
447
if (sunhourbuf[col] < 0.)
448
sunhourbuf[col] = 0.;
453
Rast_put_f_row(elev_fd, elevbuf);
455
Rast_put_f_row(azimuth_fd, azimuthbuf);
457
Rast_put_f_row(sunhour_fd, sunhourbuf);
463
/* writing history file */
464
Rast_short_history(elev_name, "raster", &hist);
465
Rast_command_history(&hist);
466
Rast_write_history(elev_name, &hist);
469
Rast_close(azimuth_fd);
470
/* writing history file */
471
Rast_short_history(azimuth_name, "raster", &hist);
472
Rast_command_history(&hist);
473
Rast_write_history(azimuth_name, &hist);
476
Rast_close(sunhour_fd);
477
/* writing history file */
478
Rast_short_history(sunhour_name, "raster", &hist);
479
Rast_command_history(&hist);
480
Rast_write_history(sunhour_name, &hist);
488
void set_solpos_time(struct posdata *pdat, int year, int month, int day,
489
int hour, int minute, int second)
496
pdat->minute = minute;
497
pdat->second = second;
500
pdat->time_updated = 1;
501
pdat->longitude_updated = 1;
504
void set_solpos_longitude(struct posdata *pdat, double longitude)
506
pdat->longitude = longitude;
508
pdat->longitude_updated = 1;
511
int roundoff(double *x)
513
/* watch out for the roundoff errors */
514
if (fabs(*x) > 1.0) {