root/ext/date/lib/astro.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. astro_revolution
  2. astro_rev180
  3. astro_GMST0
  4. astro_sunpos
  5. astro_sun_RA_dec
  6. timelib_astro_rise_set_altitude
  7. timelib_ts_to_juliandate

   1 /*
   2  * The MIT License (MIT)
   3  *
   4  * Copyright (c) 2015 Derick Rethans
   5  *
   6  * Permission is hereby granted, free of charge, to any person obtaining a copy
   7  * of this software and associated documentation files (the "Software"), to deal
   8  * in the Software without restriction, including without limitation the rights
   9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10  * copies of the Software, and to permit persons to whom the Software is
  11  * furnished to do so, subject to the following conditions:
  12  *
  13  * The above copyright notice and this permission notice shall be included in
  14  * all copies or substantial portions of the Software.
  15  *
  16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22  * THE SOFTWARE.
  23  */
  24 /*
  25    | Algorithms are taken from a public domain source by Paul             |
  26    | Schlyter, who wrote this in December 1992                            |
  27  */
  28 
  29 #include <stdio.h>
  30 #include <math.h>
  31 #include "timelib.h"
  32 
  33 #define days_since_2000_Jan_0(y,m,d) \
  34         (367L*(y)-((7*((y)+(((m)+9)/12)))/4)+((275*(m))/9)+(d)-730530L)
  35 
  36 #ifndef PI
  37  #define PI        3.1415926535897932384
  38 #endif
  39 
  40 #define RADEG     ( 180.0 / PI )
  41 #define DEGRAD    ( PI / 180.0 )
  42 
  43 /* The trigonometric functions in degrees */
  44 
  45 #define sind(x)  sin((x)*DEGRAD)
  46 #define cosd(x)  cos((x)*DEGRAD)
  47 #define tand(x)  tan((x)*DEGRAD)
  48 
  49 #define atand(x)    (RADEG*atan(x))
  50 #define asind(x)    (RADEG*asin(x))
  51 #define acosd(x)    (RADEG*acos(x))
  52 #define atan2d(y,x) (RADEG*atan2(y,x))
  53 
  54 
  55 /* Following are some macros around the "workhorse" function __daylen__ */
  56 /* They mainly fill in the desired values for the reference altitude    */
  57 /* below the horizon, and also selects whether this altitude should     */
  58 /* refer to the Sun's center or its upper limb.                         */
  59 
  60 
  61 #include "astro.h"
  62 
  63 /******************************************************************/
  64 /* This function reduces any angle to within the first revolution */
  65 /* by subtracting or adding even multiples of 360.0 until the     */
  66 /* result is >= 0.0 and < 360.0                                   */
  67 /******************************************************************/
  68 
  69 #define INV360    (1.0 / 360.0)
  70 
  71 /*****************************************/
  72 /* Reduce angle to within 0..360 degrees */
  73 /*****************************************/
  74 static double astro_revolution(double x)
  75 {
  76         return (x - 360.0 * floor(x * INV360));
  77 }
  78 
  79 /*********************************************/
  80 /* Reduce angle to within +180..+180 degrees */
  81 /*********************************************/
  82 static double astro_rev180( double x )
  83 {
  84         return (x - 360.0 * floor(x * INV360 + 0.5));
  85 }
  86 
  87 /*******************************************************************/
  88 /* This function computes GMST0, the Greenwich Mean Sidereal Time  */
  89 /* at 0h UT (i.e. the sidereal time at the Greenwhich meridian at  */
  90 /* 0h UT).  GMST is then the sidereal time at Greenwich at any     */
  91 /* time of the day.  I've generalized GMST0 as well, and define it */
  92 /* as:  GMST0 = GMST - UT  --  this allows GMST0 to be computed at */
  93 /* other times than 0h UT as well.  While this sounds somewhat     */
  94 /* contradictory, it is very practical:  instead of computing      */
  95 /* GMST like:                                                      */
  96 /*                                                                 */
  97 /*  GMST = (GMST0) + UT * (366.2422/365.2422)                      */
  98 /*                                                                 */
  99 /* where (GMST0) is the GMST last time UT was 0 hours, one simply  */
 100 /* computes:                                                       */
 101 /*                                                                 */
 102 /*  GMST = GMST0 + UT                                              */
 103 /*                                                                 */
 104 /* where GMST0 is the GMST "at 0h UT" but at the current moment!   */
 105 /* Defined in this way, GMST0 will increase with about 4 min a     */
 106 /* day.  It also happens that GMST0 (in degrees, 1 hr = 15 degr)   */
 107 /* is equal to the Sun's mean longitude plus/minus 180 degrees!    */
 108 /* (if we neglect aberration, which amounts to 20 seconds of arc   */
 109 /* or 1.33 seconds of time)                                        */
 110 /*                                                                 */
 111 /*******************************************************************/
 112 
 113 static double astro_GMST0(double d)
 114 {
 115         double sidtim0;
 116         /* Sidtime at 0h UT = L (Sun's mean longitude) + 180.0 degr  */
 117         /* L = M + w, as defined in sunpos().  Since I'm too lazy to */
 118         /* add these numbers, I'll let the C compiler do it for me.  */
 119         /* Any decent C compiler will add the constants at compile   */
 120         /* time, imposing no runtime or code overhead.               */
 121         sidtim0 = astro_revolution((180.0 + 356.0470 + 282.9404) + (0.9856002585 + 4.70935E-5) * d);
 122         return sidtim0;
 123 }
 124 
 125 /* This function computes the Sun's position at any instant */
 126 
 127 /******************************************************/
 128 /* Computes the Sun's ecliptic longitude and distance */
 129 /* at an instant given in d, number of days since     */
 130 /* 2000 Jan 0.0.  The Sun's ecliptic latitude is not  */
 131 /* computed, since it's always very near 0.           */
 132 /******************************************************/
 133 static void astro_sunpos(double d, double *lon, double *r)
 134 {
 135         double M,         /* Mean anomaly of the Sun */
 136                w,         /* Mean longitude of perihelion */
 137                           /* Note: Sun's mean longitude = M + w */
 138                e,         /* Eccentricity of Earth's orbit */
 139                E,         /* Eccentric anomaly */
 140                x, y,      /* x, y coordinates in orbit */
 141                v;         /* True anomaly */
 142 
 143         /* Compute mean elements */
 144         M = astro_revolution(356.0470 + 0.9856002585 * d);
 145         w = 282.9404 + 4.70935E-5 * d;
 146         e = 0.016709 - 1.151E-9 * d;
 147 
 148         /* Compute true longitude and radius vector */
 149         E = M + e * RADEG * sind(M) * (1.0 + e * cosd(M));
 150         x = cosd(E) - e;
 151         y = sqrt(1.0 - e*e) * sind(E);
 152         *r = sqrt(x*x + y*y);              /* Solar distance */
 153         v = atan2d(y, x);                  /* True anomaly */
 154         *lon = v + w;                        /* True solar longitude */
 155         if (*lon >= 360.0) {
 156                 *lon -= 360.0;                   /* Make it 0..360 degrees */
 157         }
 158 }
 159 
 160 static void astro_sun_RA_dec(double d, double *RA, double *dec, double *r)
 161 {
 162         double lon, obl_ecl, x, y, z;
 163 
 164         /* Compute Sun's ecliptical coordinates */
 165         astro_sunpos(d, &lon, r);
 166 
 167         /* Compute ecliptic rectangular coordinates (z=0) */
 168         x = *r * cosd(lon);
 169         y = *r * sind(lon);
 170 
 171         /* Compute obliquity of ecliptic (inclination of Earth's axis) */
 172         obl_ecl = 23.4393 - 3.563E-7 * d;
 173 
 174         /* Convert to equatorial rectangular coordinates - x is unchanged */
 175         z = y * sind(obl_ecl);
 176         y = y * cosd(obl_ecl);
 177 
 178         /* Convert to spherical coordinates */
 179         *RA = atan2d(y, x);
 180         *dec = atan2d(z, sqrt(x*x + y*y));
 181 }
 182 
 183 /**
 184  * Note: timestamp = unixtimestamp (NEEDS to be 00:00:00 UT)
 185  *       Eastern longitude positive, Western longitude negative
 186  *       Northern latitude positive, Southern latitude negative
 187  *       The longitude value IS critical in this function!
 188  *       altit = the altitude which the Sun should cross
 189  *               Set to -35/60 degrees for rise/set, -6 degrees
 190  *               for civil, -12 degrees for nautical and -18
 191  *               degrees for astronomical twilight.
 192  *         upper_limb: non-zero -> upper limb, zero -> center
 193  *               Set to non-zero (e.g. 1) when computing rise/set
 194  *               times, and to zero when computing start/end of
 195  *               twilight.
 196  *        *rise = where to store the rise time
 197  *        *set  = where to store the set  time
 198  *                Both times are relative to the specified altitude,
 199  *                and thus this function can be used to compute
 200  *                various twilight times, as well as rise/set times
 201  * Return value:  0 = sun rises/sets this day, times stored at
 202  *                    *trise and *tset.
 203  *               +1 = sun above the specified "horizon" 24 hours.
 204  *                    *trise set to time when the sun is at south,
 205  *                    minus 12 hours while *tset is set to the south
 206  *                    time plus 12 hours. "Day" length = 24 hours
 207  *               -1 = sun is below the specified "horizon" 24 hours
 208  *                    "Day" length = 0 hours, *trise and *tset are
 209  *                    both set to the time when the sun is at south.
 210  *
 211  */
 212 int timelib_astro_rise_set_altitude(timelib_time *t_loc, double lon, double lat, double altit, int upper_limb, double *h_rise, double *h_set, timelib_sll *ts_rise, timelib_sll *ts_set, timelib_sll *ts_transit)
 213 {
 214         double  d,  /* Days since 2000 Jan 0.0 (negative before) */
 215         sr,         /* Solar distance, astronomical units */
 216         sRA,        /* Sun's Right Ascension */
 217         sdec,       /* Sun's declination */
 218         sradius,    /* Sun's apparent radius */
 219         t,          /* Diurnal arc */
 220         tsouth,     /* Time when Sun is at south */
 221         sidtime;    /* Local sidereal time */
 222         timelib_time *t_utc;
 223         timelib_sll   timestamp, old_sse;
 224 
 225         int rc = 0; /* Return cde from function - usually 0 */
 226 
 227         /* Normalize time */
 228         old_sse = t_loc->sse;
 229         t_loc->h = 12;
 230         t_loc->i = t_loc->s = 0;
 231         timelib_update_ts(t_loc, NULL);
 232 
 233         /* Calculate TS belonging to UTC 00:00 of the current day */
 234         t_utc = timelib_time_ctor();
 235         t_utc->y = t_loc->y;
 236         t_utc->m = t_loc->m;
 237         t_utc->d = t_loc->d;
 238         t_utc->h = t_utc->i = t_utc->s = 0;
 239         timelib_update_ts(t_utc, NULL);
 240 
 241         /* Compute d of 12h local mean solar time */
 242         timestamp = t_loc->sse;
 243         d = timelib_ts_to_juliandate(timestamp) - lon/360.0;
 244 
 245         /* Compute local sidereal time of this moment */
 246         sidtime = astro_revolution(astro_GMST0(d) + 180.0 + lon);
 247 
 248         /* Compute Sun's RA + Decl at this moment */
 249         astro_sun_RA_dec( d, &sRA, &sdec, &sr );
 250 
 251         /* Compute time when Sun is at south - in hours UT */
 252         tsouth = 12.0 - astro_rev180(sidtime - sRA) / 15.0;
 253 
 254         /* Compute the Sun's apparent radius, degrees */
 255         sradius = 0.2666 / sr;
 256 
 257         /* Do correction to upper limb, if necessary */
 258         if (upper_limb) {
 259                 altit -= sradius;
 260         }
 261 
 262         /* Compute the diurnal arc that the Sun traverses to reach */
 263         /* the specified altitude altit: */
 264         {
 265                 double cost;
 266                 cost = (sind(altit) - sind(lat) * sind(sdec)) / (cosd(lat) * cosd(sdec));
 267                 *ts_transit = t_utc->sse + (tsouth * 3600);
 268                 if (cost >= 1.0) {
 269                         rc = -1;
 270                         t = 0.0;       /* Sun always below altit */
 271 
 272                         *ts_rise = *ts_set = t_utc->sse + (tsouth * 3600);
 273                 } else if (cost <= -1.0) {
 274                         rc = +1;
 275                         t = 12.0;      /* Sun always above altit */
 276 
 277                         *ts_rise = t_loc->sse - (12 * 3600);
 278                         *ts_set  = t_loc->sse + (12 * 3600);
 279                 } else {
 280                         t = acosd(cost) / 15.0;   /* The diurnal arc, hours */
 281 
 282                         /* Store rise and set times - as Unix Timestamp */
 283                         *ts_rise = ((tsouth - t) * 3600) + t_utc->sse;
 284                         *ts_set  = ((tsouth + t) * 3600) + t_utc->sse;
 285 
 286                         *h_rise = (tsouth - t);
 287                         *h_set  = (tsouth + t);
 288                 }
 289         }
 290 
 291         /* Kill temporary time and restore original sse */
 292         timelib_time_dtor(t_utc);
 293         t_loc->sse = old_sse;
 294 
 295         return rc;
 296 }
 297 
 298 double timelib_ts_to_juliandate(timelib_sll ts)
 299 {
 300         double tmp;
 301 
 302         tmp = ts;
 303         tmp /= 86400;
 304         tmp += 2440587.5;
 305         tmp -= 2451543;
 306 
 307         return tmp;
 308 }

/* [<][>][^][v][top][bottom][index][help] */