use crate::astro::earth_rotation_provider::itrs_to_equatorial_mean_j2000_rotation;
use crate::astro::eop::EopProvider;
use crate::calculus::ephemeris::Ephemeris;
use crate::coordinates::cartesian::Velocity;
use crate::coordinates::centers::Geodetic;
use crate::coordinates::frames::EquatorialMeanJ2000;
use crate::coordinates::frames::ECEF;
use crate::coordinates::transform::context::{AstroContext, DefaultEphemeris};
use crate::time::JulianDate;
use qtty::{AstronomicalUnit, Day};
pub type AuPerDay = qtty::Per<AstronomicalUnit, Day>;
#[derive(Debug, Clone)]
pub struct ObserverState {
velocity: Velocity<EquatorialMeanJ2000, AuPerDay>,
jd: JulianDate,
}
impl ObserverState {
pub fn geocentric(jd: JulianDate) -> Self {
use crate::coordinates::transform::TransformFrame;
let vel_ecl = DefaultEphemeris::earth_barycentric_velocity(jd);
let velocity: Velocity<EquatorialMeanJ2000, AuPerDay> = vel_ecl.to_frame();
Self { velocity, jd }
}
pub fn topocentric(site: &Geodetic<ECEF>, jd: JulianDate) -> Self {
let ctx: AstroContext = AstroContext::default();
Self::topocentric_with_ctx(site, jd, &ctx)
}
pub fn topocentric_with_ctx<Eph, Eop: EopProvider, Nut>(
site: &Geodetic<ECEF>,
jd: JulianDate,
ctx: &AstroContext<Eph, Eop, Nut>,
) -> Self {
use crate::coordinates::transform::TransformFrame;
use qtty::{Meter, Second, Seconds};
let vel_ecl = DefaultEphemeris::earth_barycentric_velocity(jd);
let mut velocity: Velocity<EquatorialMeanJ2000, AuPerDay> = vel_ecl.to_frame();
const OMEGA_EARTH: f64 = 7.292_115_0e-5;
let site_itrf_m = site.to_cartesian::<Meter>();
type MetersPerSecond = qtty::Per<Meter, Second>;
let one_second = Seconds::new(1.0);
let vx_ecef: qtty::Quantity<MetersPerSecond> =
(-site_itrf_m.y() * OMEGA_EARTH) / one_second;
let vy_ecef: qtty::Quantity<MetersPerSecond> = (site_itrf_m.x() * OMEGA_EARTH) / one_second;
let vz_ecef: qtty::Quantity<MetersPerSecond> = qtty::Quantity::zero();
let rot = itrs_to_equatorial_mean_j2000_rotation(jd, ctx);
let [vx_eq, vy_eq, vz_eq] = rot * [vx_ecef, vy_ecef, vz_ecef];
let v_diurnal = Velocity::<EquatorialMeanJ2000, AuPerDay>::new(
vx_eq.to::<AuPerDay>(),
vy_eq.to::<AuPerDay>(),
vz_eq.to::<AuPerDay>(),
);
velocity = Velocity::<EquatorialMeanJ2000, AuPerDay>::new(
velocity.x() + v_diurnal.x(),
velocity.y() + v_diurnal.y(),
velocity.z() + v_diurnal.z(),
);
Self { velocity, jd }
}
pub fn from_velocity(
velocity: Velocity<EquatorialMeanJ2000, AuPerDay>,
jd: JulianDate,
) -> Self {
Self { velocity, jd }
}
pub fn velocity(&self) -> &Velocity<EquatorialMeanJ2000, AuPerDay> {
&self.velocity
}
pub fn jd(&self) -> JulianDate {
self.jd
}
}
#[cfg(test)]
mod tests {
use super::*;
use qtty::*;
#[test]
fn test_geocentric_observer_state() {
let jd = JulianDate::J2000;
let obs = ObserverState::geocentric(jd);
let vel = obs.velocity();
let speed = (vel.x() * vel.x() + vel.y() * vel.y() + vel.z() * vel.z()).sqrt();
assert!(speed > 0.015, "Earth orbital speed too low: {}", speed);
assert!(speed < 0.020, "Earth orbital speed too high: {}", speed);
}
#[test]
fn test_observer_jd() {
let jd = JulianDate::new(2451545.0);
let obs = ObserverState::geocentric(jd);
assert_eq!(obs.jd(), jd);
}
#[test]
fn test_topocentric_includes_diurnal_velocity() {
let jd = JulianDate::J2000;
let geo = ObserverState::geocentric(jd);
let site = Geodetic::<ECEF>::new(0.0 * DEG, 0.0 * DEG, 0.0 * M); let topo = ObserverState::topocentric(&site, jd);
let dvx = topo.velocity().x() - geo.velocity().x();
let dvy = topo.velocity().y() - geo.velocity().y();
let dvz = topo.velocity().z() - geo.velocity().z();
let dv = (dvx * dvx + dvy * dvy + dvz * dvz).sqrt();
assert!(dv > 2.3e-4, "diurnal speed too low: {}", dv);
assert!(dv < 3.1e-4, "diurnal speed too high: {}", dv);
}
}