Geo Stars Library  0.9.3
Geodetic and Astrometry library
geoSun.c
Go to the documentation of this file.
1 /*!
2 \file geoAstro.c
3 \brief This file contains the wrapper routines to interface to the
4  Novas (Naval Observatory Vector Astrometry Subroutine) Library.
5 
6  http://aa.usno.navy.mil/software/novas/novas_info.html
7 
8 */
9 
10 #include <stdio.h>
11 #include <time.h>
12 #include <math.h>
13 #include "geoStars.h"
14 
15 
16 // return the julian day of the year
17 int jday(int month, int day)
18 {
19 
20  /* Days in each month 1-12 */
21  int jday[] = {0,0,31,59,90,120,151,180,211,242,272,303,333 };
22 
23  /* Determin decimal years from date */
24  return(jday[month]+day);
25 }
26 
27 // integer part
28 double ip(double x)
29 {
30  double dummy, tmp;
31 
32  dummy = modf(x, &tmp);
33  return tmp;
34 }
35 
36 
37 double fractionalYearRad(int month, int day, int hour)
38 {
39  double y;
40  y = (TWO_PI/365.0) * (jday(month,day)-1.0 +((hour-12.0)/24));
41  return(y);
42 }
43 
44 // Number of days from J2000
45 double n(int month, int day, int hour, int min, int sec)
46 {
47  return(364.5 +jday(month,day) + (hour + min/60.0 + sec/3600.0)/24.0);
48 }
49 
50 double JD(int year, int month, int day)
51 {
52 int A, B, m, y;
53 
54  if (month > 2)
55  {
56  y = year;
57  m = month;
58  }
59  else
60  {
61  y = year - 1;
62  m = month + 12;
63  }
64  A = y / 100;
65  if ((year > 1582) || ((year == 1582) && ((month > 10)
66  || ((month == 10) && (day > 4)))))
67  B = 2 - A + A / 4;
68  else
69  B = 0;
70  return ip (365.25 * (y + 4716)) + ip (30.6001 * (m + 1)) + day + B - 1524.5 ;
71 }
72 //-----------------------------------------------------------------
73 /*!
74 \brief this routine uses ANSI C time routines to obtain the current time
75 and then get the az and el of the sun at this location
76 
77 \param GEO_LOCATION *loc : Location structure
78 \param double *az : Azimuth in decimal degrees
79 \param double *el : Elevation in decimal degrees
80 \retval GEO_OK on success
81 \retval GEO_ERROR on error
82 */
83 
84 int DLL_CALLCONV geoSunPosition(GEO_LOCATION *loc, double *az, double *el)
85 {
86  struct tm *newtime;
87  long ltime=0L;
88 
89 
90  /* Obtain time: */
91  time( &ltime );
92  newtime = gmtime( &ltime );
93 
94  geoSun(loc,newtime,az,el);
95  return (GEO_OK );
96 }
97 
98 int DLL_CALLCONV geoSun(GEO_LOCATION *loc, struct tm *newtime,double *az, double *el)
99 {
100  double y,eqtime,decl,time_offset, tst,ha,theta,phi,sdecl;
101 
102 
103  // This Sun position algorithm is from :
104  // http://www.esrl.noaa.gov/gmd/grad/solcalc/solareqns.PDF
105 
106 
107  // First, the fractional year y is calculated, in radians.
108  y = fractionalYearRad(newtime->tm_mon, newtime->tm_mday, newtime->tm_hour);
109 
110  // From y, we can estimate the equation of time (in minutes) and the
111  // solar declination angle (in radians).
112  eqtime = 229.18 * (0.000075 +
113  (0.001868 * cos(y))-
114  (0.032077 * sin(y))-
115  (0.014615 * cos(2.0 * y)) -
116  (0.040849 * sin(2.0 * y)) );
117 
118 
119  decl = 0.006918 -
120  (0.399912* cos(y)) +
121  (0.070257* sin(y)) -
122  (0.006758* cos(2.0 * y)) +
123  (0.000907* sin(2.0 * y)) -
124  (0.002697* cos(3.0 * y)) +
125  (0.00148 * sin(3.0 * y));
126  sdecl = sin(decl);
127 
128  // Next, the true solar time is calculated in the following two equations.
129  // First the time offset is found, in minutes, and then the true solar time, in minutes.
130  time_offset = eqtime - (4.0 * loc->lon) + (60.0 * loc->timezone);
131 
132  // where eqtime is in minutes, longitude is in degrees, timezone is in hours
133  // from UTC (Mountain Standard Time = +7 hours).
134  tst = (newtime->tm_hour * 60.0) + newtime->tm_min + (newtime->tm_sec / 60.0) + time_offset;
135  // where hr is the hour (0 - 23), mn is the minute (0 - 60), sc is the second (0 - 60).
136 
137  // The solar hour angle, in degrees, is:
138  ha = (tst / 4.0) - 180.0;
139 
140  //The solar zenith angle (phi) can then be found from the following equation:
141  phi = acos( (loc->slat * sdecl) + (loc->clat * cos(decl) * cos(ha*DEG_TO_RAD)));
142 
143  // And the solar azimuth (theta), clockwise from north) is:
144  // cos(180-theta) = (loc->slat * cos(phi) - sin(decl)) / (loc->clat * sin(phi))
145  theta = acos(((loc->slat * cos(phi)) - sdecl) / (loc->clat * sin(phi)));
146 
147  *el = 90.0 - (phi*RAD_TO_DEG);
148  *az = theta * RAD_TO_DEG;
149 
150  printf("\nY=%f Eqtime=%f decl=%f\n tOff=%f tst=%f ha=%f tz=%f \n",y,eqtime,decl,time_offset,tst,ha,loc->timezone );
151  printf("Phi=%f, theta=%f\n", phi, theta);
152 
153  return (GEO_OK );
154 }
155 
156 // ------------------------------------------------------------------------
157 
158 int DLL_CALLCONV geoSunM(GEO_LOCATION *loc, struct tm *newtime,double *az, double *el)
159 {
160 /*
161 From
162 ftp://climate1.gsfc.nasa.gov/wiscombe/Solar_Rad/SunAngles/sunae.f
163 
164 
165 c Calculates the local solar azimuth and elevation angles, and
166 c the distance to and angle subtended by the Sun, at a specific
167 c location and time using approximate formulas in The Astronomical
168 c Almanac. Accuracy of angles is 0.01 deg or better (the angular
169 c width of the Sun is about 0.5 deg, so 0.01 deg is more than
170 c sufficient for most applications).
171 
172 c Unlike many GCM (and other) sun angle routines, this
173 c one gives slightly different sun angles depending on
174 c the year. The difference is usually down in the 4th
175 c significant digit but can slowly creep up to the 3rd
176 c significant digit after several decades to a century.
177 
178 c A refraction correction appropriate for the "US Standard
179 c Atmosphere" is added, so that the returned sun position is
180 c the APPARENT one. The correction is below 0.1 deg for solar
181 c elevations above 9 deg. To remove it, comment out the code
182 c block where variable REFRAC is set, and replace it with
183 c REFRAC = 0.0.
184 
185 c References:
186 
187 c Michalsky, J., 1988: The Astronomical Almanac's algorithm for
188 c approximate solar position (1950-2050), Solar Energy 40,
189 c 227-235 (but the version of this program in the Appendix
190 c contains errors and should not be used)
191 
192 c The Astronomical Almanac, U.S. Gov't Printing Office, Washington,
193 c D.C. (published every year): the formulas used from the 1995
194 c version are as follows:
195 c p. A12: approximation to sunrise/set times
196 c p. B61: solar elevation ("altitude") and azimuth
197 c p. B62: refraction correction
198 c p. C24: mean longitude, mean anomaly, ecliptic longitude,
199 c obliquity of ecliptic, right ascension, declination,
200 c Earth-Sun distance, angular diameter of Sun
201 c p. L2: Greenwich mean sidereal time (ignoring T^2, T^3 terms)
202 
203 
204 c Authors: Dr. Joe Michalsky (joe@asrc.albany.edu)
205 c Dr. Lee Harrison (lee@asrc.albany.edu)
206 c Atmospheric Sciences Research Center
207 c State University of New York
208 c Albany, New York
209 
210 c Modified by: Dr. Warren Wiscombe (wiscombe@climate.gsfc.nasa.gov)
211 c NASA Goddard Space Flight Center
212 c Code 913
213 c Greenbelt, MD 20771
214 
215 
216 c WARNING: Do not use this routine outside the year range
217 c 1950-2050. The approximations become increasingly
218 c worse, and the calculation of Julian date becomes
219 c more involved.
220 
221 
222  SUBROUTINE SUNAE( YEAR, DAY, HOUR, LAT, LONG,
223  & AZ, EL, SOLDIA, SOLDST )
224 
225 
226  c Input:
227  c YEAR year (INTEGER; range 1950 to 2050)
228  c DAY day of year at LAT-LONG location (INTEGER; range 1-366)
229  c HOUR hour of DAY [GMT or UT] (REAL; range -13.0 to 36.0)
230  c = (local hour) + (time zone number)
231  c + (Daylight Savings Time correction; -1 or 0)
232  C where (local hour) range is 0 to 24,
233  c (time zone number) range is -12 to +12, and
234  c (Daylight Time correction) is -1 if on Daylight Time
235  c (summer half of year), 0 otherwise;
236  c Example: 8:30 am Eastern Daylight Time would be
237  c
238  c HOUR = 8.5 + 5 - 1 = 12.5
239  c LAT latitude [degrees]
240  c (REAL; range -90.0 to 90.0; north is positive)
241  c LONG longitude [degrees]
242  c (REAL; range -180.0 to 180.0; east is positive)
243  c Output:
244  c AZ solar azimuth angle (measured east from north,
245  c 0 to 360 degs)
246  c EL solar elevation angle [-90 to 90 degs];
247  c solar zenith angle = 90 - EL
248  c SOLDIA solar diameter [degs]
249  c SOLDST distance to sun [Astronomical Units, AU]
250  c (1 AU = mean Earth-sun distance = 1.49597871E+11 m
251  c in IAU 1976 System of Astronomical Constants)
252  c Local Variables:
253  c DEC Declination (radians)
254  c ECLONG Ecliptic longitude (radians)
255  c GMST Greenwich mean sidereal time (hours)
256  c HA Hour angle (radians, -pi to pi)
257  c JD Modified Julian date (number of days, including
258  c fractions thereof, from Julian year J2000.0);
259  c actual Julian date is JD + 2451545.0
260  c LMST Local mean sidereal time (radians)
261  c MNANOM Mean anomaly (radians, normalized to 0 to 2*pi)
262  c MNLONG Mean longitude of Sun, corrected for aberration
263  c (deg; normalized to 0-360)
264  c OBLQEC Obliquity of the ecliptic (radians)
265  c RA Right ascension (radians)
266  c REFRAC Refraction correction for US Standard Atmosphere (degs)
267  c --------------------------------------------------------------------
268  */
269 
270  //c .. Scalar Arguments ..
271 
272  int YEAR, DAY;
273  double AZ, EL, AZr, ELr, HOUR, SOLDIA, SOLDST;
274  //c ..
275  //c .. Local Scalars ..
276 
277  int DELTA, LEAP;
278  double DEC, DEN, ECLONG, GMST, HA, JD, LMST,
279  MNANOM, MNLONG, NUM, OBLQEC, RA,
280  REFRAC=0.0, TIME;
281  //c ..
282 
283 /*
284  if( YEAR < 1950 .OR. YEAR.GT.2050 )
285  & STOP 'SUNAE--bad input variable YEAR'
286  if( DAY < 1 .OR. DAY.GT.366 )
287  & STOP 'SUNAE--bad input variable DAY'
288  if( HOUR < -13.0 .OR. HOUR.GT.36.0 )
289  & STOP 'SUNAE--bad input variable HOUR'
290  if( LAT < -90.0 .OR. LAT.GT.90.0 )
291  & STOP 'SUNAE--bad input variable LAT'
292  if( LONG < -180.0 .OR. LONG.GT.180.0 )
293  & STOP 'SUNAE--bad input variable LONG'
294 
295  if(PASS1) THEN
296  PI = 2.*A sin( 1.0 )
297  TWOPI = 2.*PI
298  RPD = PI / 180.
299  PASS1 = .False.
300  ENDIF
301  */
302  YEAR = newtime->tm_year+1900;
303  DAY = jday(newtime->tm_mon, newtime->tm_mday);
304  HOUR = newtime->tm_hour + (newtime->tm_min/60.0) + (newtime->tm_sec/3600.0) + loc->timezone - loc->dst;
305 
306  printf("Hour=%f\n",HOUR);
307 
308  // ** current Julian date (actually add 2,400,000
309  // ** for true JD); LEAP = leap days since 1949;
310  // ** 32916.5 is midnite 0 jan 1949 minus 2.4e6
311  DELTA = YEAR - 1949;
312  LEAP = DELTA / 4;
313  JD = 32916.5 + ((DELTA * 365.0) + LEAP + DAY) + HOUR / 24.0 +1;
314 
315  // ** last yr of century not leap yr unless divisible
316  // ** by 400 (not executed for the allowed YEAR range,
317  // ** but left in so our successors can adapt this for
318  // ** the following 100 years)
319 
320  if( ( ( YEAR % 100 ) == 0) && (( YEAR % 400 ) != 0) ) JD = JD - 1.0;
321 
322  // ** ecliptic coordinates
323  // ** 51545.0 + 2.4e6 = noon 1 jan 2000
324  TIME = JD - 51545.0;
325 
326  printf("JD=%f TIME=%f\n",JD,TIME);
327 
328  // ** force mean longitude between 0 and 360 degs
329  MNLONG = 280.460 + 0.9856474 * TIME;
330  MNLONG = fmod( MNLONG, 360.0 );
331  if( MNLONG < 0.0 ) MNLONG = MNLONG + 360.0;
332 
333  // ** mean anomaly in radians between 0 and 2*pi
334  MNANOM = 357.528 + 0.9856003 * TIME;
335  MNANOM = fmod( MNANOM, 360.0 );
336  if( MNANOM < 0.0) MNANOM = MNANOM + 360.0;
337  printf("Mean Long=%f Mean Anom=%f\n",MNLONG,MNANOM);
338 
339  MNANOM = MNANOM * DEG_TO_RAD; //radians
340 
341  // ** ecliptic longitude and obliquity
342  // ** of ecliptic in radians
343  ECLONG = MNLONG + 1.915 * sin( MNANOM ) + 0.020 * sin( 2.0 * MNANOM );
344  ECLONG = fmod( ECLONG, 360.0 );
345  if( ECLONG < 0.0 ) ECLONG = ECLONG + 360.0;
346 
347  OBLQEC = 23.439 - 0.0000004 * TIME;
348  ECLONG = ECLONG * DEG_TO_RAD;
349  OBLQEC = OBLQEC * DEG_TO_RAD;
350  printf("Ecc Long=%f Obliq=%f\n",ECLONG*RAD_TO_DEG,OBLQEC*RAD_TO_DEG);
351 
352  // ** right ascension
353  NUM = cos( OBLQEC )* sin( ECLONG );
354  DEN = cos( ECLONG );
355  RA = atan2( NUM , DEN );
356  printf("RA-raw = %f num=%f, den=%f\n",RA*RAD_TO_DEG,NUM,DEN);
357 
358 
359  // ** Force right ascension between 0 and 2*pi
360  if( DEN < 0.0 ) RA = RA + M_PI;
361  else if( NUM < 0.0 ) RA = RA + TWO_PI;
362 
363  printf("RA = %f ",RA*RAD_TO_DEG);
364 
365  // ** declination
366  DEC = asin( sin(OBLQEC) * sin(ECLONG) );
367 
368  printf("DEC = %f \n",DEC*RAD_TO_DEG);
369 
370  // ** Greenwich mean sidereal time in hours
371  GMST = 6.697375 + 0.0657098242*TIME + HOUR;
372 
373  // ** Hour not changed to sidereal time since
374  // ** 'time' includes the fractional day
375  GMST = fmod( GMST, 24.0 );
376  if( GMST < 0.0 ) GMST = GMST + 24.0;
377 
378  // ** local mean sidereal time in radians
379  LMST = GMST + loc->lon / 15.0;
380  LMST = fmod( LMST, 24.0 );
381  if( LMST < 0.0 ) LMST = LMST + 24.0;
382 
383  LMST = LMST*15.0 * DEG_TO_RAD;
384 
385  // ** hour angle in radians between -pi and pi
386  HA = LMST - RA;
387 
388  if( HA < -M_PI ) HA = HA + TWO_PI;
389  if( HA > M_PI ) HA = HA - TWO_PI;
390 
391  printf("HA=%f deg\n",HA*RAD_TO_DEG);
392  //HA = -7.99 * DEG_TO_RAD;
393 
394 
395  // ** solar azimuth and elevation (radians)
396  ELr = asin( sin( DEC )* loc->slat +
397  cos( DEC )* loc->clat * cos( HA ) );
398 
399  // AZr = asin( -cos( DEC )* sin( HA ) / cos( ELr ) );
400  AZr = atan( sin( HA ) / ((cos( HA ) * loc->slat) - (tan(DEC) * loc->clat)) );
401  printf("raw az=%f DECL = %f\n",AZr*RAD_TO_DEG, DEC*RAD_TO_DEG);
402 
403  // ** Put azimuth between 0 and 2*pi radians
404  if( sin( DEC ) - sin( ELr ) * loc->slat >= 0.0 )
405  {
406 
407  if( sin(AZr) < 0.0) AZr = AZr + TWO_PI;
408  else AZr = M_PI - AZr;
409 
410  }
411 
412  // ** Convert elevation and azimuth to degrees
413  EL = ELr * RAD_TO_DEG;
414  AZ = AZr * RAD_TO_DEG;
415 
416  // ======== Refraction correction for U.S. Standard Atmos. ==========
417  // (assumes elevation in degs) (3.51823=1013.25 mb/288 K)
418  if( EL >= 19.225 ) REFRAC = 0.00452 * 3.51823 / tan( ELr );
419  else
420  {
421  if( (EL > -0.766) && (EL < 19.225) )
422  REFRAC = 3.51823 * ( 0.1594 + EL*(0.0196 + 0.00002*EL) ) /
423  ( 1.0 + EL * (0.505 + 0.0845 * EL) );
424  else if( EL <= -0.766 ) REFRAC = 0.0;
425  }
426  printf("Refrac = %f deg\n",REFRAC);
427  EL = EL + REFRAC;
428 
429  // ===================================================================
430  // ** distance to sun in A.U. & diameter in degs
431 
432  SOLDST = 1.00014 - 0.01671 * cos(MNANOM) - 0.00014 * cos( 2.0 *MNANOM );
433  SOLDIA = 0.5332 / SOLDST;
434 
435  if( EL < -90.0 || EL > 90.0 ) printf("SUNAE--output argument EL out of range(%f)\n",EL);
436  if( AZ < 0.0 || AZ > 360.0 ) printf("SUNAE--output argument AZ out of range(%f)\n",AZ);
437 
438  *az = AZ;
439  *el = EL;
440 
441  return(GEO_OK);
442 }
443 
444 #define oe_base (23.0+(26.0/60.0)+(21.448/3600.0))
445 
446 // t is in julian centuries from J2000
447 double obliquity(double t)
448 {
449  double u,u2,u3,u4,u5,u6,u7,u8,u9,u10;
450  u = t/100.0;
451  u2= u*u;
452  u3=u2*u;
453  u4=u3*u;
454  u5=u4*u;
455  u6=u5*u;
456  u7=u6*u;
457  u8=u7*u;
458  u9=u8*u;
459  u10=u9*u;
460 
461  // high precision
462  return(oe_base
463  - (4680.93/3600.0 * u)
464  - (1.55 /3600.0 * u2)
465  + (1999.25/3600.0 * u3)
466  - (51.38 /3600.0 * u4)
467  - (249.67 /3600.0 * u5)
468  - (39.05 /3600.0 * u6)
469  + (7.12 /3600.0 * u7)
470  + (27.87 /3600.0 * u8)
471  + (5.79 /3600.0 * u9)
472  + (2.45 /3600.0 * u10)
473  );
474 
475  // low precision:
476  //oe = oe_base -( 46.8150 / 3660.0 * t) - (0.00059 / 3660.0 * t2) + (0.001813 / 3660.0 *t3);
477 
478 }
479 
480 // this is the solar position algorithm from
481 // Astronomical Algorithms, by Jean Meeus
482 int DLL_CALLCONV geoSunAA(GEO_LOCATION *loc, struct tm *newtime,double *az, double *el)
483 {
484  double jd,l0, t,t2,t3,m,m_r, e,c,r,
485  trueLon,trueLon_r,trueLon2k,trueAnom,trueAnom_r,
486  partDay,appLon,appLon_r,oe,oec,oe_r,oec_r,om,om_r,ra,dec;
487  int year;
488 
489  year = newtime->tm_year+1900;
490  partDay = (newtime->tm_hour + (newtime->tm_min/60.0) + (newtime->tm_sec / 3600.0) - loc->timezone)/24.0;
491 
492  // j2000 date
493  jd = JD(year,newtime->tm_mon,newtime->tm_mday);
494  printf("\nJD=%10.5f ",jd+partDay);
495 
496  // j2000 date in centuries
497  t=(jd+partDay- 2451545.0)/36525.0;
498  t2 = t*t;
499  t3= t2*t;
500  printf("T=%2.10f\n",t);
501 
502  // Geometric mean longitude referred to the mean equinox of the date
503  l0 = 280.46645 + (36000.76983 * t) + (0.0003032 * t2);
504  if(l0 < 0.0) l0 = fmod(l0,360.0) + 360.0;
505 
506  // mean anomoly of the Sun
507  m = 357.52910 + (35999.05030 * t) - (0.0001559 * t2) - (0.00000048 * t3);
508  // if(m < 0.0) m = fmod(m,360.0) + 360.0;
509  m_r = m * DEG_TO_RAD;
510 
511  // eccentricity of the Earth's orbit
512  e = 0.016708617 - (0.000042037 * t) - (0.0000001236 * t2);
513 
514  // Sun's equation of center
515  c = (1.914600 - (0.004817 * t) - (0.000014 * t2)) * sin(m_r) +
516  (0.019993 - (0.000101 * t)) * sin(m_r+m_r) +
517  (0.000290 * sin(m_r+m_r+m_r));
518 
519  // Sun true longitude & anomoly
520  trueLon = l0 + c;
521  trueLon_r = trueLon * DEG_TO_RAD;
522  trueAnom = m + c;
523  trueAnom_r= trueAnom * DEG_TO_RAD;
524  trueLon2k = trueLon - (0.01397 * (year - 2000));
525  printf("\nl0= %f m=%f e=%f c=%f trueLon=%f trueAnom=%f\n",l0,m,e,c,trueLon,trueAnom);
526 
527  // radius vector
528  r = (1.000001018 * (1.0-(e*e)))/(1.0 + e *cos(trueAnom_r));
529 
530  // apparent longitude
531  om = 125.04 - 1934.136 * t;
532  om_r = om * DEG_TO_RAD;
533  appLon = trueLon - 0.00569 - (0.00478 * sin(om_r));
534  appLon_r= appLon * DEG_TO_RAD;
535 
536  // obliquity of ecliptic
537  // and corrected value (oec)
538  oe = obliquity(t);
539  oec = oe + 0.00256 * cos(DEG_TO_RAD*(125.04-1934.1136*t)); // corrected
540  oe_r = oe * DEG_TO_RAD;
541  oec_r= oec * DEG_TO_RAD;
542  printf("\nom=%f appLon=%f oe=%f oec=%f r=%f\n",om,appLon,oe,oec,r);
543 
544  ra = atan2(cos(oec_r)*sin(appLon_r), cos(appLon_r))*RAD_TO_DEG;
545  // if(ra < 0.0) ra = fmod(ra,360.0); // + 360.0;
546 
547  dec = asin(sin(oec_r)*sin(appLon_r))*RAD_TO_DEG;
548  printf("\nra=%f, dec=%f\n",ra, dec);
549 
550  //
551  return 1;
552 }