/src/php-src/ext/date/lib/astro.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * The MIT License (MIT) |
3 | | * |
4 | | * Copyright (c) 2015-2019 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 "timelib.h" |
30 | | #include <stdio.h> |
31 | | #include <math.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 | 0 | # define PI 3.1415926535897932384 |
38 | | #endif |
39 | | |
40 | 0 | #define RADEG ( 180.0 / PI ) |
41 | 0 | #define DEGRAD ( PI / 180.0 ) |
42 | | |
43 | | /* The trigonometric functions in degrees */ |
44 | | |
45 | 0 | #define sind(x) sin((x)*DEGRAD) |
46 | 0 | #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 | 0 | #define acosd(x) (RADEG*acos(x)) |
52 | 0 | #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 | 0 | #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 | 0 | { |
76 | 0 | return (x - 360.0 * floor(x * INV360)); |
77 | 0 | } |
78 | | |
79 | | /*********************************************/ |
80 | | /* Reduce angle to within +180..+180 degrees */ |
81 | | /*********************************************/ |
82 | | static double astro_rev180( double x ) |
83 | 0 | { |
84 | 0 | return (x - 360.0 * floor(x * INV360 + 0.5)); |
85 | 0 | } |
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 | 0 | { |
115 | 0 | 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 | 0 | sidtim0 = astro_revolution((180.0 + 356.0470 + 282.9404) + (0.9856002585 + 4.70935E-5) * d); |
122 | 0 | return sidtim0; |
123 | 0 | } |
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 | 0 | { |
135 | 0 | double M, /* Mean anomaly of the Sun */ |
136 | 0 | w, /* Mean longitude of perihelion */ |
137 | | /* Note: Sun's mean longitude = M + w */ |
138 | 0 | e, /* Eccentricity of Earth's orbit */ |
139 | 0 | E, /* Eccentric anomaly */ |
140 | 0 | x, y, /* x, y coordinates in orbit */ |
141 | 0 | v; /* True anomaly */ |
142 | | |
143 | | /* Compute mean elements */ |
144 | 0 | M = astro_revolution(356.0470 + 0.9856002585 * d); |
145 | 0 | w = 282.9404 + 4.70935E-5 * d; |
146 | 0 | e = 0.016709 - 1.151E-9 * d; |
147 | | |
148 | | /* Compute true longitude and radius vector */ |
149 | 0 | E = M + e * RADEG * sind(M) * (1.0 + e * cosd(M)); |
150 | 0 | x = cosd(E) - e; |
151 | 0 | y = sqrt(1.0 - e*e) * sind(E); |
152 | 0 | *r = sqrt(x*x + y*y); /* Solar distance */ |
153 | 0 | v = atan2d(y, x); /* True anomaly */ |
154 | 0 | *lon = v + w; /* True solar longitude */ |
155 | 0 | if (*lon >= 360.0) { |
156 | 0 | *lon -= 360.0; /* Make it 0..360 degrees */ |
157 | 0 | } |
158 | 0 | } |
159 | | |
160 | | static void astro_sun_RA_dec(double d, double *RA, double *dec, double *r) |
161 | 0 | { |
162 | 0 | double lon, obl_ecl, x, y, z; |
163 | | |
164 | | /* Compute Sun's ecliptical coordinates */ |
165 | 0 | astro_sunpos(d, &lon, r); |
166 | | |
167 | | /* Compute ecliptic rectangular coordinates (z=0) */ |
168 | 0 | x = *r * cosd(lon); |
169 | 0 | y = *r * sind(lon); |
170 | | |
171 | | /* Compute obliquity of ecliptic (inclination of Earth's axis) */ |
172 | 0 | obl_ecl = 23.4393 - 3.563E-7 * d; |
173 | | |
174 | | /* Convert to equatorial rectangular coordinates - x is unchanged */ |
175 | 0 | z = y * sind(obl_ecl); |
176 | 0 | y = y * cosd(obl_ecl); |
177 | | |
178 | | /* Convert to spherical coordinates */ |
179 | 0 | *RA = atan2d(y, x); |
180 | 0 | *dec = atan2d(z, sqrt(x*x + y*y)); |
181 | 0 | } |
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 | 0 | { |
214 | 0 | double d, /* Days since 2000 Jan 0.0 (negative before) */ |
215 | 0 | sr, /* Solar distance, astronomical units */ |
216 | 0 | sRA, /* Sun's Right Ascension */ |
217 | 0 | sdec, /* Sun's declination */ |
218 | 0 | sradius, /* Sun's apparent radius */ |
219 | 0 | t, /* Diurnal arc */ |
220 | 0 | tsouth, /* Time when Sun is at south */ |
221 | 0 | sidtime; /* Local sidereal time */ |
222 | 0 | timelib_time *t_utc; |
223 | 0 | timelib_sll timestamp, old_sse; |
224 | |
|
225 | 0 | int rc = 0; /* Return cde from function - usually 0 */ |
226 | | |
227 | | /* Normalize time */ |
228 | 0 | old_sse = t_loc->sse; |
229 | 0 | t_loc->h = 12; |
230 | 0 | t_loc->i = t_loc->s = 0; |
231 | 0 | timelib_update_ts(t_loc, NULL); |
232 | | |
233 | | /* Calculate TS belonging to UTC 00:00 of the current day, for input into |
234 | | * the algorithm */ |
235 | 0 | t_utc = timelib_time_ctor(); |
236 | 0 | t_utc->y = t_loc->y; |
237 | 0 | t_utc->m = t_loc->m; |
238 | 0 | t_utc->d = t_loc->d; |
239 | 0 | t_utc->h = t_utc->i = t_utc->s = 0; |
240 | 0 | timelib_update_ts(t_utc, NULL); |
241 | | |
242 | | /* Compute d of 12h local mean solar time */ |
243 | 0 | timestamp = t_utc->sse; |
244 | 0 | d = timelib_ts_to_j2000(timestamp) + 2 - lon/360.0; |
245 | | |
246 | | /* Compute local sidereal time of this moment */ |
247 | 0 | sidtime = astro_revolution(astro_GMST0(d) + 180.0 + lon); |
248 | | |
249 | | /* Compute Sun's RA + Decl at this moment */ |
250 | 0 | astro_sun_RA_dec( d, &sRA, &sdec, &sr ); |
251 | | |
252 | | /* Compute time when Sun is at south - in hours UT */ |
253 | 0 | tsouth = 12.0 - astro_rev180(sidtime - sRA) / 15.0; |
254 | | |
255 | | /* Compute the Sun's apparent radius, degrees */ |
256 | 0 | sradius = 0.2666 / sr; |
257 | | |
258 | | /* Do correction to upper limb, if necessary */ |
259 | 0 | if (upper_limb) { |
260 | 0 | altit -= sradius; |
261 | 0 | } |
262 | | |
263 | | /* Compute the diurnal arc that the Sun traverses to reach */ |
264 | | /* the specified altitude altit: */ |
265 | 0 | { |
266 | 0 | double cost; |
267 | 0 | cost = (sind(altit) - sind(lat) * sind(sdec)) / (cosd(lat) * cosd(sdec)); |
268 | 0 | *ts_transit = t_utc->sse + (tsouth * 3600); |
269 | 0 | if (cost >= 1.0) { |
270 | 0 | rc = -1; |
271 | 0 | t = 0.0; /* Sun always below altit */ |
272 | |
|
273 | 0 | *ts_rise = *ts_set = t_utc->sse + (tsouth * 3600); |
274 | 0 | } else if (cost <= -1.0) { |
275 | 0 | rc = +1; |
276 | 0 | t = 12.0; /* Sun always above altit */ |
277 | |
|
278 | 0 | *ts_rise = t_loc->sse - (12 * 3600); |
279 | 0 | *ts_set = t_loc->sse + (12 * 3600); |
280 | 0 | } else { |
281 | 0 | t = acosd(cost) / 15.0; /* The diurnal arc, hours */ |
282 | | |
283 | | /* Store rise and set times - as Unix Timestamp */ |
284 | 0 | *ts_rise = ((tsouth - t) * 3600) + t_utc->sse; |
285 | 0 | *ts_set = ((tsouth + t) * 3600) + t_utc->sse; |
286 | |
|
287 | 0 | *h_rise = (tsouth - t); |
288 | 0 | *h_set = (tsouth + t); |
289 | 0 | } |
290 | 0 | } |
291 | | |
292 | | /* Kill temporary time and restore original sse */ |
293 | 0 | timelib_time_dtor(t_utc); |
294 | 0 | t_loc->sse = old_sse; |
295 | |
|
296 | 0 | return rc; |
297 | 0 | } |
298 | | |
299 | | double timelib_ts_to_julianday(timelib_sll ts) |
300 | 0 | { |
301 | 0 | double tmp; |
302 | |
|
303 | 0 | tmp = (double) ts; |
304 | 0 | tmp /= (double) 86400; |
305 | 0 | tmp += (double) 2440587.5; |
306 | |
|
307 | 0 | return tmp; |
308 | 0 | } |
309 | | |
310 | | double timelib_ts_to_j2000(timelib_sll ts) |
311 | 0 | { |
312 | 0 | return timelib_ts_to_julianday(ts) - 2451545; |
313 | 0 | } |