1#![warn(missing_docs)]
7
8pub use crate::dynamic::DynOrigin;
14pub use generated::*;
15use lox_core::elements::GravitationalParameter;
16use lox_core::f64::consts::{SECONDS_PER_DAY, SECONDS_PER_JULIAN_CENTURY};
17use lox_core::units::Distance;
18use std::fmt::{Display, Formatter};
19use thiserror::Error;
20
21pub mod dynamic;
23#[allow(clippy::approx_constant)]
24mod generated;
25
26#[derive(Clone, Copy, Debug, Eq, PartialEq)]
28#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
29#[repr(transparent)]
30pub struct NaifId(pub i32);
31
32impl Display for NaifId {
33 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
34 write!(f, "{}", self.0)
35 }
36}
37
38pub trait Origin {
40 fn id(&self) -> NaifId;
42 fn name(&self) -> &'static str;
44}
45
46#[derive(Clone, Debug, Error, Eq, PartialEq)]
48#[error("undefined property '{prop}' for origin '{origin}'")]
49pub struct UndefinedOriginPropertyError {
50 origin: String,
51 prop: String,
52}
53
54pub type Radii = (Distance, Distance, Distance);
56
57pub trait TryTriaxialEllipsoid: Origin {
59 fn try_radii(&self) -> Result<Radii, UndefinedOriginPropertyError>;
61}
62
63pub trait TriaxialEllipsoid: Origin {
65 fn radii(&self) -> Radii;
67}
68
69impl<T: TriaxialEllipsoid> TryTriaxialEllipsoid for T {
70 fn try_radii(&self) -> Result<Radii, UndefinedOriginPropertyError> {
71 Ok(self.radii())
72 }
73}
74
75fn flattening(equatorial_radius: Distance, polar_radius: Distance) -> f64 {
76 let r_eq = equatorial_radius.as_f64();
77 let r_p = polar_radius.as_f64();
78 (r_eq - r_p) / r_eq
79}
80
81pub trait Spheroid: TriaxialEllipsoid {
83 fn equatorial_radius(&self) -> Distance {
85 self.radii().0
86 }
87
88 fn polar_radius(&self) -> Distance {
90 self.radii().2
91 }
92
93 fn flattening(&self) -> f64 {
95 flattening(self.equatorial_radius(), self.polar_radius())
96 }
97}
98
99pub trait TrySpheroid: TryTriaxialEllipsoid {
101 fn try_equatorial_radius(&self) -> Result<Distance, UndefinedOriginPropertyError>;
103
104 fn try_polar_radius(&self) -> Result<Distance, UndefinedOriginPropertyError>;
106
107 fn try_flattening(&self) -> Result<f64, UndefinedOriginPropertyError> {
109 self.try_radii().map(|radii| flattening(radii.0, radii.2))
110 }
111}
112
113impl<T: Spheroid> TrySpheroid for T {
114 fn try_equatorial_radius(&self) -> Result<Distance, UndefinedOriginPropertyError> {
115 Ok(self.equatorial_radius())
116 }
117
118 fn try_polar_radius(&self) -> Result<Distance, UndefinedOriginPropertyError> {
119 Ok(self.polar_radius())
120 }
121
122 fn try_flattening(&self) -> Result<f64, UndefinedOriginPropertyError> {
123 Ok(self.flattening())
124 }
125}
126
127pub trait TryMeanRadius: Origin {
129 fn try_mean_radius(&self) -> Result<Distance, UndefinedOriginPropertyError>;
131}
132
133pub trait MeanRadius: Origin {
135 fn mean_radius(&self) -> Distance;
137}
138
139impl<T: MeanRadius> TryMeanRadius for T {
140 fn try_mean_radius(&self) -> Result<Distance, UndefinedOriginPropertyError> {
141 Ok(self.mean_radius())
142 }
143}
144
145pub trait PointMass: Origin {
147 fn gravitational_parameter(&self) -> GravitationalParameter;
149}
150
151pub trait TryPointMass: Origin {
153 fn try_gravitational_parameter(
155 &self,
156 ) -> Result<GravitationalParameter, UndefinedOriginPropertyError>;
157}
158
159impl<T: PointMass> TryPointMass for T {
160 fn try_gravitational_parameter(
161 &self,
162 ) -> Result<GravitationalParameter, UndefinedOriginPropertyError> {
163 Ok(self.gravitational_parameter())
164 }
165}
166
167#[derive(Clone, Copy, Debug)]
168pub(crate) enum RotationalElementType {
169 RightAscension,
170 Declination,
171 Rotation,
172}
173
174impl RotationalElementType {
175 fn sincos(&self, val: f64) -> f64 {
176 match self {
177 RotationalElementType::Declination => val.cos(),
178 _ => val.sin(),
179 }
180 }
181
182 fn sincos_dot(&self, val: f64) -> f64 {
183 match self {
184 RotationalElementType::Declination => val.sin(),
185 _ => val.cos(),
186 }
187 }
188
189 fn sign(&self) -> f64 {
190 match self {
191 RotationalElementType::Declination => -1.0,
192 _ => 1.0,
193 }
194 }
195
196 fn dt(&self) -> f64 {
197 match self {
198 RotationalElementType::Rotation => SECONDS_PER_DAY,
199 _ => SECONDS_PER_JULIAN_CENTURY,
200 }
201 }
202}
203
204pub(crate) struct RotationalElement<const N: usize> {
205 typ: RotationalElementType,
206 c0: f64,
207 c1: f64,
208 c2: f64,
209 c: [f64; N],
210 theta0: [f64; N],
211 theta1: [f64; N],
212}
213
214impl<const N: usize> RotationalElement<N> {
215 fn trig_term(&self, t: f64) -> f64 {
216 self.c
217 .iter()
218 .zip(self.theta0.iter())
219 .zip(self.theta1.iter())
220 .map(|((&c, &theta0), &theta1)| {
221 c * self
222 .typ
223 .sincos(theta0 + theta1 * t / SECONDS_PER_JULIAN_CENTURY)
224 })
225 .sum()
226 }
227
228 fn trig_term_dot(&self, t: f64) -> f64 {
229 self.c
230 .iter()
231 .zip(self.theta0.iter())
232 .zip(self.theta1.iter())
233 .map(|((&c, &theta0), &theta1)| {
234 c * theta1 / SECONDS_PER_JULIAN_CENTURY
235 * self
236 .typ
237 .sincos_dot(theta0 + theta1 * t / SECONDS_PER_JULIAN_CENTURY)
238 })
239 .sum()
240 }
241
242 fn angle(&self, t: f64) -> f64 {
243 self.c0
244 + self.c1 * t / self.typ.dt()
245 + self.c2 * t.powi(2) / self.typ.dt().powi(2)
246 + self.trig_term(t)
247 }
248
249 fn angle_dot(&self, t: f64) -> f64 {
250 self.c1 / self.typ.dt()
251 + 2.0 * self.c2 * t / self.typ.dt().powi(2)
252 + self.typ.sign() * self.trig_term_dot(t)
253 }
254}
255
256pub type Elements = (f64, f64, f64);
258
259pub trait RotationalElements: Origin {
261 fn rotational_elements(&self, t: f64) -> Elements;
263
264 fn rotational_element_rates(&self, t: f64) -> Elements;
266
267 fn right_ascension(&self, t: f64) -> f64 {
269 self.rotational_elements(t).0
270 }
271
272 fn right_ascension_rate(&self, t: f64) -> f64 {
274 self.rotational_element_rates(t).0
275 }
276
277 fn declination(&self, t: f64) -> f64 {
279 self.rotational_elements(t).1
280 }
281
282 fn declination_rate(&self, t: f64) -> f64 {
284 self.rotational_element_rates(t).1
285 }
286
287 fn rotation_angle(&self, t: f64) -> f64 {
289 self.rotational_elements(t).2
290 }
291
292 fn rotation_rate(&self, t: f64) -> f64 {
294 self.rotational_element_rates(t).2
295 }
296}
297
298pub trait TryRotationalElements: Origin {
300 fn try_rotational_elements(&self, t: f64) -> Result<Elements, UndefinedOriginPropertyError>;
302
303 fn try_rotational_element_rates(
305 &self,
306 t: f64,
307 ) -> Result<Elements, UndefinedOriginPropertyError>;
308
309 fn try_right_ascension(&self, t: f64) -> Result<f64, UndefinedOriginPropertyError> {
311 self.try_rotational_elements(t).map(|r| r.0)
312 }
313
314 fn try_right_ascension_rate(&self, t: f64) -> Result<f64, UndefinedOriginPropertyError> {
316 self.try_rotational_element_rates(t).map(|r| r.0)
317 }
318
319 fn try_declination(&self, t: f64) -> Result<f64, UndefinedOriginPropertyError> {
321 self.try_rotational_elements(t).map(|r| r.1)
322 }
323
324 fn try_declination_rate(&self, t: f64) -> Result<f64, UndefinedOriginPropertyError> {
326 self.try_rotational_element_rates(t).map(|r| r.1)
327 }
328
329 fn try_rotation_angle(&self, t: f64) -> Result<f64, UndefinedOriginPropertyError> {
331 self.try_rotational_elements(t).map(|r| r.2)
332 }
333
334 fn try_rotation_rate(&self, t: f64) -> Result<f64, UndefinedOriginPropertyError> {
336 self.try_rotational_element_rates(t).map(|r| r.2)
337 }
338}
339
340impl<T: RotationalElements> TryRotationalElements for T {
341 fn try_rotational_elements(&self, t: f64) -> Result<Elements, UndefinedOriginPropertyError> {
342 Ok(self.rotational_elements(t))
343 }
344
345 fn try_rotational_element_rates(
346 &self,
347 t: f64,
348 ) -> Result<Elements, UndefinedOriginPropertyError> {
349 Ok(self.rotational_element_rates(t))
350 }
351}
352
353pub trait TryJ2 {
355 fn try_j2(&self) -> Result<f64, UndefinedOriginPropertyError>;
357}
358
359pub trait J2 {
361 fn j2(&self) -> f64;
363}
364
365impl<T> TryJ2 for T
366where
367 T: J2,
368{
369 fn try_j2(&self) -> Result<f64, UndefinedOriginPropertyError> {
370 Ok(self.j2())
371 }
372}
373
374impl J2 for Earth {
375 fn j2(&self) -> f64 {
376 1.08262668e-3
377 }
378}
379
380#[cfg(test)]
381mod tests {
382 use lox_test_utils::assert_approx_eq;
383
384 use super::*;
385
386 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
390 pub struct Jupiter;
391
392 impl Origin for Jupiter {
393 fn id(&self) -> NaifId {
394 NaifId(599)
395 }
396
397 fn name(&self) -> &'static str {
398 "Jupiter"
399 }
400 }
401 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
402 pub struct Rupert;
403
404 impl Origin for Rupert {
405 fn id(&self) -> NaifId {
406 NaifId(1099)
407 }
408
409 fn name(&self) -> &'static str {
410 "Persephone/Rupert"
411 }
412 }
413
414 #[test]
415 fn test_body() {
416 let body = Jupiter;
417 let id = body.id().0;
418 let name = body.name();
419 assert_eq!(id, 599);
420 assert_eq!(name, "Jupiter");
421
422 let body = Rupert;
423 let id = body.id().0;
424 let name = body.name();
425 assert_eq!(id, 1099);
426 assert_eq!(name, "Persephone/Rupert");
427 }
428
429 const RIGHT_ASCENSION_JUPITER: RotationalElement<15> = RotationalElement {
430 typ: RotationalElementType::RightAscension,
431 c0: 4.6784701644349695f64,
432 c1: -0.00011342894808711148f64,
433 c2: 0f64,
434 c: [
435 0f64,
436 0f64,
437 0f64,
438 0f64,
439 0f64,
440 0f64,
441 0f64,
442 0f64,
443 0f64,
444 0f64,
445 0.0000020420352248333656f64,
446 0.000016371188383706813f64,
447 0.000024993114888558796f64,
448 0.0000005235987755982989f64,
449 0.00003752457891787809f64,
450 ],
451 theta0: [
452 1.2796754075622423f64,
453 0.42970006184100396f64,
454 4.9549897464119015f64,
455 6.2098814785958245f64,
456 2.092649773141201f64,
457 4.010766621082969f64,
458 6.147922290150026f64,
459 1.9783307071355725f64,
460 2.5593508151244846f64,
461 0.8594001236820079f64,
462 1.734171606432425f64,
463 3.0699533280603655f64,
464 5.241627996900319f64,
465 1.9898901100379935f64,
466 0.864134346731335f64,
467 ],
468 theta1: [
469 1596.503281347521f64,
470 787.7927551311844f64,
471 84.66068602648895f64,
472 20.792107379008446f64,
473 4.574507969477138f64,
474 1.1222467090323538f64,
475 41.58421475801689f64,
476 105.9414855960558f64,
477 3193.006562695042f64,
478 1575.5855102623689f64,
479 84.65553032387855f64,
480 20.80363527871787f64,
481 4.582318317879813f64,
482 105.94580703128374f64,
483 1.1222467090323538f64,
484 ],
485 };
486
487 const DECLINATION_JUPITER: RotationalElement<15> = RotationalElement {
488 typ: RotationalElementType::Declination,
489 c0: 1.1256553894213766f64,
490 c1: 0.00004211479485062318f64,
491 c2: 0f64,
492 c: [
493 0f64,
494 0f64,
495 0f64,
496 0f64,
497 0f64,
498 0f64,
499 0f64,
500 0f64,
501 0f64,
502 0f64,
503 0.0000008726646259971648f64,
504 0.000007051130178057092f64,
505 0.000010768681484805013f64,
506 -0.00000022689280275926283f64,
507 0.00001616174887346749f64,
508 ],
509 theta0: [
510 1.2796754075622423f64,
511 0.42970006184100396f64,
512 4.9549897464119015f64,
513 6.2098814785958245f64,
514 2.092649773141201f64,
515 4.010766621082969f64,
516 6.147922290150026f64,
517 1.9783307071355725f64,
518 2.5593508151244846f64,
519 0.8594001236820079f64,
520 1.734171606432425f64,
521 3.0699533280603655f64,
522 5.241627996900319f64,
523 1.9898901100379935f64,
524 0.864134346731335f64,
525 ],
526 theta1: [
527 1596.503281347521f64,
528 787.7927551311844f64,
529 84.66068602648895f64,
530 20.792107379008446f64,
531 4.574507969477138f64,
532 1.1222467090323538f64,
533 41.58421475801689f64,
534 105.9414855960558f64,
535 3193.006562695042f64,
536 1575.5855102623689f64,
537 84.65553032387855f64,
538 20.80363527871787f64,
539 4.582318317879813f64,
540 105.94580703128374f64,
541 1.1222467090323538f64,
542 ],
543 };
544
545 const ROTATION_JUPITER: RotationalElement<0> = RotationalElement {
546 typ: RotationalElementType::Rotation,
547 c0: 4.973315703557842f64,
548 c1: 15.193719457141356f64,
549 c2: 0f64,
550 c: [],
551 theta0: [],
552 theta1: [],
553 };
554
555 impl RotationalElements for Jupiter {
556 fn rotational_elements(&self, t: f64) -> Elements {
557 (
558 RIGHT_ASCENSION_JUPITER.angle(t),
559 DECLINATION_JUPITER.angle(t),
560 ROTATION_JUPITER.angle(t),
561 )
562 }
563
564 fn rotational_element_rates(&self, t: f64) -> Elements {
565 (
566 RIGHT_ASCENSION_JUPITER.angle_dot(t),
567 DECLINATION_JUPITER.angle_dot(t),
568 ROTATION_JUPITER.angle_dot(t),
569 )
570 }
571 }
572
573 #[test]
574 fn test_rotational_elements_right_ascension() {
575 assert_approx_eq!(
576 Jupiter.right_ascension(0.0),
577 4.678480799964803,
578 rtol <= 1e-8
579 );
580 }
581
582 #[test]
583 fn test_rotational_elements_right_ascension_dot() {
584 assert_approx_eq!(
585 Jupiter.right_ascension_rate(0.0),
586 -1.3266588500099516e-13,
587 rtol <= 1e-8
588 );
589 }
590
591 #[test]
592 fn test_rotational_elements_declination() {
593 assert_approx_eq!(Jupiter.declination(0.0), 1.1256642372977634, rtol <= 1e-8);
594 }
595
596 #[test]
597 fn test_rotational_elements_declination_dot() {
598 assert_approx_eq!(
599 Jupiter.declination_rate(0.0),
600 3.004482367136341e-15,
601 rtol <= 1e-8
602 );
603 }
604
605 #[test]
606 fn test_rotational_elements_prime_meridian() {
607 assert_approx_eq!(Jupiter.rotation_angle(0.0), 4.973315703557842, rtol <= 1e-8);
608 }
609
610 #[test]
611 fn test_rotational_elements_prime_meridian_dot() {
612 assert_approx_eq!(
613 Jupiter.rotation_rate(0.0),
614 0.00017585323445765458,
615 rtol <= 1e-8
616 );
617 }
618
619 #[test]
620 fn test_rotational_elements_error() {
621 assert!(DynOrigin::Mundilfari.try_rotational_elements(0.0).is_err());
622 assert!(
623 DynOrigin::Mundilfari
624 .try_rotational_element_rates(0.0)
625 .is_err()
626 );
627 }
628}