1use core::f64::consts::PI;
7
8use crate::elpmpp02_data::{
9 MAIN_DIST, MAIN_LAT, MAIN_LONG, PERT_DIST_T0, PERT_DIST_T1, PERT_DIST_T2, PERT_DIST_T3,
10 PERT_LAT_T0, PERT_LAT_T1, PERT_LAT_T2, PERT_LONG_T0, PERT_LONG_T1, PERT_LONG_T2, PERT_LONG_T3,
11};
12use crate::{
13 delaunay_args, normalize_radians, nutation_deps, nutation_dpsi, ARCSEC_TO_RAD, RAD_TO_DEG,
14};
15
16const TWO_PI: f64 = 2.0 * PI;
17const DEG: f64 = PI / 180.0;
18const SEC: f64 = PI / 648000.0;
20
21const DW1_0: f64 = -0.07008;
23const DW2_0: f64 = 0.20794;
24const DW3_0: f64 = -0.07215;
25const DEART_0: f64 = -0.00033;
26const DPERI: f64 = -0.00749;
27const DW1_1: f64 = -0.35106;
28const DGAM: f64 = 0.00085;
29const DE: f64 = -0.00006;
30const DEART_1: f64 = 0.00732;
31const DEP: f64 = 0.00224;
32const DW2_1: f64 = 0.08017;
33const DW3_1: f64 = -0.04317;
34const DW1_2: f64 = -0.03743;
35const DW1_3: f64 = -0.00018865;
36const DW1_4: f64 = -0.00001024;
37const DW2_2: f64 = 0.00470602;
38const DW2_3: f64 = -0.00025213;
39const DW3_2: f64 = -0.00261070;
40const DW3_3: f64 = -0.00010712;
41
42const AM: f64 = 0.074801329;
44const ALPHA: f64 = 0.002571881;
45const DTSM: f64 = 2.0 * ALPHA / (3.0 * AM);
46const XA: f64 = 2.0 * ALPHA / 3.0;
47const W11_RATE: f64 = 1732559343.73604;
48const W11: f64 = W11_RATE * SEC;
49const BP: [[f64; 2]; 5] = [
50 [0.311079095, -0.103837907],
51 [-0.004482398, 0.000668287],
52 [-0.001102485, -0.001298072],
53 [0.001056062, -0.000178028],
54 [0.000050928, -0.000037342],
55];
56const W21_RATE: f64 = (14643420.3171 + DW2_1) * SEC;
57const W31_RATE: f64 = (-6967919.5383 + DW3_1) * SEC;
58const X2: f64 = W21_RATE / W11;
59const X3: f64 = W31_RATE / W11;
60const Y2: f64 = AM * BP[0][0] + XA * BP[4][0];
61const Y3: f64 = AM * BP[0][1] + XA * BP[4][1];
62const D21: f64 = X2 - Y2;
63const D22: f64 = W11 * BP[1][0];
64const D23: f64 = W11 * BP[2][0];
65const D24: f64 = W11 * BP[3][0];
66const D25: f64 = Y2 / AM;
67const D31: f64 = X3 - Y3;
68const D32: f64 = W11 * BP[1][1];
69const D33: f64 = W11 * BP[2][1];
70const D34: f64 = W11 * BP[3][1];
71const D35: f64 = Y3 / AM;
72const CW2_1: f64 = D21 * DW1_1 + D25 * DEART_1 + D22 * DGAM + D23 * DE + D24 * DEP;
73const CW3_1: f64 = D31 * DW1_1 + D35 * DEART_1 + D32 * DGAM + D33 * DE + D34 * DEP;
74const DELNU_NU: f64 = (0.55604 + DW1_1) * SEC / W11;
75const DELE: f64 = (0.01789 + DE) * SEC;
76const DELG: f64 = (-0.08066 + DGAM) * SEC;
77const DELNP_NU: f64 = (-0.06424 + DEART_1) * SEC / W11;
78const DELEP: f64 = (-0.12879 + DEP) * SEC;
79const FB1: f64 = -AM * DELNU_NU + DELNP_NU;
80const FB2: f64 = DELG;
81const FB3: f64 = DELE;
82const FB4: f64 = DELEP;
83const FB5: f64 = -XA * DELNU_NU + DTSM * DELNP_NU;
84const FA_DIST: f64 = 1.0 - 2.0 / 3.0 * DELNU_NU;
85const RA0: f64 = 384747.961370173 / 384747.980674318;
86
87fn mod2pi(x: f64) -> f64 {
88 x - TWO_PI * ((x + PI) / TWO_PI).floor()
89}
90
91struct ElpArguments {
92 w1: f64,
93 d: f64,
94 f: f64,
95 l: f64,
96 lp: f64,
97 zeta: f64,
98 me: f64,
99 ve: f64,
100 em: f64,
101 ma: f64,
102 ju: f64,
103 sa: f64,
104 ur: f64,
105 ne: f64,
106}
107
108fn compute_elp_arguments(t: f64) -> ElpArguments {
109 let t2 = t * t;
110 let t3 = t2 * t;
111 let t4 = t3 * t;
112
113 let w10 = (-142.0 + 18.0 / 60.0 + (59.95571 + DW1_0) / 3600.0) * DEG;
114 let w1_1 = mod2pi((W11_RATE + DW1_1) * t * SEC);
115 let w12 = mod2pi((-6.8084 + DW1_2) * t2 * SEC);
116 let w13 = mod2pi((0.006604 + DW1_3) * t3 * SEC);
117 let w14 = mod2pi((-3.169e-5 + DW1_4) * t4 * SEC);
118
119 let w20 = (83.0 + 21.0 / 60.0 + (11.67475 + DW2_0) / 3600.0) * DEG;
120 let w21 = mod2pi((14643420.3171 + DW2_1 + CW2_1) * t * SEC);
121 let w22 = mod2pi((-38.2631 + DW2_2) * t2 * SEC);
122 let w23 = mod2pi((-0.045047 + DW2_3) * t3 * SEC);
123 let w24 = mod2pi(0.00021301 * t4 * SEC);
124
125 let w30 = (125.0 + 2.0 / 60.0 + (40.39816 + DW3_0) / 3600.0) * DEG;
126 let w31 = mod2pi((-6967919.5383 + DW3_1 + CW3_1) * t * SEC);
127 let w32 = mod2pi((6.359 + DW3_2) * t2 * SEC);
128 let w33 = mod2pi((0.007625 + DW3_3) * t3 * SEC);
129 let w34 = mod2pi(-3.586e-5 * t4 * SEC);
130
131 let ea0 = (100.0 + 27.0 / 60.0 + (59.13885 + DEART_0) / 3600.0) * DEG;
132 let ea1 = mod2pi((129597742.293 + DEART_1) * t * SEC);
133 let ea2 = mod2pi(-0.0202 * t2 * SEC);
134 let ea3 = mod2pi(9e-6 * t3 * SEC);
135 let ea4 = mod2pi(1.5e-7 * t4 * SEC);
136
137 let p0 = (102.0 + 56.0 / 60.0 + (14.45766 + DPERI) / 3600.0) * DEG;
138 let p1 = mod2pi(1161.24342 * t * SEC);
139 let p2 = mod2pi(0.529265 * t2 * SEC);
140 let p3 = mod2pi(-1.1814e-4 * t3 * SEC);
141 let p4 = mod2pi(1.1379e-5 * t4 * SEC);
142
143 let w1 = w10 + w1_1 + w12 + w13 + w14;
144 let w2 = w20 + w21 + w22 + w23 + w24;
145 let w3 = w30 + w31 + w32 + w33 + w34;
146 let ea = ea0 + ea1 + ea2 + ea3 + ea4;
147 let pomp = p0 + p1 + p2 + p3 + p4;
148
149 let me = (-108.0 + 15.0 / 60.0 + 3.216919 / 3600.0) * DEG + mod2pi(538101628.66888 * t * SEC);
150 let ve = (-179.0 + 58.0 / 60.0 + 44.758419 / 3600.0) * DEG + mod2pi(210664136.45777 * t * SEC);
151 let em = (100.0 + 27.0 / 60.0 + 59.13885 / 3600.0) * DEG + mod2pi(129597742.293 * t * SEC);
152 let ma = (-5.0 + 26.0 / 60.0 + 3.642778 / 3600.0) * DEG + mod2pi(68905077.65936 * t * SEC);
153 let ju = (34.0 + 21.0 / 60.0 + 5.379392 / 3600.0) * DEG + mod2pi(10925660.57335 * t * SEC);
154 let sa = (50.0 + 4.0 / 60.0 + 38.902495 / 3600.0) * DEG + mod2pi(4399609.33632 * t * SEC);
155 let ur = (-46.0 + 3.0 / 60.0 + 4.354234 / 3600.0) * DEG + mod2pi(1542482.57845 * t * SEC);
156 let ne = (-56.0 + 20.0 / 60.0 + 56.808371 / 3600.0) * DEG + mod2pi(786547.897 * t * SEC);
157
158 ElpArguments {
159 w1: mod2pi(w1),
160 d: mod2pi(w1 - ea + PI),
161 f: mod2pi(w1 - w3),
162 l: mod2pi(w1 - w2),
163 lp: mod2pi(ea - pomp),
164 zeta: mod2pi(w1 + 0.02438029560881907 * t),
165 me: mod2pi(me),
166 ve: mod2pi(ve),
167 em: mod2pi(em),
168 ma: mod2pi(ma),
169 ju: mod2pi(ju),
170 sa: mod2pi(sa),
171 ur: mod2pi(ur),
172 ne: mod2pi(ne),
173 }
174}
175
176fn main_sum(terms: &[[f64; 10]], args: &ElpArguments, is_sine: bool, fa: f64) -> f64 {
178 let mut sum = 0.0;
179 for t in terms {
180 let phase = t[0] * args.d + t[1] * args.f + t[2] * args.l + t[3] * args.lp;
181 let a_eff = fa * t[4] + FB1 * t[5] + FB2 * t[6] + FB3 * t[7] + FB4 * t[8] + FB5 * t[9];
182 sum += a_eff * if is_sine { phase.sin() } else { phase.cos() };
183 }
184 sum
185}
186
187fn pert_sum(terms: &[[f64; 15]], args: &ElpArguments) -> f64 {
189 let mut sum = 0.0;
190 for t in terms {
191 let phase = t[14]
192 + t[0] * args.d
193 + t[1] * args.f
194 + t[2] * args.l
195 + t[3] * args.lp
196 + t[4] * args.me
197 + t[5] * args.ve
198 + t[6] * args.em
199 + t[7] * args.ma
200 + t[8] * args.ju
201 + t[9] * args.sa
202 + t[10] * args.ur
203 + t[11] * args.ne
204 + t[12] * args.zeta;
205 sum += t[13] * phase.sin();
206 }
207 sum
208}
209
210fn compute_moon_position(t: f64) -> (f64, f64, f64) {
213 let args = compute_elp_arguments(t);
214
215 let main_long = main_sum(MAIN_LONG, &args, true, 1.0);
216 let main_lat = main_sum(MAIN_LAT, &args, true, 1.0);
217 let main_dist = main_sum(MAIN_DIST, &args, false, FA_DIST);
218
219 let pl0 = pert_sum(PERT_LONG_T0, &args);
220 let pl1 = pert_sum(PERT_LONG_T1, &args);
221 let pl2 = pert_sum(PERT_LONG_T2, &args);
222 let pl3 = pert_sum(PERT_LONG_T3, &args);
223 let pb0 = pert_sum(PERT_LAT_T0, &args);
224 let pb1 = pert_sum(PERT_LAT_T1, &args);
225 let pb2 = pert_sum(PERT_LAT_T2, &args);
226 let pd0 = pert_sum(PERT_DIST_T0, &args);
227 let pd1 = pert_sum(PERT_DIST_T1, &args);
228 let pd2 = pert_sum(PERT_DIST_T2, &args);
229 let pd3 = pert_sum(PERT_DIST_T3, &args);
230
231 let t2 = t * t;
232 let t3 = t2 * t;
233
234 let long_m = args.w1 + main_long + pl0 + mod2pi(pl1 * t) + mod2pi(pl2 * t2) + mod2pi(pl3 * t3);
235 let lat_m = main_lat + pb0 + mod2pi(pb1 * t) + mod2pi(pb2 * t2);
236 let r = RA0 * (main_dist + pd0 + pd1 * t + pd2 * t2 + pd3 * t3);
237
238 let cos_lat = lat_m.cos();
240 let x0 = r * long_m.cos() * cos_lat;
241 let y0 = r * long_m.sin() * cos_lat;
242 let z0 = r * lat_m.sin();
243
244 let t4 = t3 * t;
246 let t5 = t4 * t;
247 let p = 0.10180391e-4 * t + 0.47020439e-6 * t2 - 0.5417367e-9 * t3 - 0.2507948e-11 * t4
248 + 0.463486e-14 * t5;
249 let q = -0.113469002e-3 * t + 0.12372674e-6 * t2 + 0.12654170e-8 * t3
250 - 0.1371808e-11 * t4
251 - 0.320334e-14 * t5;
252 let sq = (1.0 - p * p - q * q).sqrt();
253 let p11 = 1.0 - 2.0 * p * p;
254 let p12 = 2.0 * p * q;
255 let p13 = 2.0 * p * sq;
256 let p21 = 2.0 * p * q;
257 let p22 = 1.0 - 2.0 * q * q;
258 let p23 = -2.0 * q * sq;
259 let p31 = -2.0 * p * sq;
260 let p32 = 2.0 * q * sq;
261 let p33 = 1.0 - 2.0 * p * p - 2.0 * q * q;
262
263 let x = p11 * x0 + p12 * y0 + p13 * z0;
264 let y = p21 * x0 + p22 * y0 + p23 * z0;
265 let z = p31 * x0 + p32 * y0 + p33 * z0;
266
267 let dist = (x * x + y * y + z * z).sqrt();
268 let longitude = y.atan2(x);
269 let latitude = (z / dist).asin();
270 (longitude, latitude, dist)
271}
272
273fn precession_in_longitude(t: f64) -> f64 {
274 let t2 = t * t;
275 5028.796195 * t + 1.1054348 * t2 - 0.00007964 * t2 * t
276}
277
278fn mean_obliquity(t: f64) -> f64 {
279 let t2 = t * t;
280 (84381.448 - 46.8150 * t - 0.00059 * t2 + 0.001813 * t2 * t) * ARCSEC_TO_RAD
281}
282
283fn true_obliquity(t: f64) -> f64 {
284 let (l, lp, f, d, om) = delaunay_args(t);
285 mean_obliquity(t) + nutation_deps(l, lp, f, d, om, t) * ARCSEC_TO_RAD
286}
287
288fn ecliptic_to_equatorial(lambda: f64, beta: f64, eps: f64) -> (f64, f64) {
289 let (sin_lam, cos_lam) = lambda.sin_cos();
290 let (sin_beta, cos_beta) = beta.sin_cos();
291 let (sin_eps, cos_eps) = eps.sin_cos();
292 let ra = (sin_lam * cos_eps - sin_beta / cos_beta * sin_eps).atan2(cos_lam);
293 let dec = (sin_beta * cos_eps + cos_beta * sin_eps * sin_lam).asin();
294 (ra, dec)
295}
296
297#[derive(Debug, Clone, Copy, PartialEq)]
301pub struct MoonState {
302 pub longitude_degrees: f64,
304 pub latitude_degrees: f64,
306 pub distance_km: f64,
308 pub ra_degrees: f64,
310 pub dec_degrees: f64,
312}
313
314#[must_use]
317pub fn moon_position(jde_tt: f64) -> MoonState {
318 let t = (jde_tt - 2451545.0) / 36525.0;
319 let (geo_lon, lat, dist) = compute_moon_position(t);
320
321 let p_a = precession_in_longitude(t);
322 let (l, lp, f, d, om) = delaunay_args(t);
323 let dpsi = nutation_dpsi(l, lp, f, d, om, t);
324 let lon = normalize_radians(geo_lon + (p_a + dpsi) * ARCSEC_TO_RAD);
325
326 let eps = true_obliquity(t);
327 let (ra, dec) = ecliptic_to_equatorial(lon, lat, eps);
328
329 MoonState {
330 longitude_degrees: (lon * RAD_TO_DEG).rem_euclid(360.0),
331 latitude_degrees: lat * RAD_TO_DEG,
332 distance_km: dist,
333 ra_degrees: (ra * RAD_TO_DEG).rem_euclid(360.0),
334 dec_degrees: dec * RAD_TO_DEG,
335 }
336}