1*d19ef5a2SAaron LI /*-
2*d19ef5a2SAaron LI * SPDX-License-Identifier: BSD-3-Clause
3*d19ef5a2SAaron LI *
4*d19ef5a2SAaron LI * Copyright (c) 2019-2020 The DragonFly Project. All rights reserved.
5*d19ef5a2SAaron LI *
6*d19ef5a2SAaron LI * This code is derived from software contributed to The DragonFly Project
7*d19ef5a2SAaron LI * by Aaron LI <aly@aaronly.me>
8*d19ef5a2SAaron LI *
9*d19ef5a2SAaron LI * Redistribution and use in source and binary forms, with or without
10*d19ef5a2SAaron LI * modification, are permitted provided that the following conditions
11*d19ef5a2SAaron LI * are met:
12*d19ef5a2SAaron LI *
13*d19ef5a2SAaron LI * 1. Redistributions of source code must retain the above copyright
14*d19ef5a2SAaron LI * notice, this list of conditions and the following disclaimer.
15*d19ef5a2SAaron LI * 2. Redistributions in binary form must reproduce the above copyright
16*d19ef5a2SAaron LI * notice, this list of conditions and the following disclaimer in
17*d19ef5a2SAaron LI * the documentation and/or other materials provided with the
18*d19ef5a2SAaron LI * distribution.
19*d19ef5a2SAaron LI * 3. Neither the name of The DragonFly Project nor the names of its
20*d19ef5a2SAaron LI * contributors may be used to endorse or promote products derived
21*d19ef5a2SAaron LI * from this software without specific, prior written permission.
22*d19ef5a2SAaron LI *
23*d19ef5a2SAaron LI * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24*d19ef5a2SAaron LI * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25*d19ef5a2SAaron LI * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
26*d19ef5a2SAaron LI * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
27*d19ef5a2SAaron LI * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28*d19ef5a2SAaron LI * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
29*d19ef5a2SAaron LI * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30*d19ef5a2SAaron LI * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
31*d19ef5a2SAaron LI * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
32*d19ef5a2SAaron LI * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
33*d19ef5a2SAaron LI * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34*d19ef5a2SAaron LI * SUCH DAMAGE.
35*d19ef5a2SAaron LI *
36*d19ef5a2SAaron LI * Reference:
37*d19ef5a2SAaron LI * Calendrical Calculations, The Ultimate Edition (4th Edition)
38*d19ef5a2SAaron LI * by Edward M. Reingold and Nachum Dershowitz
39*d19ef5a2SAaron LI * 2018, Cambridge University Press
40*d19ef5a2SAaron LI */
41*d19ef5a2SAaron LI
42*d19ef5a2SAaron LI #include <math.h>
43*d19ef5a2SAaron LI #include <stdbool.h>
44*d19ef5a2SAaron LI #include <stddef.h>
45*d19ef5a2SAaron LI #include <stdio.h>
46*d19ef5a2SAaron LI #include <stdlib.h>
47*d19ef5a2SAaron LI
48*d19ef5a2SAaron LI #include "basics.h"
49*d19ef5a2SAaron LI #include "gregorian.h"
50*d19ef5a2SAaron LI #include "moon.h"
51*d19ef5a2SAaron LI #include "sun.h"
52*d19ef5a2SAaron LI #include "utils.h"
53*d19ef5a2SAaron LI
54*d19ef5a2SAaron LI
55*d19ef5a2SAaron LI /*
56*d19ef5a2SAaron LI * Time from new moon to new moon (i.e., a lunation).
57*d19ef5a2SAaron LI * Ref: Sec.(14.6), Eq.(14.44)
58*d19ef5a2SAaron LI */
59*d19ef5a2SAaron LI const double mean_synodic_month = 29.530588861;
60*d19ef5a2SAaron LI
61*d19ef5a2SAaron LI
62*d19ef5a2SAaron LI /*
63*d19ef5a2SAaron LI * Argument data 1 used by 'nth_new_moon()'.
64*d19ef5a2SAaron LI * Ref: Sec.(14.6), Table(14.3)
65*d19ef5a2SAaron LI */
66*d19ef5a2SAaron LI static const struct nth_new_moon_arg1 {
67*d19ef5a2SAaron LI double v;
68*d19ef5a2SAaron LI int w;
69*d19ef5a2SAaron LI int x;
70*d19ef5a2SAaron LI int y;
71*d19ef5a2SAaron LI int z;
72*d19ef5a2SAaron LI } nth_new_moon_data1[] = {
73*d19ef5a2SAaron LI { -0.40720, 0, 0, 1, 0 },
74*d19ef5a2SAaron LI { 0.17241, 1, 1, 0, 0 },
75*d19ef5a2SAaron LI { 0.01608, 0, 0, 2, 0 },
76*d19ef5a2SAaron LI { 0.01039, 0, 0, 0, 2 },
77*d19ef5a2SAaron LI { 0.00739, 1, -1, 1, 0 },
78*d19ef5a2SAaron LI { -0.00514, 1, 1, 1, 0 },
79*d19ef5a2SAaron LI { 0.00208, 2, 2, 0, 0 },
80*d19ef5a2SAaron LI { -0.00111, 0, 0, 1, -2 },
81*d19ef5a2SAaron LI { -0.00057, 0, 0, 1, 2 },
82*d19ef5a2SAaron LI { 0.00056, 1, 1, 2, 0 },
83*d19ef5a2SAaron LI { -0.00042, 0, 0, 3, 0 },
84*d19ef5a2SAaron LI { 0.00042, 1, 1, 0, 2 },
85*d19ef5a2SAaron LI { 0.00038, 1, 1, 0, -2 },
86*d19ef5a2SAaron LI { -0.00024, 1, -1, 2, 0 },
87*d19ef5a2SAaron LI { -0.00007, 0, 2, 1, 0 },
88*d19ef5a2SAaron LI { 0.00004, 0, 0, 2, -2 },
89*d19ef5a2SAaron LI { 0.00004, 0, 3, 0, 0 },
90*d19ef5a2SAaron LI { 0.00003, 0, 1, 1, -2 },
91*d19ef5a2SAaron LI { 0.00003, 0, 0, 2, 2 },
92*d19ef5a2SAaron LI { -0.00003, 0, 1, 1, 2 },
93*d19ef5a2SAaron LI { 0.00003, 0, -1, 1, 2 },
94*d19ef5a2SAaron LI { -0.00002, 0, -1, 1, -2 },
95*d19ef5a2SAaron LI { -0.00002, 0, 1, 3, 0 },
96*d19ef5a2SAaron LI { 0.00002, 0, 0, 4, 0 },
97*d19ef5a2SAaron LI };
98*d19ef5a2SAaron LI
99*d19ef5a2SAaron LI /*
100*d19ef5a2SAaron LI * Argument data 2 used by 'nth_new_moon()'.
101*d19ef5a2SAaron LI * Ref: Sec.(14.6), Table(14.4)
102*d19ef5a2SAaron LI */
103*d19ef5a2SAaron LI static const struct nth_new_moon_arg2 {
104*d19ef5a2SAaron LI double i;
105*d19ef5a2SAaron LI double j;
106*d19ef5a2SAaron LI double l;
107*d19ef5a2SAaron LI } nth_new_moon_data2[] = {
108*d19ef5a2SAaron LI { 251.88, 0.016321, 0.000165 },
109*d19ef5a2SAaron LI { 251.83, 26.651886, 0.000164 },
110*d19ef5a2SAaron LI { 349.42, 36.412478, 0.000126 },
111*d19ef5a2SAaron LI { 84.66, 18.206239, 0.000110 },
112*d19ef5a2SAaron LI { 141.74, 53.303771, 0.000062 },
113*d19ef5a2SAaron LI { 207.14, 2.453732, 0.000060 },
114*d19ef5a2SAaron LI { 154.84, 7.306860, 0.000056 },
115*d19ef5a2SAaron LI { 34.52, 27.261239, 0.000047 },
116*d19ef5a2SAaron LI { 207.19, 0.121824, 0.000042 },
117*d19ef5a2SAaron LI { 291.34, 1.844379, 0.000040 },
118*d19ef5a2SAaron LI { 161.72, 24.198154, 0.000037 },
119*d19ef5a2SAaron LI { 239.56, 25.513099, 0.000035 },
120*d19ef5a2SAaron LI { 331.55, 3.592518, 0.000023 },
121*d19ef5a2SAaron LI };
122*d19ef5a2SAaron LI
123*d19ef5a2SAaron LI /*
124*d19ef5a2SAaron LI * Calculate the moment of the $n-th new moon after the new moon of
125*d19ef5a2SAaron LI * January 11, 1 (Gregorian), which is the first new moon after RD 0.
126*d19ef5a2SAaron LI * This function is centered upon January, 2000.
127*d19ef5a2SAaron LI * Ref: Sec.(14.6), Eq.(14.45)
128*d19ef5a2SAaron LI */
129*d19ef5a2SAaron LI double
nth_new_moon(int n)130*d19ef5a2SAaron LI nth_new_moon(int n)
131*d19ef5a2SAaron LI {
132*d19ef5a2SAaron LI int n0 = 24724; /* Months from RD 0 until j2000 */
133*d19ef5a2SAaron LI int k = n - n0; /* Months since j2000 */
134*d19ef5a2SAaron LI double nm = 1236.85; /* Months per century */
135*d19ef5a2SAaron LI double c = k / nm; /* Julian centuries */
136*d19ef5a2SAaron LI double j2000 = 0.5 + gregorian_new_year(2000);
137*d19ef5a2SAaron LI
138*d19ef5a2SAaron LI double coef_ap[] = { 5.09766, mean_synodic_month * nm,
139*d19ef5a2SAaron LI 0.00015437, -0.000000150, 0.00000000073 };
140*d19ef5a2SAaron LI double approx = j2000 + poly(c, coef_ap, nitems(coef_ap));
141*d19ef5a2SAaron LI
142*d19ef5a2SAaron LI double extra = 0.000325 * sin_deg(299.77 + 132.8475848 * c -
143*d19ef5a2SAaron LI 0.009173 * c*c);
144*d19ef5a2SAaron LI
145*d19ef5a2SAaron LI double coef_sa[] = { 2.5534, 29.10535670 * nm,
146*d19ef5a2SAaron LI -0.0000014, -0.00000011 };
147*d19ef5a2SAaron LI double coef_la[] = { 201.5643, 385.81693528 * nm,
148*d19ef5a2SAaron LI 0.0107582, 0.00001238, -0.000000058 };
149*d19ef5a2SAaron LI double coef_ma[] = { 160.7108, 390.67050284 * nm,
150*d19ef5a2SAaron LI -0.0016118, -0.00000227, 0.000000011 };
151*d19ef5a2SAaron LI double coef_om[] = { 124.7746, -1.56375588 * nm,
152*d19ef5a2SAaron LI 0.0020672, 0.00000215 };
153*d19ef5a2SAaron LI double solar_anomaly = poly(c, coef_sa, nitems(coef_sa));
154*d19ef5a2SAaron LI double lunar_anomaly = poly(c, coef_la, nitems(coef_la));
155*d19ef5a2SAaron LI double moon_argument = poly(c, coef_ma, nitems(coef_ma));
156*d19ef5a2SAaron LI double omega = poly(c, coef_om, nitems(coef_om));
157*d19ef5a2SAaron LI double E = 1.0 - 0.002516 * c - 0.0000074 * c*c;
158*d19ef5a2SAaron LI
159*d19ef5a2SAaron LI double sum_c = 0.0;
160*d19ef5a2SAaron LI const struct nth_new_moon_arg1 *arg1;
161*d19ef5a2SAaron LI for (size_t i = 0; i < nitems(nth_new_moon_data1); i++) {
162*d19ef5a2SAaron LI arg1 = &nth_new_moon_data1[i];
163*d19ef5a2SAaron LI sum_c += arg1->v * pow(E, arg1->w) * sin_deg(
164*d19ef5a2SAaron LI arg1->x * solar_anomaly +
165*d19ef5a2SAaron LI arg1->y * lunar_anomaly +
166*d19ef5a2SAaron LI arg1->z * moon_argument);
167*d19ef5a2SAaron LI }
168*d19ef5a2SAaron LI double correction = -0.00017 * sin_deg(omega) + sum_c;
169*d19ef5a2SAaron LI
170*d19ef5a2SAaron LI double additional = 0.0;
171*d19ef5a2SAaron LI const struct nth_new_moon_arg2 *arg2;
172*d19ef5a2SAaron LI for (size_t i = 0; i < nitems(nth_new_moon_data2); i++) {
173*d19ef5a2SAaron LI arg2 = &nth_new_moon_data2[i];
174*d19ef5a2SAaron LI additional += arg2->l * sin_deg(arg2->i + arg2->j * k);
175*d19ef5a2SAaron LI }
176*d19ef5a2SAaron LI
177*d19ef5a2SAaron LI double dt = approx + correction + extra + additional;
178*d19ef5a2SAaron LI return universal_from_dynamical(dt);
179*d19ef5a2SAaron LI }
180*d19ef5a2SAaron LI
181*d19ef5a2SAaron LI /*
182*d19ef5a2SAaron LI * Mean longitude of moon at moment given in Julian centuries $c.
183*d19ef5a2SAaron LI * Ref: Sec.(14.6), Eq.(14.49)
184*d19ef5a2SAaron LI */
185*d19ef5a2SAaron LI static double
lunar_longitude_mean(double c)186*d19ef5a2SAaron LI lunar_longitude_mean(double c)
187*d19ef5a2SAaron LI {
188*d19ef5a2SAaron LI double coef[] = { 218.3164477, 481267.88123421, -0.0015786,
189*d19ef5a2SAaron LI 1.0 / 538841.0, -1.0 / 65194000.0 };
190*d19ef5a2SAaron LI return mod_f(poly(c, coef, nitems(coef)), 360);
191*d19ef5a2SAaron LI }
192*d19ef5a2SAaron LI
193*d19ef5a2SAaron LI /*
194*d19ef5a2SAaron LI * Elongation of moon (angular distance from Sun) at moment given in Julian
195*d19ef5a2SAaron LI * centuries $c.
196*d19ef5a2SAaron LI * Ref: Sec.(14.6), Eq.(14.50)
197*d19ef5a2SAaron LI */
198*d19ef5a2SAaron LI static double
lunar_elongation(double c)199*d19ef5a2SAaron LI lunar_elongation(double c)
200*d19ef5a2SAaron LI {
201*d19ef5a2SAaron LI double coef[] = { 297.8501921, 445267.1114034, -0.0018819,
202*d19ef5a2SAaron LI 1.0 / 545868.0, -1.0 / 113065000.0 };
203*d19ef5a2SAaron LI return mod_f(poly(c, coef, nitems(coef)), 360);
204*d19ef5a2SAaron LI }
205*d19ef5a2SAaron LI
206*d19ef5a2SAaron LI /*
207*d19ef5a2SAaron LI * Mean anomaly of Sun (angular distance from perihelion) at moment given in
208*d19ef5a2SAaron LI * Julian centuries $c.
209*d19ef5a2SAaron LI * Ref: Sec.(14.6), Eq.(14.51)
210*d19ef5a2SAaron LI */
211*d19ef5a2SAaron LI static double
solar_anomaly(double c)212*d19ef5a2SAaron LI solar_anomaly(double c)
213*d19ef5a2SAaron LI {
214*d19ef5a2SAaron LI double coef[] = { 357.5291092, 35999.0502909, -0.0001536,
215*d19ef5a2SAaron LI 1.0 / 24490000.0 };
216*d19ef5a2SAaron LI return mod_f(poly(c, coef, nitems(coef)), 360);
217*d19ef5a2SAaron LI }
218*d19ef5a2SAaron LI
219*d19ef5a2SAaron LI /*
220*d19ef5a2SAaron LI * Mean anomaly of moon (angular distance from perigee) at moment given in
221*d19ef5a2SAaron LI * Julian centuries $c.
222*d19ef5a2SAaron LI * Ref: Sec.(14.6), Eq.(14.52)
223*d19ef5a2SAaron LI */
224*d19ef5a2SAaron LI static double
lunar_anomaly(double c)225*d19ef5a2SAaron LI lunar_anomaly(double c)
226*d19ef5a2SAaron LI {
227*d19ef5a2SAaron LI double coef[] = { 134.9633964, 477198.8675055, 0.0087414,
228*d19ef5a2SAaron LI 1.0 / 69699.0, -1.0 / 14712000.0 };
229*d19ef5a2SAaron LI return mod_f(poly(c, coef, nitems(coef)), 360);
230*d19ef5a2SAaron LI }
231*d19ef5a2SAaron LI
232*d19ef5a2SAaron LI /*
233*d19ef5a2SAaron LI * Moon's argument of latitude (the distance from the moon's node) at moment
234*d19ef5a2SAaron LI * given in Julian centuries $c.
235*d19ef5a2SAaron LI * Ref: Sec.(14.6), Eq.(14.53)
236*d19ef5a2SAaron LI */
237*d19ef5a2SAaron LI static double
moon_node(double c)238*d19ef5a2SAaron LI moon_node(double c)
239*d19ef5a2SAaron LI {
240*d19ef5a2SAaron LI double coef[] = { 93.2720950, 483202.0175233, -0.0036539,
241*d19ef5a2SAaron LI -1.0 / 3526000.0, 1.0 / 863310000.0 };
242*d19ef5a2SAaron LI return mod_f(poly(c, coef, nitems(coef)), 360);
243*d19ef5a2SAaron LI }
244*d19ef5a2SAaron LI
245*d19ef5a2SAaron LI /*
246*d19ef5a2SAaron LI * Argument data used by 'lunar_longitude()'.
247*d19ef5a2SAaron LI * Ref: Sec.(14.6), Table(14.5)
248*d19ef5a2SAaron LI */
249*d19ef5a2SAaron LI static const struct lunar_longitude_arg {
250*d19ef5a2SAaron LI int v;
251*d19ef5a2SAaron LI int w;
252*d19ef5a2SAaron LI int x;
253*d19ef5a2SAaron LI int y;
254*d19ef5a2SAaron LI int z;
255*d19ef5a2SAaron LI } lunar_longitude_data[] = {
256*d19ef5a2SAaron LI { 6288774, 0, 0, 1, 0 },
257*d19ef5a2SAaron LI { 1274027, 2, 0, -1, 0 },
258*d19ef5a2SAaron LI { 658314, 2, 0, 0, 0 },
259*d19ef5a2SAaron LI { 213618, 0, 0, 2, 0 },
260*d19ef5a2SAaron LI { -185116, 0, 1, 0, 0 },
261*d19ef5a2SAaron LI { -114332, 0, 0, 0, 2 },
262*d19ef5a2SAaron LI { 58793, 2, 0, -2, 0 },
263*d19ef5a2SAaron LI { 57066, 2, -1, -1, 0 },
264*d19ef5a2SAaron LI { 53322, 2, 0, 1, 0 },
265*d19ef5a2SAaron LI { 45758, 2, -1, 0, 0 },
266*d19ef5a2SAaron LI { -40923, 0, 1, -1, 0 },
267*d19ef5a2SAaron LI { -34720, 1, 0, 0, 0 },
268*d19ef5a2SAaron LI { -30383, 0, 1, 1, 0 },
269*d19ef5a2SAaron LI { 15327, 2, 0, 0, -2 },
270*d19ef5a2SAaron LI { -12528, 0, 0, 1, 2 },
271*d19ef5a2SAaron LI { 10980, 0, 0, 1, -2 },
272*d19ef5a2SAaron LI { 10675, 4, 0, -1, 0 },
273*d19ef5a2SAaron LI { 10034, 0, 0, 3, 0 },
274*d19ef5a2SAaron LI { 8548, 4, 0, -2, 0 },
275*d19ef5a2SAaron LI { -7888, 2, 1, -1, 0 },
276*d19ef5a2SAaron LI { -6766, 2, 1, 0, 0 },
277*d19ef5a2SAaron LI { -5163, 1, 0, -1, 0 },
278*d19ef5a2SAaron LI { 4987, 1, 1, 0, 0 },
279*d19ef5a2SAaron LI { 4036, 2, -1, 1, 0 },
280*d19ef5a2SAaron LI { 3994, 2, 0, 2, 0 },
281*d19ef5a2SAaron LI { 3861, 4, 0, 0, 0 },
282*d19ef5a2SAaron LI { 3665, 2, 0, -3, 0 },
283*d19ef5a2SAaron LI { -2689, 0, 1, -2, 0 },
284*d19ef5a2SAaron LI { -2602, 2, 0, -1, 2 },
285*d19ef5a2SAaron LI { 2390, 2, -1, -2, 0 },
286*d19ef5a2SAaron LI { -2348, 1, 0, 1, 0 },
287*d19ef5a2SAaron LI { 2236, 2, -2, 0, 0 },
288*d19ef5a2SAaron LI { -2120, 0, 1, 2, 0 },
289*d19ef5a2SAaron LI { -2069, 0, 2, 0, 0 },
290*d19ef5a2SAaron LI { 2048, 2, -2, -1, 0 },
291*d19ef5a2SAaron LI { -1773, 2, 0, 1, -2 },
292*d19ef5a2SAaron LI { -1595, 2, 0, 0, 2 },
293*d19ef5a2SAaron LI { 1215, 4, -1, -1, 0 },
294*d19ef5a2SAaron LI { -1110, 0, 0, 2, 2 },
295*d19ef5a2SAaron LI { -892, 3, 0, -1, 0 },
296*d19ef5a2SAaron LI { -810, 2, 1, 1, 0 },
297*d19ef5a2SAaron LI { 759, 4, -1, -2, 0 },
298*d19ef5a2SAaron LI { -713, 0, 2, -1, 0 },
299*d19ef5a2SAaron LI { -700, 2, 2, -1, 0 },
300*d19ef5a2SAaron LI { 691, 2, 1, -2, 0 },
301*d19ef5a2SAaron LI { 596, 2, -1, 0, -2 },
302*d19ef5a2SAaron LI { 549, 4, 0, 1, 0 },
303*d19ef5a2SAaron LI { 537, 0, 0, 4, 0 },
304*d19ef5a2SAaron LI { 520, 4, -1, 0, 0 },
305*d19ef5a2SAaron LI { -487, 1, 0, -2, 0 },
306*d19ef5a2SAaron LI { -399, 2, 1, 0, -2 },
307*d19ef5a2SAaron LI { -381, 0, 0, 2, -2 },
308*d19ef5a2SAaron LI { 351, 1, 1, 1, 0 },
309*d19ef5a2SAaron LI { -340, 3, 0, -2, 0 },
310*d19ef5a2SAaron LI { 330, 4, 0, -3, 0 },
311*d19ef5a2SAaron LI { 327, 2, -1, 2, 0 },
312*d19ef5a2SAaron LI { -323, 0, 2, 1, 0 },
313*d19ef5a2SAaron LI { 299, 1, 1, -1, 0 },
314*d19ef5a2SAaron LI { 294, 2, 0, 3, 0 },
315*d19ef5a2SAaron LI };
316*d19ef5a2SAaron LI
317*d19ef5a2SAaron LI /*
318*d19ef5a2SAaron LI * Calculate the geocentric longitude of moon (in degrees) at moment $t.
319*d19ef5a2SAaron LI * Ref: Sec.(14.6), Eq.(14.48)
320*d19ef5a2SAaron LI */
321*d19ef5a2SAaron LI double
lunar_longitude(double t)322*d19ef5a2SAaron LI lunar_longitude(double t)
323*d19ef5a2SAaron LI {
324*d19ef5a2SAaron LI double c = julian_centuries(t);
325*d19ef5a2SAaron LI double nu = nutation(t);
326*d19ef5a2SAaron LI
327*d19ef5a2SAaron LI double L_prime = lunar_longitude_mean(c);
328*d19ef5a2SAaron LI double D = lunar_elongation(c);
329*d19ef5a2SAaron LI double M = solar_anomaly(c);
330*d19ef5a2SAaron LI double M_prime = lunar_anomaly(c);
331*d19ef5a2SAaron LI double F = moon_node(c);
332*d19ef5a2SAaron LI double E = 1.0 - 0.002516 * c - 0.0000074 * c*c;
333*d19ef5a2SAaron LI
334*d19ef5a2SAaron LI double sum = 0.0;
335*d19ef5a2SAaron LI const struct lunar_longitude_arg *arg;
336*d19ef5a2SAaron LI for (size_t i = 0; i < nitems(lunar_longitude_data); i++) {
337*d19ef5a2SAaron LI arg = &lunar_longitude_data[i];
338*d19ef5a2SAaron LI sum += arg->v * pow(E, abs(arg->x)) * sin_deg(
339*d19ef5a2SAaron LI arg->w * D +
340*d19ef5a2SAaron LI arg->x * M +
341*d19ef5a2SAaron LI arg->y * M_prime +
342*d19ef5a2SAaron LI arg->z * F);
343*d19ef5a2SAaron LI }
344*d19ef5a2SAaron LI double correction = sum / 1e6;
345*d19ef5a2SAaron LI
346*d19ef5a2SAaron LI double venus = sin_deg(119.75 + 131.849 * c) * 3958 / 1e6;
347*d19ef5a2SAaron LI double jupiter = sin_deg(53.09 + 479264.29 * c) * 318 / 1e6;
348*d19ef5a2SAaron LI double flat_earth = sin_deg(L_prime - F) * 1962 / 1e6;
349*d19ef5a2SAaron LI
350*d19ef5a2SAaron LI return mod_f((L_prime + correction + venus + jupiter +
351*d19ef5a2SAaron LI flat_earth + nu), 360);
352*d19ef5a2SAaron LI }
353*d19ef5a2SAaron LI
354*d19ef5a2SAaron LI /*
355*d19ef5a2SAaron LI * Argument data used by 'lunar_latitude()'.
356*d19ef5a2SAaron LI * Ref: Sec.(14.6), Table(14.6)
357*d19ef5a2SAaron LI */
358*d19ef5a2SAaron LI static const struct lunar_latitude_arg {
359*d19ef5a2SAaron LI int v;
360*d19ef5a2SAaron LI int w;
361*d19ef5a2SAaron LI int x;
362*d19ef5a2SAaron LI int y;
363*d19ef5a2SAaron LI int z;
364*d19ef5a2SAaron LI } lunar_latitude_data[] = {
365*d19ef5a2SAaron LI { 5128122, 0, 0, 0, 1 },
366*d19ef5a2SAaron LI { 280602, 0, 0, 1, 1 },
367*d19ef5a2SAaron LI { 277693, 0, 0, 1, -1 },
368*d19ef5a2SAaron LI { 173237, 2, 0, 0, -1 },
369*d19ef5a2SAaron LI { 55413, 2, 0, -1, 1 },
370*d19ef5a2SAaron LI { 46271, 2, 0, -1, -1 },
371*d19ef5a2SAaron LI { 32573, 2, 0, 0, 1 },
372*d19ef5a2SAaron LI { 17198, 0, 0, 2, 1 },
373*d19ef5a2SAaron LI { 9266, 2, 0, 1, -1 },
374*d19ef5a2SAaron LI { 8822, 0, 0, 2, -1 },
375*d19ef5a2SAaron LI { 8216, 2, -1, 0, -1 },
376*d19ef5a2SAaron LI { 4324, 2, 0, -2, -1 },
377*d19ef5a2SAaron LI { 4200, 2, 0, 1, 1 },
378*d19ef5a2SAaron LI { -3359, 2, 1, 0, -1 },
379*d19ef5a2SAaron LI { 2463, 2, -1, -1, 1 },
380*d19ef5a2SAaron LI { 2211, 2, -1, 0, 1 },
381*d19ef5a2SAaron LI { 2065, 2, -1, -1, -1 },
382*d19ef5a2SAaron LI { -1870, 0, 1, -1, -1 },
383*d19ef5a2SAaron LI { 1828, 4, 0, -1, -1 },
384*d19ef5a2SAaron LI { -1794, 0, 1, 0, 1 },
385*d19ef5a2SAaron LI { -1749, 0, 0, 0, 3 },
386*d19ef5a2SAaron LI { -1565, 0, 1, -1, 1 },
387*d19ef5a2SAaron LI { -1491, 1, 0, 0, 1 },
388*d19ef5a2SAaron LI { -1475, 0, 1, 1, 1 },
389*d19ef5a2SAaron LI { -1410, 0, 1, 1, -1 },
390*d19ef5a2SAaron LI { -1344, 0, 1, 0, -1 },
391*d19ef5a2SAaron LI { -1335, 1, 0, 0, -1 },
392*d19ef5a2SAaron LI { 1107, 0, 0, 3, 1 },
393*d19ef5a2SAaron LI { 1021, 4, 0, 0, -1 },
394*d19ef5a2SAaron LI { 833, 4, 0, -1, 1 },
395*d19ef5a2SAaron LI { 777, 0, 0, 1, -3 },
396*d19ef5a2SAaron LI { 671, 4, 0, -2, 1 },
397*d19ef5a2SAaron LI { 607, 2, 0, 0, -3 },
398*d19ef5a2SAaron LI { 596, 2, 0, 2, -1 },
399*d19ef5a2SAaron LI { 491, 2, -1, 1, -1 },
400*d19ef5a2SAaron LI { -451, 2, 0, -2, 1 },
401*d19ef5a2SAaron LI { 439, 0, 0, 3, -1 },
402*d19ef5a2SAaron LI { 422, 2, 0, 2, 1 },
403*d19ef5a2SAaron LI { 421, 2, 0, -3, -1 },
404*d19ef5a2SAaron LI { -366, 2, 1, -1, 1 },
405*d19ef5a2SAaron LI { -351, 2, 1, 0, 1 },
406*d19ef5a2SAaron LI { 331, 4, 0, 0, 1 },
407*d19ef5a2SAaron LI { 315, 2, -1, 1, 1 },
408*d19ef5a2SAaron LI { 302, 2, -2, 0, -1 },
409*d19ef5a2SAaron LI { -283, 0, 0, 1, 3 },
410*d19ef5a2SAaron LI { -229, 2, 1, 1, -1 },
411*d19ef5a2SAaron LI { 223, 1, 1, 0, -1 },
412*d19ef5a2SAaron LI { 223, 1, 1, 0, 1 },
413*d19ef5a2SAaron LI { -220, 0, 1, -2, -1 },
414*d19ef5a2SAaron LI { -220, 2, 1, -1, -1 },
415*d19ef5a2SAaron LI { -185, 1, 0, 1, 1 },
416*d19ef5a2SAaron LI { 181, 2, -1, -2, -1 },
417*d19ef5a2SAaron LI { -177, 0, 1, 2, 1 },
418*d19ef5a2SAaron LI { 176, 4, 0, -2, -1 },
419*d19ef5a2SAaron LI { 166, 4, -1, -1, -1 },
420*d19ef5a2SAaron LI { -164, 1, 0, 1, -1 },
421*d19ef5a2SAaron LI { 132, 4, 0, 1, -1 },
422*d19ef5a2SAaron LI { -119, 1, 0, -1, -1 },
423*d19ef5a2SAaron LI { 115, 4, -1, 0, -1 },
424*d19ef5a2SAaron LI { 107, 2, -2, 0, 1 },
425*d19ef5a2SAaron LI };
426*d19ef5a2SAaron LI
427*d19ef5a2SAaron LI /*
428*d19ef5a2SAaron LI * Calculate the geocentric latitude of moon (in degrees) at moment $t.
429*d19ef5a2SAaron LI * Lunar latitude ranges from about -6 to 6 degress.
430*d19ef5a2SAaron LI * Ref: Sec.(14.6), Eq.(14.63)
431*d19ef5a2SAaron LI */
432*d19ef5a2SAaron LI double
lunar_latitude(double t)433*d19ef5a2SAaron LI lunar_latitude(double t)
434*d19ef5a2SAaron LI {
435*d19ef5a2SAaron LI double c = julian_centuries(t);
436*d19ef5a2SAaron LI
437*d19ef5a2SAaron LI double L_prime = lunar_longitude_mean(c);
438*d19ef5a2SAaron LI double D = lunar_elongation(c);
439*d19ef5a2SAaron LI double M = solar_anomaly(c);
440*d19ef5a2SAaron LI double M_prime = lunar_anomaly(c);
441*d19ef5a2SAaron LI double F = moon_node(c);
442*d19ef5a2SAaron LI double E = 1.0 - 0.002516 * c - 0.0000074 * c*c;
443*d19ef5a2SAaron LI
444*d19ef5a2SAaron LI double sum = 0.0;
445*d19ef5a2SAaron LI const struct lunar_latitude_arg *arg;
446*d19ef5a2SAaron LI for (size_t i = 0; i < nitems(lunar_latitude_data); i++) {
447*d19ef5a2SAaron LI arg = &lunar_latitude_data[i];
448*d19ef5a2SAaron LI sum += arg->v * pow(E, abs(arg->x)) * sin_deg(
449*d19ef5a2SAaron LI arg->w * D +
450*d19ef5a2SAaron LI arg->x * M +
451*d19ef5a2SAaron LI arg->y * M_prime +
452*d19ef5a2SAaron LI arg->z * F);
453*d19ef5a2SAaron LI }
454*d19ef5a2SAaron LI double beta = sum / 1e6;
455*d19ef5a2SAaron LI
456*d19ef5a2SAaron LI double venus = (sin_deg(119.75 + 131.849 * c + F) +
457*d19ef5a2SAaron LI sin_deg(119.75 + 131.849 * c - F)) * 175 / 1e6;
458*d19ef5a2SAaron LI double flat_earth = (-2235 * sin_deg(L_prime) +
459*d19ef5a2SAaron LI 127 * sin_deg(L_prime - M_prime) -
460*d19ef5a2SAaron LI 115 * sin_deg(L_prime + M_prime)) / 1e6;
461*d19ef5a2SAaron LI double extra = sin_deg(313.45 + 481266.484 * c) * 382 / 1e6;
462*d19ef5a2SAaron LI
463*d19ef5a2SAaron LI return (beta + venus + flat_earth + extra);
464*d19ef5a2SAaron LI }
465*d19ef5a2SAaron LI
466*d19ef5a2SAaron LI /*
467*d19ef5a2SAaron LI * Argument data used by 'lunar_distance()'.
468*d19ef5a2SAaron LI * Ref: Sec.(14.6), Table(14.7)
469*d19ef5a2SAaron LI */
470*d19ef5a2SAaron LI static const struct lunar_distance_arg {
471*d19ef5a2SAaron LI int v;
472*d19ef5a2SAaron LI int w;
473*d19ef5a2SAaron LI int x;
474*d19ef5a2SAaron LI int y;
475*d19ef5a2SAaron LI int z;
476*d19ef5a2SAaron LI } lunar_distance_data[] = {
477*d19ef5a2SAaron LI { -20905355, 0, 0, 1, 0 },
478*d19ef5a2SAaron LI { -3699111, 2, 0, -1, 0 },
479*d19ef5a2SAaron LI { -2955968, 2, 0, 0, 0 },
480*d19ef5a2SAaron LI { -569925, 0, 0, 2, 0 },
481*d19ef5a2SAaron LI { 48888, 0, 1, 0, 0 },
482*d19ef5a2SAaron LI { -3149, 0, 0, 0, 2 },
483*d19ef5a2SAaron LI { 246158, 2, 0, -2, 0 },
484*d19ef5a2SAaron LI { -152138, 2, -1, -1, 0 },
485*d19ef5a2SAaron LI { -170733, 2, 0, 1, 0 },
486*d19ef5a2SAaron LI { -204586, 2, -1, 0, 0 },
487*d19ef5a2SAaron LI { -129620, 0, 1, -1, 0 },
488*d19ef5a2SAaron LI { 108743, 1, 0, 0, 0 },
489*d19ef5a2SAaron LI { 104755, 0, 1, 1, 0 },
490*d19ef5a2SAaron LI { 10321, 2, 0, 0, -2 },
491*d19ef5a2SAaron LI { 0, 0, 0, 1, 2 },
492*d19ef5a2SAaron LI { 79661, 0, 0, 1, -2 },
493*d19ef5a2SAaron LI { -34782, 4, 0, -1, 0 },
494*d19ef5a2SAaron LI { -23210, 0, 0, 3, 0 },
495*d19ef5a2SAaron LI { -21636, 4, 0, -2, 0 },
496*d19ef5a2SAaron LI { 24208, 2, 1, -1, 0 },
497*d19ef5a2SAaron LI { 30824, 2, 1, 0, 0 },
498*d19ef5a2SAaron LI { -8379, 1, 0, -1, 0 },
499*d19ef5a2SAaron LI { -16675, 1, 1, 0, 0 },
500*d19ef5a2SAaron LI { -12831, 2, -1, 1, 0 },
501*d19ef5a2SAaron LI { -10445, 2, 0, 2, 0 },
502*d19ef5a2SAaron LI { -11650, 4, 0, 0, 0 },
503*d19ef5a2SAaron LI { 14403, 2, 0, -3, 0 },
504*d19ef5a2SAaron LI { -7003, 0, 1, -2, 0 },
505*d19ef5a2SAaron LI { 0, 2, 0, -1, 2 },
506*d19ef5a2SAaron LI { 10056, 2, -1, -2, 0 },
507*d19ef5a2SAaron LI { 6322, 1, 0, 1, 0 },
508*d19ef5a2SAaron LI { -9884, 2, -2, 0, 0 },
509*d19ef5a2SAaron LI { 5751, 0, 1, 2, 0 },
510*d19ef5a2SAaron LI { 0, 0, 2, 0, 0 },
511*d19ef5a2SAaron LI { -4950, 2, -2, -1, 0 },
512*d19ef5a2SAaron LI { 4130, 2, 0, 1, -2 },
513*d19ef5a2SAaron LI { 0, 2, 0, 0, 2 },
514*d19ef5a2SAaron LI { -3958, 4, -1, -1, 0 },
515*d19ef5a2SAaron LI { 0, 0, 0, 2, 2 },
516*d19ef5a2SAaron LI { 3258, 3, 0, -1, 0 },
517*d19ef5a2SAaron LI { 2616, 2, 1, 1, 0 },
518*d19ef5a2SAaron LI { -1897, 4, -1, -2, 0 },
519*d19ef5a2SAaron LI { -2117, 0, 2, -1, 0 },
520*d19ef5a2SAaron LI { 2354, 2, 2, -1, 0 },
521*d19ef5a2SAaron LI { 0, 2, 1, -2, 0 },
522*d19ef5a2SAaron LI { 0, 2, -1, 0, -2 },
523*d19ef5a2SAaron LI { -1423, 4, 0, 1, 0 },
524*d19ef5a2SAaron LI { -1117, 0, 0, 4, 0 },
525*d19ef5a2SAaron LI { -1571, 4, -1, 0, 0 },
526*d19ef5a2SAaron LI { -1739, 1, 0, -2, 0 },
527*d19ef5a2SAaron LI { 0, 2, 1, 0, -2 },
528*d19ef5a2SAaron LI { -4421, 0, 0, 2, -2 },
529*d19ef5a2SAaron LI { 0, 1, 1, 1, 0 },
530*d19ef5a2SAaron LI { 0, 3, 0, -2, 0 },
531*d19ef5a2SAaron LI { 0, 4, 0, -3, 0 },
532*d19ef5a2SAaron LI { 0, 2, -1, 2, 0 },
533*d19ef5a2SAaron LI { 1165, 0, 2, 1, 0 },
534*d19ef5a2SAaron LI { 0, 1, 1, -1, 0 },
535*d19ef5a2SAaron LI { 0, 2, 0, 3, 0 },
536*d19ef5a2SAaron LI { 8752, 2, 0, -1, -2 },
537*d19ef5a2SAaron LI };
538*d19ef5a2SAaron LI
539*d19ef5a2SAaron LI /*
540*d19ef5a2SAaron LI * Calculate the distance to moon (in meters) at moment $t.
541*d19ef5a2SAaron LI * Ref: Sec.(14.6), Eq.(14.65)
542*d19ef5a2SAaron LI */
543*d19ef5a2SAaron LI double
lunar_distance(double t)544*d19ef5a2SAaron LI lunar_distance(double t)
545*d19ef5a2SAaron LI {
546*d19ef5a2SAaron LI double c = julian_centuries(t);
547*d19ef5a2SAaron LI
548*d19ef5a2SAaron LI double D = lunar_elongation(c);
549*d19ef5a2SAaron LI double M = solar_anomaly(c);
550*d19ef5a2SAaron LI double M_prime = lunar_anomaly(c);
551*d19ef5a2SAaron LI double F = moon_node(c);
552*d19ef5a2SAaron LI double E = 1.0 - 0.002516 * c - 0.0000074 * c*c;
553*d19ef5a2SAaron LI
554*d19ef5a2SAaron LI double correction = 0.0;
555*d19ef5a2SAaron LI const struct lunar_distance_arg *arg;
556*d19ef5a2SAaron LI for (size_t i = 0; i < nitems(lunar_distance_data); i++) {
557*d19ef5a2SAaron LI arg = &lunar_distance_data[i];
558*d19ef5a2SAaron LI correction += arg->v * pow(E, abs(arg->x)) * cos_deg(
559*d19ef5a2SAaron LI arg->w * D +
560*d19ef5a2SAaron LI arg->x * M +
561*d19ef5a2SAaron LI arg->y * M_prime +
562*d19ef5a2SAaron LI arg->z * F);
563*d19ef5a2SAaron LI }
564*d19ef5a2SAaron LI
565*d19ef5a2SAaron LI return 385000560.0 + correction;
566*d19ef5a2SAaron LI }
567*d19ef5a2SAaron LI
568*d19ef5a2SAaron LI /*
569*d19ef5a2SAaron LI * Calculate the altitude of moon (in degrees) above the horizon at
570*d19ef5a2SAaron LI * location ($latitude, $longitude) and moment $t, ignoring parallax
571*d19ef5a2SAaron LI * and refraction.
572*d19ef5a2SAaron LI * NOTE: This calculates the geocentric altitude viewed from the center
573*d19ef5a2SAaron LI * of the Earth.
574*d19ef5a2SAaron LI * Ref: Sec.(14.6), Eq.(14.64)
575*d19ef5a2SAaron LI */
576*d19ef5a2SAaron LI double
lunar_altitude(double t,double latitude,double longitude)577*d19ef5a2SAaron LI lunar_altitude(double t, double latitude, double longitude)
578*d19ef5a2SAaron LI {
579*d19ef5a2SAaron LI double lambda = lunar_longitude(t);
580*d19ef5a2SAaron LI double beta = lunar_latitude(t);
581*d19ef5a2SAaron LI double alpha = right_ascension(t, beta, lambda);
582*d19ef5a2SAaron LI double delta = declination(t, beta, lambda);
583*d19ef5a2SAaron LI double theta = sidereal_from_moment(t);
584*d19ef5a2SAaron LI double H = mod_f(theta + longitude - alpha, 360);
585*d19ef5a2SAaron LI
586*d19ef5a2SAaron LI double v = (sin_deg(latitude) * sin_deg(delta) +
587*d19ef5a2SAaron LI cos_deg(latitude) * cos_deg(delta) * cos_deg(H));
588*d19ef5a2SAaron LI return mod3_f(arcsin_deg(v), -180, 180);
589*d19ef5a2SAaron LI }
590*d19ef5a2SAaron LI
591*d19ef5a2SAaron LI /*
592*d19ef5a2SAaron LI * Parallax of moon at moment $t and location ($latitude, $longitude).
593*d19ef5a2SAaron LI * Ref: Sec.(14.6), Eq.(14.66)
594*d19ef5a2SAaron LI */
595*d19ef5a2SAaron LI static double
lunar_parallax(double t,double latitude,double longitude)596*d19ef5a2SAaron LI lunar_parallax(double t, double latitude, double longitude)
597*d19ef5a2SAaron LI {
598*d19ef5a2SAaron LI double geo = lunar_altitude(t, latitude, longitude);
599*d19ef5a2SAaron LI double distance = lunar_distance(t);
600*d19ef5a2SAaron LI /* Equatorial horizontal parallax of the moon */
601*d19ef5a2SAaron LI double sin_pi = 6378140.0 / distance;
602*d19ef5a2SAaron LI return arcsin_deg(sin_pi * cos_deg(geo));
603*d19ef5a2SAaron LI }
604*d19ef5a2SAaron LI
605*d19ef5a2SAaron LI /*
606*d19ef5a2SAaron LI * Calculate the topocentric altitude of moon viewed from the Earth surface
607*d19ef5a2SAaron LI * at location ($latitude, $longitude) and at moment $t.
608*d19ef5a2SAaron LI * Ref: Sec.(14.6), Eq.(14.67)
609*d19ef5a2SAaron LI */
610*d19ef5a2SAaron LI static double
lunar_altitude_topocentric(double t,double latitude,double longitude)611*d19ef5a2SAaron LI lunar_altitude_topocentric(double t, double latitude, double longitude)
612*d19ef5a2SAaron LI {
613*d19ef5a2SAaron LI return (lunar_altitude(t, latitude, longitude) -
614*d19ef5a2SAaron LI lunar_parallax(t, latitude, longitude));
615*d19ef5a2SAaron LI }
616*d19ef5a2SAaron LI
617*d19ef5a2SAaron LI /*
618*d19ef5a2SAaron LI * Calculate the observed altitude of the upper limb of moon at location
619*d19ef5a2SAaron LI * ($latitude, $longitude) and moment $t.
620*d19ef5a2SAaron LI * Ref: Sec.(14.7), Eq.(14.82)
621*d19ef5a2SAaron LI */
622*d19ef5a2SAaron LI double
lunar_altitude_observed(double t,const struct location * loc)623*d19ef5a2SAaron LI lunar_altitude_observed(double t, const struct location *loc)
624*d19ef5a2SAaron LI {
625*d19ef5a2SAaron LI double moon_radius = 16.0 / 60.0; /* 16 arcminutes */
626*d19ef5a2SAaron LI return (lunar_altitude_topocentric(t, loc->latitude, loc->longitude) +
627*d19ef5a2SAaron LI refraction(loc->elevation) + moon_radius);
628*d19ef5a2SAaron LI }
629*d19ef5a2SAaron LI
630*d19ef5a2SAaron LI /*
631*d19ef5a2SAaron LI * Calculate the phase of the moon, defined as the difference in
632*d19ef5a2SAaron LI * longitudes of the Sun and moon, at the given moment $t.
633*d19ef5a2SAaron LI * Ref: Sec.(14.6), Eq.(14.56)
634*d19ef5a2SAaron LI */
635*d19ef5a2SAaron LI double
lunar_phase(double t)636*d19ef5a2SAaron LI lunar_phase(double t)
637*d19ef5a2SAaron LI {
638*d19ef5a2SAaron LI double phi = mod_f(lunar_longitude(t) - solar_longitude(t), 360);
639*d19ef5a2SAaron LI
640*d19ef5a2SAaron LI /*
641*d19ef5a2SAaron LI * To check whether the above result conflicts with the time of
642*d19ef5a2SAaron LI * new moon as calculated by the more precise 'nth_new_moon()'
643*d19ef5a2SAaron LI */
644*d19ef5a2SAaron LI double t0 = nth_new_moon(0);
645*d19ef5a2SAaron LI int n = (int)lround((t - t0) / mean_synodic_month);
646*d19ef5a2SAaron LI double tn = nth_new_moon(n);
647*d19ef5a2SAaron LI double phi2 = mod_f((t - tn) / mean_synodic_month, 1) * 360;
648*d19ef5a2SAaron LI
649*d19ef5a2SAaron LI /* prefer the approximation based on the 'nth_new_moon()' moment */
650*d19ef5a2SAaron LI return (fabs(phi - phi2) > 180) ? phi2 : phi;
651*d19ef5a2SAaron LI }
652*d19ef5a2SAaron LI
653*d19ef5a2SAaron LI /*
654*d19ef5a2SAaron LI * Calculate the moment of the next time at or after the given moment $t
655*d19ef5a2SAaron LI * when the phase of the moon is $phi degree.
656*d19ef5a2SAaron LI * Ref: Sec.(14.6), Eq.(14.58)
657*d19ef5a2SAaron LI */
658*d19ef5a2SAaron LI double
lunar_phase_atafter(double phi,double t)659*d19ef5a2SAaron LI lunar_phase_atafter(double phi, double t)
660*d19ef5a2SAaron LI {
661*d19ef5a2SAaron LI double rate = mean_synodic_month / 360.0;
662*d19ef5a2SAaron LI double phase = lunar_phase(t);
663*d19ef5a2SAaron LI double tau = t + rate * mod_f(phi - phase, 360);
664*d19ef5a2SAaron LI
665*d19ef5a2SAaron LI /* estimate range (within 2 days) */
666*d19ef5a2SAaron LI double a = (t > tau - 2) ? t : tau - 2;
667*d19ef5a2SAaron LI double b = tau + 2;
668*d19ef5a2SAaron LI
669*d19ef5a2SAaron LI return invert_angular(lunar_phase, phi, a, b);
670*d19ef5a2SAaron LI }
671*d19ef5a2SAaron LI
672*d19ef5a2SAaron LI /*
673*d19ef5a2SAaron LI * Calculate the moment of the new moon before the given moment $t.
674*d19ef5a2SAaron LI * Ref: Sec.(14.6), Eq.(14.46)
675*d19ef5a2SAaron LI */
676*d19ef5a2SAaron LI double
new_moon_before(double t)677*d19ef5a2SAaron LI new_moon_before(double t)
678*d19ef5a2SAaron LI {
679*d19ef5a2SAaron LI double t0 = nth_new_moon(0);
680*d19ef5a2SAaron LI double phi = lunar_phase(t);
681*d19ef5a2SAaron LI int n = (int)lround((t - t0) / mean_synodic_month - phi / 360.0);
682*d19ef5a2SAaron LI
683*d19ef5a2SAaron LI int k = n - 1;
684*d19ef5a2SAaron LI double t1 = nth_new_moon(k);
685*d19ef5a2SAaron LI while (t1 < t) {
686*d19ef5a2SAaron LI k++;
687*d19ef5a2SAaron LI t1 = nth_new_moon(k);
688*d19ef5a2SAaron LI }
689*d19ef5a2SAaron LI
690*d19ef5a2SAaron LI return nth_new_moon(k-1);
691*d19ef5a2SAaron LI }
692*d19ef5a2SAaron LI
693*d19ef5a2SAaron LI /*
694*d19ef5a2SAaron LI * Calculate the moment of the new moon at or after the given moment $t.
695*d19ef5a2SAaron LI * Ref: Sec.(14.6), Eq.(14.47)
696*d19ef5a2SAaron LI */
697*d19ef5a2SAaron LI double
new_moon_atafter(double t)698*d19ef5a2SAaron LI new_moon_atafter(double t)
699*d19ef5a2SAaron LI {
700*d19ef5a2SAaron LI double t0 = nth_new_moon(0);
701*d19ef5a2SAaron LI double phi = lunar_phase(t);
702*d19ef5a2SAaron LI int n = (int)lround((t - t0) / mean_synodic_month - phi / 360.0);
703*d19ef5a2SAaron LI
704*d19ef5a2SAaron LI double t1 = nth_new_moon(n);
705*d19ef5a2SAaron LI while (t1 < t) {
706*d19ef5a2SAaron LI n++;
707*d19ef5a2SAaron LI t1 = nth_new_moon(n);
708*d19ef5a2SAaron LI }
709*d19ef5a2SAaron LI
710*d19ef5a2SAaron LI return t1;
711*d19ef5a2SAaron LI }
712*d19ef5a2SAaron LI
713*d19ef5a2SAaron LI /*
714*d19ef5a2SAaron LI * Calculate the moment of moonrise in standard time on fixed date $rd
715*d19ef5a2SAaron LI * at location $loc.
716*d19ef5a2SAaron LI * NOTE: Return an NaN if no moonrise.
717*d19ef5a2SAaron LI * Ref: Sec.(14.7), Eq.(14.83)
718*d19ef5a2SAaron LI */
719*d19ef5a2SAaron LI double
moonrise(int rd,const struct location * loc)720*d19ef5a2SAaron LI moonrise(int rd, const struct location *loc)
721*d19ef5a2SAaron LI {
722*d19ef5a2SAaron LI double t = (double)rd - loc->zone; /* universal time */
723*d19ef5a2SAaron LI bool waning = lunar_phase(t) > 180.0;
724*d19ef5a2SAaron LI /* lunar altitude at midnight */
725*d19ef5a2SAaron LI double alt = lunar_altitude_observed(t, loc);
726*d19ef5a2SAaron LI double offset = alt / (4.0 * (90.0 - fabs(loc->latitude)));
727*d19ef5a2SAaron LI
728*d19ef5a2SAaron LI /* approximate rising time */
729*d19ef5a2SAaron LI double t_approx = t;
730*d19ef5a2SAaron LI if (waning) {
731*d19ef5a2SAaron LI t_approx -= offset;
732*d19ef5a2SAaron LI if (offset > 0)
733*d19ef5a2SAaron LI t_approx += 1;
734*d19ef5a2SAaron LI } else {
735*d19ef5a2SAaron LI t_approx += 0.5 + offset;
736*d19ef5a2SAaron LI }
737*d19ef5a2SAaron LI
738*d19ef5a2SAaron LI /* binary search to determine the rising time */
739*d19ef5a2SAaron LI const double eps = 30.0 / 3600 / 24; /* accuracy of 30 seconds */
740*d19ef5a2SAaron LI double a = t_approx - 6.0/24.0;
741*d19ef5a2SAaron LI double b = t_approx + 6.0/24.0;
742*d19ef5a2SAaron LI double t_rise;
743*d19ef5a2SAaron LI do {
744*d19ef5a2SAaron LI t_rise = (a + b) / 2.0;
745*d19ef5a2SAaron LI if (lunar_altitude_observed(t_rise, loc) > 0)
746*d19ef5a2SAaron LI b = t_rise;
747*d19ef5a2SAaron LI else
748*d19ef5a2SAaron LI a = t_rise;
749*d19ef5a2SAaron LI } while (fabs(a - b) >= eps);
750*d19ef5a2SAaron LI
751*d19ef5a2SAaron LI if (t_rise < t + 1) {
752*d19ef5a2SAaron LI t_rise += loc->zone; /* standard time */
753*d19ef5a2SAaron LI /* may be just before to midnight */
754*d19ef5a2SAaron LI return (t_rise > (double)rd) ? t_rise : (double)rd;
755*d19ef5a2SAaron LI } else {
756*d19ef5a2SAaron LI /* no moonrise on this day */
757*d19ef5a2SAaron LI return NAN;
758*d19ef5a2SAaron LI }
759*d19ef5a2SAaron LI }
760*d19ef5a2SAaron LI
761*d19ef5a2SAaron LI /*
762*d19ef5a2SAaron LI * Calculate the moment of moonset in standard time on fixed date $rd
763*d19ef5a2SAaron LI * at location $loc.
764*d19ef5a2SAaron LI * NOTE: Return an NaN if no moonset.
765*d19ef5a2SAaron LI * Ref: Sec.(14.7), Eq.(14.84)
766*d19ef5a2SAaron LI */
767*d19ef5a2SAaron LI double
moonset(int rd,const struct location * loc)768*d19ef5a2SAaron LI moonset(int rd, const struct location *loc)
769*d19ef5a2SAaron LI {
770*d19ef5a2SAaron LI double t = (double)rd - loc->zone; /* universal time */
771*d19ef5a2SAaron LI bool waxing = lunar_phase(t) < 180.0;
772*d19ef5a2SAaron LI /* lunar altitude at midnight */
773*d19ef5a2SAaron LI double alt = lunar_altitude_observed(t, loc);
774*d19ef5a2SAaron LI double offset = alt / (4.0 * (90.0 - fabs(loc->latitude)));
775*d19ef5a2SAaron LI
776*d19ef5a2SAaron LI /* approximate setting time */
777*d19ef5a2SAaron LI double t_approx = t;
778*d19ef5a2SAaron LI if (waxing) {
779*d19ef5a2SAaron LI t_approx += offset;
780*d19ef5a2SAaron LI if (offset <= 0)
781*d19ef5a2SAaron LI t_approx += 1;
782*d19ef5a2SAaron LI } else {
783*d19ef5a2SAaron LI t_approx += 0.5 - offset;
784*d19ef5a2SAaron LI }
785*d19ef5a2SAaron LI
786*d19ef5a2SAaron LI /* binary search to determine the setting time */
787*d19ef5a2SAaron LI const double eps = 30.0 / 3600 / 24; /* accuracy of 30 seconds */
788*d19ef5a2SAaron LI double a = t_approx - 6.0/24.0;
789*d19ef5a2SAaron LI double b = t_approx + 6.0/24.0;
790*d19ef5a2SAaron LI double t_set;
791*d19ef5a2SAaron LI do {
792*d19ef5a2SAaron LI t_set = (a + b) / 2.0;
793*d19ef5a2SAaron LI if (lunar_altitude_observed(t_set, loc) < 0)
794*d19ef5a2SAaron LI b = t_set;
795*d19ef5a2SAaron LI else
796*d19ef5a2SAaron LI a = t_set;
797*d19ef5a2SAaron LI } while (fabs(a - b) >= eps);
798*d19ef5a2SAaron LI
799*d19ef5a2SAaron LI if (t_set < t + 1) {
800*d19ef5a2SAaron LI t_set += loc->zone; /* standard time */
801*d19ef5a2SAaron LI /* may be just before to midnight */
802*d19ef5a2SAaron LI return (t_set > (double)rd) ? t_set : (double)rd;
803*d19ef5a2SAaron LI } else {
804*d19ef5a2SAaron LI /* no moonrise on this day */
805*d19ef5a2SAaron LI return NAN;
806*d19ef5a2SAaron LI }
807*d19ef5a2SAaron LI }
808*d19ef5a2SAaron LI
809*d19ef5a2SAaron LI /**************************************************************************/
810*d19ef5a2SAaron LI
811*d19ef5a2SAaron LI /*
812*d19ef5a2SAaron LI * Print moon information at the given moment $t (in standard time)
813*d19ef5a2SAaron LI * and events in the year.
814*d19ef5a2SAaron LI */
815*d19ef5a2SAaron LI void
show_moon_info(double t,const struct location * loc)816*d19ef5a2SAaron LI show_moon_info(double t, const struct location *loc)
817*d19ef5a2SAaron LI {
818*d19ef5a2SAaron LI char buf[64];
819*d19ef5a2SAaron LI int rd = (int)floor(t);
820*d19ef5a2SAaron LI double t_u = t - loc->zone; /* universal time */
821*d19ef5a2SAaron LI
822*d19ef5a2SAaron LI /*
823*d19ef5a2SAaron LI * Lunar phase
824*d19ef5a2SAaron LI */
825*d19ef5a2SAaron LI
826*d19ef5a2SAaron LI double eps = 1e-5;
827*d19ef5a2SAaron LI double phi = lunar_phase(t_u);
828*d19ef5a2SAaron LI bool waxing = (phi <= 180);
829*d19ef5a2SAaron LI bool crescent = (phi <= 90 || phi > 270);
830*d19ef5a2SAaron LI const char *phase_name;
831*d19ef5a2SAaron LI if (fabs(phi) < eps || fabs(phi - 360) < eps)
832*d19ef5a2SAaron LI phase_name = "New Moon";
833*d19ef5a2SAaron LI else if (fabs(phi - 90) < eps)
834*d19ef5a2SAaron LI phase_name = "First Quarter";
835*d19ef5a2SAaron LI else if (fabs(phi - 180) < eps)
836*d19ef5a2SAaron LI phase_name = "Full Moon";
837*d19ef5a2SAaron LI else if (fabs(phi - 270) < eps)
838*d19ef5a2SAaron LI phase_name = "Last Quarter";
839*d19ef5a2SAaron LI else
840*d19ef5a2SAaron LI phase_name = NULL;
841*d19ef5a2SAaron LI
842*d19ef5a2SAaron LI if (phase_name) {
843*d19ef5a2SAaron LI snprintf(buf, sizeof(buf), "%s", phase_name);
844*d19ef5a2SAaron LI } else {
845*d19ef5a2SAaron LI snprintf(buf, sizeof(buf), "%s %s",
846*d19ef5a2SAaron LI (waxing ? "Waxing" : "Waning"),
847*d19ef5a2SAaron LI (crescent ? "Crescent" : "Gibbous"));
848*d19ef5a2SAaron LI }
849*d19ef5a2SAaron LI printf("Moon phase: %.2lf° (%s)\n", phi, buf);
850*d19ef5a2SAaron LI
851*d19ef5a2SAaron LI /*
852*d19ef5a2SAaron LI * Moon position
853*d19ef5a2SAaron LI */
854*d19ef5a2SAaron LI double lon = lunar_longitude(t_u);
855*d19ef5a2SAaron LI double alt = lunar_altitude_observed(t_u, loc);
856*d19ef5a2SAaron LI printf("Moon position: %.4lf° (longitude), %.4lf° (altitude)\n",
857*d19ef5a2SAaron LI lon, alt);
858*d19ef5a2SAaron LI
859*d19ef5a2SAaron LI /*
860*d19ef5a2SAaron LI * Moon rise and set
861*d19ef5a2SAaron LI */
862*d19ef5a2SAaron LI
863*d19ef5a2SAaron LI double moments[2] = { moonrise(rd, loc), moonset(rd, loc) };
864*d19ef5a2SAaron LI const char *names[2] = { "Moonrise", "Moonset" };
865*d19ef5a2SAaron LI if (!isnan(moments[0]) && !isnan(moments[1]) &&
866*d19ef5a2SAaron LI moments[0] > moments[1]) {
867*d19ef5a2SAaron LI double t_tmp = moments[0];
868*d19ef5a2SAaron LI moments[0] = moments[1];
869*d19ef5a2SAaron LI moments[1] = t_tmp;
870*d19ef5a2SAaron LI const char *p = names[0];
871*d19ef5a2SAaron LI names[0] = names[1];
872*d19ef5a2SAaron LI names[1] = p;
873*d19ef5a2SAaron LI }
874*d19ef5a2SAaron LI for (size_t i = 0; i < nitems(moments); i++) {
875*d19ef5a2SAaron LI if (isnan(moments[i]))
876*d19ef5a2SAaron LI snprintf(buf, sizeof(buf), "(null)");
877*d19ef5a2SAaron LI else
878*d19ef5a2SAaron LI format_time(buf, sizeof(buf), moments[i]);
879*d19ef5a2SAaron LI printf("%-8s: %s\n", names[i], buf);
880*d19ef5a2SAaron LI }
881*d19ef5a2SAaron LI
882*d19ef5a2SAaron LI /*
883*d19ef5a2SAaron LI * Moon phases in the year
884*d19ef5a2SAaron LI */
885*d19ef5a2SAaron LI
886*d19ef5a2SAaron LI int year = gregorian_year_from_fixed(rd);
887*d19ef5a2SAaron LI struct date date = { year, 1, 1 };
888*d19ef5a2SAaron LI double t_begin = fixed_from_gregorian(&date) - loc->zone;
889*d19ef5a2SAaron LI date.year++;
890*d19ef5a2SAaron LI double t_end = fixed_from_gregorian(&date) - loc->zone;
891*d19ef5a2SAaron LI
892*d19ef5a2SAaron LI printf("\nLunar events in year %d:\n", year);
893*d19ef5a2SAaron LI printf("%19s %19s %19s %19s\n",
894*d19ef5a2SAaron LI "New Moon", "First Quarter", "Full Moon", "Last Quarter");
895*d19ef5a2SAaron LI
896*d19ef5a2SAaron LI double t_newmoon = t_begin;
897*d19ef5a2SAaron LI while ((t_newmoon = new_moon_atafter(t_newmoon)) < t_end) {
898*d19ef5a2SAaron LI t_newmoon += loc->zone; /* to standard time */
899*d19ef5a2SAaron LI gregorian_from_fixed((int)floor(t_newmoon), &date);
900*d19ef5a2SAaron LI format_time(buf, sizeof(buf), t_newmoon);
901*d19ef5a2SAaron LI printf("%d-%02d-%02d %s",
902*d19ef5a2SAaron LI date.year, date.month, date.day, buf);
903*d19ef5a2SAaron LI
904*d19ef5a2SAaron LI /*
905*d19ef5a2SAaron LI * first quarter, full moon, last quarter
906*d19ef5a2SAaron LI */
907*d19ef5a2SAaron LI double t_event = t_newmoon;
908*d19ef5a2SAaron LI int phi_events[] = { 90, 180, 270 };
909*d19ef5a2SAaron LI for (size_t i = 0; i < nitems(phi_events); i++) {
910*d19ef5a2SAaron LI t_event = lunar_phase_atafter(phi_events[i], t_event);
911*d19ef5a2SAaron LI t_event += loc->zone;
912*d19ef5a2SAaron LI gregorian_from_fixed((int)floor(t_event), &date);
913*d19ef5a2SAaron LI format_time(buf, sizeof(buf), t_event);
914*d19ef5a2SAaron LI printf(" %d-%02d-%02d %s",
915*d19ef5a2SAaron LI date.year, date.month, date.day, buf);
916*d19ef5a2SAaron LI }
917*d19ef5a2SAaron LI printf("\n");
918*d19ef5a2SAaron LI
919*d19ef5a2SAaron LI /* go to the next new moon */
920*d19ef5a2SAaron LI t_newmoon += 28;
921*d19ef5a2SAaron LI }
922*d19ef5a2SAaron LI }
923