use supernovas_ffi::novas_geom_posvel;
use crate::{
Frame, Position, ReferenceSystem, Velocity,
error::{Error, Result},
source::Source,
};
#[derive(Debug, Clone, Copy)]
pub struct Geometric {
frame: Frame,
system: ReferenceSystem,
pos: Position,
vel: Velocity,
}
impl Geometric {
#[must_use]
pub fn position(self) -> Position {
self.pos
}
#[must_use]
pub fn velocity(self) -> Velocity {
self.vel
}
#[must_use]
pub fn reference_system(self) -> ReferenceSystem {
self.system
}
#[must_use]
pub fn frame(self) -> Frame {
self.frame
}
}
pub(crate) fn geometric_of_source_in(
source: &(impl Source + ?Sized),
frame: &Frame,
system: ReferenceSystem,
) -> Result<Geometric> {
let mut pos_au = [0.0_f64; 3];
let mut vel_au_per_day = [0.0_f64; 3];
let rc = unsafe {
novas_geom_posvel(
source.as_object(),
frame.as_novas_frame(),
system.to_sys(),
pos_au.as_mut_ptr(),
vel_au_per_day.as_mut_ptr(),
)
};
if rc != 0 {
return Err(Error::ffi(rc));
}
Ok(Geometric {
frame: *frame,
system,
pos: Position::from_au(pos_au[0], pos_au[1], pos_au[2])?,
vel: Velocity::from_au_per_day(vel_au_per_day[0], vel_au_per_day[1], vel_au_per_day[2])?,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Accuracy, CatalogEntry, Observer, Planet, SolarBody, Time, unit};
fn frame() -> Frame {
let obs = Observer::geodetic(37.234, -118.282, 1222.0).unwrap();
let t = Time::from_utc_jd(2_461_236.75, 37, 0.0).unwrap();
Frame::new(Accuracy::Reduced, &obs, &t).unwrap()
}
fn vega() -> CatalogEntry {
CatalogEntry::icrs(
"Vega",
"18:36:56.336".parse().unwrap(),
"+38:47:01.28".parse().unwrap(),
)
.unwrap()
}
#[test]
fn geometric_differs_from_apparent_at_arcsec_scale() {
let f = frame();
let g = vega().geometric_in(&f, ReferenceSystem::Cirs).unwrap();
let a = vega().apparent_in(&f, ReferenceSystem::Cirs).unwrap();
let gp = g.position().as_meters();
let gm = (gp[0] * gp[0] + gp[1] * gp[1] + gp[2] * gp[2]).sqrt();
let ghat = [gp[0] / gm, gp[1] / gm, gp[2] / gm];
let rhat = a.as_sky_pos().r_hat;
let dot = ghat[0] * rhat[0] + ghat[1] * rhat[1] + ghat[2] * rhat[2];
let sep_rad = dot.clamp(-1.0, 1.0).acos();
let sep_arcsec = sep_rad / unit::ARCSEC;
assert!(
sep_arcsec > 0.1,
"geometric vs apparent sep = {sep_arcsec} arcsec (too small; expected aberration signal)"
);
}
#[test]
fn getters_round_trip() {
let f = frame();
let g = vega().geometric_in(&f, ReferenceSystem::Cirs).unwrap();
assert_eq!(g.reference_system(), ReferenceSystem::Cirs);
assert!((g.frame().tt_jd() - f.tt_jd()).abs() < 1e-9);
assert!(g.position().as_meters().iter().all(|x| x.is_finite()));
assert!(g.velocity().as_mps().iter().all(|x| x.is_finite()));
}
#[test]
fn geometric_for_planet_is_finite() {
let f = Frame::new(
Accuracy::Reduced,
&Observer::Geocenter,
&Time::from_utc_jd(2_461_236.75, 37, 0.0).unwrap(),
)
.unwrap();
let g = Planet::new(SolarBody::Sun)
.unwrap()
.geometric_in(&f, ReferenceSystem::Icrs)
.unwrap();
let p = g.position().as_meters();
let mag = (p[0] * p[0] + p[1] * p[1] + p[2] * p[2]).sqrt();
assert!(mag > 1e10 && mag < 1e12, "Sun distance {mag} m looks wrong");
let v = g.velocity().as_mps();
assert!(v.iter().all(|x| x.is_finite()));
}
#[test]
fn itrs_output_is_supported() {
let f = frame();
let g = vega().geometric_in(&f, ReferenceSystem::Itrs).unwrap();
assert_eq!(g.reference_system(), ReferenceSystem::Itrs);
assert!(g.position().as_meters().iter().all(|x| x.is_finite()));
}
}