2
/*******************************************************************************
3
r.sun: rsunlib.c. This program was writen by Jaro Hofierka in Summer 1993 and re-engineered
4
in 1996-1999. In cooperation with Marcel Suri and Thomas Huld from JRC in Ispra
5
a new version of r.sun was prepared using ESRA solar radiation formulas.
6
See manual pages for details.
7
(C) 2002 Copyright Jaro Hofierka, Gresaka 22, 085 01 Bardejov, Slovakia,
8
and GeoModel, s.r.o., Bratislava, Slovakia
9
email: hofierka@geomodel.sk, marcel.suri@jrc.it, suri@geomodel.sk
10
*******************************************************************************/
12
* This program is free software; you can redistribute it and/or
13
* modify it under the terms of the GNU General Public License
14
* as published by the Free Software Foundation; either version 2
15
* of the License, or (at your option) any later version.
17
* This program is distributed in the hope that it will be useful,
18
* but WITHOUT ANY WARRANTY; without even the implied warranty of
19
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
* GNU General Public License for more details.
22
* You should have received a copy of the GNU General Public License
23
* along with this program; if not, write to the Free Software
24
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
27
/*v. 2.0 July 2002, NULL data handling, JH */
28
/*v. 2.1 January 2003, code optimization by Thomas Huld, JH */
32
#include <grass/gis.h>
33
#include <grass/gprojects.h>
34
#include <grass/glocale.h>
35
#include "sunradstruct.h"
36
#include "local_proto.h"
37
#include "rsunglobals.h"
45
void setUseCivilTime(int val)
51
double angular_loss_denom;
53
void setAngularLossDenominator()
55
angular_loss_denom = 1. / (1 - exp(-1. / a_r));
65
void setUseShadow(int val)
72
int useHorizonDataFlag;
75
return useHorizonDataFlag;
77
void setUseHorizonData(int val)
79
useHorizonDataFlag = val;
83
double getTimeOffset()
87
void setTimeOffset(double val)
92
double horizonInterval;
93
double getHorizonInterval()
95
return horizonInterval;
97
void setHorizonInterval(double val)
99
horizonInterval = val;
103
/* com_sol_const(): compute the Solar Constant corrected for the day of the
104
year. The Earth is closest to the Sun (Perigee) on about January 3rd,
105
it is furthest from the sun (Apogee) about July 6th. The 1367 W/m^2 solar
106
constant is at the average 1AU distance, but on Jan 3 it gets up to
107
around 1412.71 W/m^2 and on July 6 it gets down to around 1321 W/m^2.
108
This value is for what hits the top of the atmosphere before any energy
110
double com_sol_const(int no_of_day)
114
/* Solar constant: 1367.0 W/m^2.
116
Perigee offset: here we call Jan 2 at 8:18pm the Perigee, so day
117
number 2.8408. In angular units that's (2*pi * 2.8408 / 365.25) = 0.048869.
119
Orbital eccentricity: For Earth this is currently about 0.01672,
120
and so the distance to the sun varies by +/- 0.01672 from the
121
mean distance (1AU), so over the year the amplitude of the
122
function is 2*ecc = 0.03344.
124
And 365.25 is of course the number of days in a year.
128
d1 = pi2 * no_of_day / 365.25;
129
I0 = 1367. * (1 + 0.03344 * cos(d1 - 0.048869));
135
void com_par_const(double longitTime, struct SunGeometryConstDay *sungeom,
136
struct GridGeometry *gridGeom)
139
double totOffsetTime;
141
sungeom->lum_C11 = gridGeom->sinlat * sungeom->cosdecl;
142
sungeom->lum_C13 = -gridGeom->coslat * sungeom->sindecl;
143
sungeom->lum_C22 = sungeom->cosdecl;
144
sungeom->lum_C31 = gridGeom->coslat * sungeom->cosdecl;
145
sungeom->lum_C33 = gridGeom->sinlat * sungeom->sindecl;
147
if (fabs(sungeom->lum_C31) >= EPS) {
148
totOffsetTime = timeOffset + longitTime;
150
if (useCivilTime()) {
151
sungeom->timeAngle -= totOffsetTime * HOURANGLE;
153
pom = -sungeom->lum_C33 / sungeom->lum_C31;
154
if (fabs(pom) <= 1) {
156
pom = (pom * 180) / M_PI;
157
sungeom->sunrise_time = (90 - pom) / 15 + 6;
158
sungeom->sunset_time = (pom - 90) / 15 + 18;
162
/* G_debug(3,"\n Sun is ABOVE the surface during the whole day"); */
163
sungeom->sunrise_time = 0;
164
sungeom->sunset_time = 24;
167
/* G_debug(3,"\n The sun is BELOW the surface during the whole day"); */
168
if (fabs(pom) - 1 <= EPS) {
169
sungeom->sunrise_time = 12;
170
sungeom->sunset_time = 12;
182
void com_par(struct SunGeometryConstDay *sungeom,
183
struct SunGeometryVarDay *sunVarGeom,
184
struct GridGeometry *gridGeom, double latitude, double longitude)
186
double pom, xpom, ypom;
189
double lum_Lx, lum_Ly;
191
double newLatitude, newLongitude;
193
double delt_lat, delt_lon;
194
double delt_east, delt_nor;
198
costimeAngle = cos(sungeom->timeAngle);
201
lum_Lx = -sungeom->lum_C22 * sin(sungeom->timeAngle);
202
lum_Ly = sungeom->lum_C11 * costimeAngle + sungeom->lum_C13;
203
sunVarGeom->sinSolarAltitude =
204
sungeom->lum_C31 * costimeAngle + sungeom->lum_C33;
206
if (fabs(sungeom->lum_C31) < EPS) {
207
if (fabs(sunVarGeom->sinSolarAltitude) >= EPS) {
208
if (sunVarGeom->sinSolarAltitude > 0) {
209
/* G_debug(3,"\tSun is ABOVE area during the whole day"); */
210
sungeom->sunrise_time = 0;
211
sungeom->sunset_time = 24;
214
sunVarGeom->solarAltitude = 0.;
215
sunVarGeom->solarAzimuth = UNDEF;
220
/* G_debug(3,"\tThe Sun is ON HORIZON during the whole day"); */
221
sungeom->sunrise_time = 0;
222
sungeom->sunset_time = 24;
226
sunVarGeom->solarAltitude = asin(sunVarGeom->sinSolarAltitude); /* vertical angle of the sun */
227
/* sinSolarAltitude is sin(solarAltitude) */
229
xpom = lum_Lx * lum_Lx;
230
ypom = lum_Ly * lum_Ly;
231
pom = sqrt(xpom + ypom);
234
if (fabs(pom) > EPS) {
235
sunVarGeom->solarAzimuth = lum_Ly / pom;
236
sunVarGeom->solarAzimuth = acos(sunVarGeom->solarAzimuth); /* horiz. angle of the Sun */
237
/* solarAzimuth *= RAD; */
239
sunVarGeom->solarAzimuth = pi2 - sunVarGeom->solarAzimuth;
242
sunVarGeom->solarAzimuth = UNDEF;
247
if (sunVarGeom->solarAzimuth < 0.5 * M_PI)
248
sunVarGeom->sunAzimuthAngle = 0.5 * M_PI - sunVarGeom->solarAzimuth;
250
sunVarGeom->sunAzimuthAngle = 2.5 * M_PI - sunVarGeom->solarAzimuth;
253
inputAngle = sunVarGeom->sunAzimuthAngle + pihalf;
254
inputAngle = (inputAngle >= pi2) ? inputAngle - pi2 : inputAngle;
257
delt_lat = -0.0001 * cos(inputAngle); /* Arbitrary small distance in latitude */
258
delt_lon = 0.0001 * sin(inputAngle) / cos(latitude);
260
newLatitude = (latitude + delt_lat) * rad2deg;
261
newLongitude = (longitude + delt_lon) * rad2deg;
264
if ((G_projection() != PROJECTION_LL)) {
265
if (pj_do_proj(&newLongitude, &newLatitude, &oproj, &iproj) < 0) {
266
G_fatal_error("Error in pj_do_proj");
270
delt_east = newLongitude - gridGeom->xp;
271
delt_nor = newLatitude - gridGeom->yp;
273
delt_dist = sqrt(delt_east * delt_east + delt_nor * delt_nor);
276
sunVarGeom->stepsinangle = gridGeom->stepxy * delt_nor / delt_dist;
277
sunVarGeom->stepcosangle = gridGeom->stepxy * delt_east / delt_dist;
280
sunVarGeom->stepsinangle = stepxy * sin(sunVarGeom->sunAzimuthAngle);
281
sunVarGeom->stepcosangle = stepxy * cos(sunVarGeom->sunAzimuthAngle);
283
sunVarGeom->tanSolarAltitude = tan(sunVarGeom->solarAltitude);
290
int searching(double *length, struct SunGeometryVarDay *sunVarGeom,
291
struct GridGeometry *gridGeom)
294
double curvature_diff;
297
if (sunVarGeom->zp == UNDEFZ)
301
gridGeom->yy0 += sunVarGeom->stepsinangle;
302
gridGeom->xx0 += sunVarGeom->stepcosangle;
303
if (((gridGeom->xx0 + (0.5 * gridGeom->stepx)) < 0)
304
|| ((gridGeom->xx0 + (0.5 * gridGeom->stepx)) > gridGeom->deltx)
305
|| ((gridGeom->yy0 + (0.5 * gridGeom->stepy)) < 0)
306
|| ((gridGeom->yy0 + (0.5 * gridGeom->stepy)) > gridGeom->delty))
313
where_is_point(length, sunVarGeom, gridGeom);
315
gridGeom->xx0 = gridGeom->xg0;
316
gridGeom->yy0 = gridGeom->yg0;
319
curvature_diff = EARTHRADIUS * (1. - cos(*length / EARTHRADIUS));
321
z2 = sunVarGeom->z_orig + curvature_diff +
322
*length * sunVarGeom->tanSolarAltitude;
323
if (z2 < sunVarGeom->zp)
324
succes = 2; /* shadow */
325
if (z2 > sunVarGeom->zmax)
326
succes = 3; /* no test needed all visible */
330
gridGeom->xx0 = gridGeom->xg0;
331
gridGeom->yy0 = gridGeom->yg0;
339
double lumcline2(struct SunGeometryConstDay *sungeom,
340
struct SunGeometryVarDay *sunVarGeom,
341
struct SunGeometryVarSlope *sunSlopeGeom,
342
struct GridGeometry *gridGeom, unsigned char *horizonpointer)
349
double timeoffset, horizPos;
350
double horizonHeight;
353
sunVarGeom->isShadow = 0;
358
if (useHorizonData()) {
359
/* Start is due east, sungeom->timeangle = -pi/2 */
360
/* timeoffset = sungeom->timeAngle+pihalf; */
361
timeoffset = sunVarGeom->sunAzimuthAngle;
366
else if(timeoffset>pi2)
368
horizPos = arrayNumInt - timeoffset/horizonInterval;
371
horizPos = timeoffset / getHorizonInterval();
374
lowPos = (int)horizPos;
375
highPos = lowPos + 1;
376
if (highPos == arrayNumInt) {
379
horizonHeight = invScale * ((1. -
381
lowPos)) * horizonpointer[lowPos]
382
+ (horizPos - lowPos)
383
* horizonpointer[highPos]);
384
sunVarGeom->isShadow =
385
(horizonHeight > sunVarGeom->solarAltitude);
387
if (!sunVarGeom->isShadow) {
388
/* if (z_orig != UNDEFZ) {
389
s = sunSlopeGeom->lum_C31_l
390
* cos(-sungeom->timeAngle - sunSlopeGeom->longit_l)
391
+ sunSlopeGeom->lum_C33_l;
393
s = sunVarGeom->sinSolarAltitude;
396
s = sunSlopeGeom->lum_C31_l
397
* cos(-sungeom->timeAngle - sunSlopeGeom->longit_l)
398
+ sunSlopeGeom->lum_C33_l; /* Jenco */
401
} /* End if useHorizonData() */
403
while ((r = searching(&length, sunVarGeom, gridGeom)) == 1) {
405
break; /* no test is needed */
410
sunVarGeom->isShadow = 1; /* shadow */
414
/* if (z_orig != UNDEFZ) {
415
s = sunSlopeGeom->lum_C31_l
416
* cos(-sungeom->timeAngle - sunSlopeGeom->longit_l)
417
+ sunSlopeGeom->lum_C33_l;
419
s = sunVarGeom->sinSolarAltitude;
422
s = sunSlopeGeom->lum_C31_l
423
* cos(-sungeom->timeAngle - sunSlopeGeom->longit_l)
424
+ sunSlopeGeom->lum_C33_l; /* Jenco */
429
/* if (z_orig != UNDEFZ) {
430
s = sunSlopeGeom->lum_C31_l
431
* cos(-sungeom->timeAngle - sunSlopeGeom->longit_l)
432
+ sunSlopeGeom->lum_C33_l;
434
s = sunVarGeom->sinSolarAltitude;
437
s = sunSlopeGeom->lum_C31_l
438
* cos(-sungeom->timeAngle - sunSlopeGeom->longit_l)
439
+ sunSlopeGeom->lum_C33_l; /* Jenco */
442
/* if (s <= 0) return UNDEFZ; ?? */
451
double brad(double sh, double *bh, struct SunGeometryVarDay *sunVarGeom,
452
struct SunGeometryVarSlope *sunSlopeGeom,
453
struct SolarRadVar *sunRadVar)
455
double opticalAirMass, airMass2Linke, rayl, br;
456
double drefract, temp1, temp2, h0refract;
457
double elevationCorr;
459
double locSolarAltitude;
461
locSolarAltitude = sunVarGeom->solarAltitude;
463
/* FIXME: please document all coefficients */
464
elevationCorr = exp(-sunVarGeom->z_orig / 8434.5);
465
temp1 = 0.1594 + locSolarAltitude * (1.123 + 0.065656 * locSolarAltitude);
466
temp2 = 1. + locSolarAltitude * (28.9344 + 277.3971 * locSolarAltitude);
467
drefract = 0.061359 * temp1 / temp2; /* in radians */
468
h0refract = locSolarAltitude + drefract;
469
opticalAirMass = elevationCorr / (sin(h0refract) +
470
0.50572 * pow(h0refract * rad2deg +
472
airMass2Linke = 0.8662 * sunRadVar->linke;
473
if (opticalAirMass <= 20.) {
474
rayl = 1. / (6.6296 +
475
opticalAirMass * (1.7513 +
476
opticalAirMass * (-0.1202 +
483
rayl = 1. / (10.4 + 0.718 * opticalAirMass);
486
sunRadVar->cbh * sunRadVar->G_norm_extra *
487
sunVarGeom->sinSolarAltitude * exp(-rayl * opticalAirMass *
489
if (sunSlopeGeom->aspect != UNDEF && sunSlopeGeom->slope != 0.)
490
br = *bh * sh / sunVarGeom->sinSolarAltitude;
497
double brad_angle_loss(double sh, double *bh,
498
struct SunGeometryVarDay *sunVarGeom,
499
struct SunGeometryVarSlope *sunSlopeGeom,
500
struct SolarRadVar *sunRadVar)
502
double p, opticalAirMass, airMass2Linke, rayl, br;
503
double drefract, temp1, temp2, h0refract;
505
double locSolarAltitude;
507
locSolarAltitude = sunVarGeom->solarAltitude;
509
/* FIXME: please document all coefficients */
510
p = exp(-sunVarGeom->z_orig / 8434.5);
511
temp1 = 0.1594 + locSolarAltitude * (1.123 + 0.065656 * locSolarAltitude);
512
temp2 = 1. + locSolarAltitude * (28.9344 + 277.3971 * locSolarAltitude);
513
drefract = 0.061359 * temp1 / temp2; /* in radians */
514
h0refract = locSolarAltitude + drefract;
515
opticalAirMass = p / (sin(h0refract) +
516
0.50572 * pow(h0refract * rad2deg + 6.07995,
518
airMass2Linke = 0.8662 * sunRadVar->linke;
519
if (opticalAirMass <= 20.)
523
(1.7513 + opticalAirMass *
524
(-0.1202 + opticalAirMass *
525
(0.0065 - opticalAirMass * 0.00013))));
527
rayl = 1. / (10.4 + 0.718 * opticalAirMass);
529
sunRadVar->cbh * sunRadVar->G_norm_extra *
530
sunVarGeom->sinSolarAltitude * exp(-rayl * opticalAirMass *
532
if (sunSlopeGeom->aspect != UNDEF && sunSlopeGeom->slope != 0.)
533
br = *bh * sh / sunVarGeom->sinSolarAltitude;
537
br *= (1 - exp(-sh / a_r)) * angular_loss_denom;
544
double drad(double sh, double bh, double *rr,
545
struct SunGeometryVarDay *sunVarGeom,
546
struct SunGeometryVarSlope *sunSlopeGeom,
547
struct SolarRadVar *sunRadVar)
549
double tn, fd, fx = 0., A1, A2, A3, A1b;
550
double r_sky, kb, dr, gh, a_ln, ln, fg;
552
double cosslope, sinslope;
553
double locSinSolarAltitude;
556
locLinke = sunRadVar->linke;
557
locSinSolarAltitude = sunVarGeom->sinSolarAltitude;
559
cosslope = cos(sunSlopeGeom->slope);
560
sinslope = sin(sunSlopeGeom->slope);
562
/* FIXME: please document all coefficients */
563
tn = -0.015843 + locLinke * (0.030543 + 0.0003797 * locLinke);
564
A1b = 0.26463 + locLinke * (-0.061581 + 0.0031408 * locLinke);
565
if (A1b * tn < 0.0022)
569
A2 = 2.04020 + locLinke * (0.018945 - 0.011161 * locLinke);
570
A3 = -1.3025 + locLinke * (0.039231 + 0.0085079 * locLinke);
572
fd = A1 + A2 * locSinSolarAltitude +
573
A3 * locSinSolarAltitude * locSinSolarAltitude;
574
dh = sunRadVar->cdh * sunRadVar->G_norm_extra * fd * tn;
576
if (sunSlopeGeom->aspect != UNDEF && sunSlopeGeom->slope != 0.) {
577
kb = bh / (sunRadVar->G_norm_extra * locSinSolarAltitude);
578
r_sky = (1. + cosslope) / 2.;
579
a_ln = sunVarGeom->solarAzimuth - sunSlopeGeom->aspect;
583
else if (a_ln < -M_PI)
586
fg = sinslope - sunSlopeGeom->slope * cosslope -
587
M_PI * sin(0.5 * sunSlopeGeom->slope) * sin(0.5 *
588
sunSlopeGeom->slope);
589
if ((sunVarGeom->isShadow == 1) || sh <= 0.)
590
fx = r_sky + fg * 0.252271;
591
else if (sunVarGeom->solarAltitude >= 0.1) {
592
fx = ((0.00263 - kb * (0.712 + 0.6883 * kb)) * fg + r_sky) *
593
(1. - kb) + kb * sh / locSinSolarAltitude;
595
else if (sunVarGeom->solarAltitude < 0.1)
596
fx = ((0.00263 - 0.712 * kb - 0.6883 * kb * kb) * fg +
597
r_sky) * (1. - kb) + kb *
598
sinslope * cos(a_ln) /
599
(0.1 - 0.008 * sunVarGeom->solarAltitude);
602
*rr = sunRadVar->alb * gh * (1 - cosslope) / 2.;
613
double drad_angle_loss(double sh, double bh, double *rr,
614
struct SunGeometryVarDay *sunVarGeom,
615
struct SunGeometryVarSlope *sunSlopeGeom,
616
struct SolarRadVar *sunRadVar)
619
double tn, fd, fx = 0., A1, A2, A3, A1b;
620
double r_sky, kb, dr, gh, a_ln, ln, fg;
621
double cosslope, sinslope;
623
double diff_coeff_angleloss;
624
double refl_coeff_angleloss;
626
double diff_loss_factor, refl_loss_factor;
627
double locSinSolarAltitude;
630
locSinSolarAltitude = sunVarGeom->sinSolarAltitude;
631
locLinke = sunRadVar->linke;
632
cosslope = cos(sunSlopeGeom->slope);
633
sinslope = sin(sunSlopeGeom->slope);
635
/* FIXME: please document all coefficients */
636
tn = -0.015843 + locLinke * (0.030543 + 0.0003797 * locLinke);
637
A1b = 0.26463 + locLinke * (-0.061581 + 0.0031408 * locLinke);
639
if (A1b * tn < 0.0022)
643
A2 = 2.04020 + locLinke * (0.018945 - 0.011161 * locLinke);
644
A3 = -1.3025 + locLinke * (0.039231 + 0.0085079 * locLinke);
646
fd = A1 + A2 * locSinSolarAltitude +
647
A3 * locSinSolarAltitude * locSinSolarAltitude;
648
dh = sunRadVar->cdh * sunRadVar->G_norm_extra * fd * tn;
651
if (sunSlopeGeom->aspect != UNDEF && sunSlopeGeom->slope != 0.) {
653
kb = bh / (sunRadVar->G_norm_extra * locSinSolarAltitude);
654
r_sky = (1. + cosslope) / 2.;
655
a_ln = sunVarGeom->solarAzimuth - sunSlopeGeom->aspect;
660
else if (a_ln < -M_PI)
663
fg = sinslope - sunSlopeGeom->slope * cosslope -
664
M_PI * sin(sunSlopeGeom->slope / 2.) * sin(sunSlopeGeom->slope /
666
if ((sunVarGeom->isShadow) || (sh <= 0.))
667
fx = r_sky + fg * 0.252271;
668
else if (sunVarGeom->solarAltitude >= 0.1) {
669
fx = ((0.00263 - kb * (0.712 + 0.6883 * kb)) * fg + r_sky) * (1. -
671
+ kb * sh / locSinSolarAltitude;
673
else if (sunVarGeom->solarAltitude < 0.1)
674
fx = ((0.00263 - 0.712 * kb - 0.6883 * kb * kb) * fg +
675
r_sky) * (1. - kb) + kb * sinslope * cos(a_ln) /
676
(0.1 - 0.008 * sunVarGeom->solarAltitude);
680
*rr = sunRadVar->alb * gh * (1 - cosslope) / 2.;
687
c1 = 4. / (3. * M_PI);
688
diff_coeff_angleloss = sinslope
689
+ (M_PI - sunSlopeGeom->slope - sinslope) / (1 + cosslope);
690
refl_coeff_angleloss = sinslope
691
+ (sunSlopeGeom->slope - sinslope) / (1 - cosslope);
694
= 1. - exp(-(c1 * diff_coeff_angleloss
695
+ c2 * diff_coeff_angleloss * diff_coeff_angleloss)
698
= 1. - exp(-(c1 * refl_coeff_angleloss
699
+ c2 * refl_coeff_angleloss * refl_coeff_angleloss)
702
dr *= diff_loss_factor;
703
*rr *= refl_loss_factor;