use thiserror::Error;
use crate::SKResult;
#[derive(Debug, Error)]
pub enum KeplerError {
#[error("Eccentricity Out of Bounds {x}")]
EccenOutOfBound{x: f64}
}
pub enum Anomaly {
Mean(f64),
True(f64),
Eccentric(f64)
}
use nalgebra::{Vector3, UnitQuaternion};
type Vec3 = Vector3<f64>;
type Quat = UnitQuaternion<f64>;
#[derive(Debug, Clone)]
pub struct Kepler {
pub a: f64,
pub eccen: f64,
pub incl: f64,
pub raan: f64,
pub w: f64,
pub nu: f64, }
fn mean2eccentric(m: f64, eccen: f64) -> f64
{
use std::f64::consts::PI;
#[allow(non_snake_case)]
let mut E = match (m > PI) || ((m < 0.0) && (m > -PI)) {
true => m - eccen,
false => m + eccen
};
loop {
let de = (m - E + eccen*E.sin()) / (1.0 - eccen*E.cos());
E += de;
if de.abs() < 1.0e-6 {
break;
}
}
E
}
fn eccentric2true(ea: f64, eccen: f64) -> f64 {
f64::atan2(
ea.sin() * (1.0 - eccen.powi(2)).sqrt(),
ea.cos() - eccen
)
}
fn mean2true(ma: f64, eccen: f64) -> f64 {
eccentric2true(mean2eccentric(ma, eccen), eccen)
}
fn to_trueanomaly(an: Anomaly, eccen: f64) -> f64 {
match an {
Anomaly::True(v) => v,
Anomaly::Mean(ma) => {
mean2true(ma, eccen)
}
Anomaly::Eccentric(ea) => {
eccentric2true(ea, eccen)
}
}
}
impl Kepler {
pub fn new(a: f64, eccen: f64, i: f64, raan: f64, argp: f64, an: Anomaly) -> Kepler {
Kepler {
a: a,
eccen: eccen,
incl: i,
raan: raan,
w: argp,
nu: to_trueanomaly(an, eccen)
}
}
pub fn semiparameter(&self) -> f64 {
self.a * (1.0 - self.eccen.powi(2))
}
pub fn propagate(&self, dt: &crate::Duration) -> Kepler {
let n = self.mean_motion();
let ma = self.mean_anomaly() + n*dt.seconds();
let nu = mean2true(ma, self.eccen);
Kepler {
a: self.a,
eccen: self.eccen,
incl: self.incl,
raan: self.raan,
w: self.w,
nu: nu
}
}
pub fn eccentric_anomaly(&self) -> f64 {
f64::atan2(
self.nu.sin()*(1.0-self.eccen.powi(2)).sqrt(),
1.0 + self.eccen * self.nu.cos())
}
pub fn mean_anomaly(&self) -> f64 {
let ea = self.eccentric_anomaly();
ea - self.eccen*ea.sin()
}
pub fn true_anomaly(&self) -> f64 {
self.nu
}
pub fn mean_motion(&self) -> f64 {
(crate::consts::MU_EARTH / self.a.powi(3)).sqrt()
}
pub fn period(&self) -> f64 {
2.0 * std::f64::consts::PI / self.mean_motion()
}
pub fn from_pv(r: Vec3, v: Vec3) -> SKResult<Kepler> {
let h = r.cross(&v);
let n = Vec3::z_axis().cross(&h);
let e =
((v.norm_squared() -
crate::consts::MU_EARTH / r.norm()) * r - r.dot(&v) * v) / crate::consts::MU_EARTH;
let eccen = e.norm();
if eccen >= 1.0 {
return Err(Box::new(KeplerError::EccenOutOfBound { x: eccen }));
}
let xi = v.norm().powi(2) / 2.0 - crate::consts::MU_EARTH / r.norm();
let a = -crate::consts::MU_EARTH / (2.0 * xi);
let incl = (h.z/h.norm()).acos();
let mut raan = (n.x/n.norm()).acos();
if n.y < 0.0 {
raan = 2.0 * std::f64::consts::PI - raan;
}
let mut w = (n.dot(&e)/n.norm()/e.norm()).acos();
if e.z < 0.0 {
w = 2.0 * std::f64::consts::PI - w;
}
let mut nu = (r.dot(&e)/r.norm()/e.norm()).acos();
if r.dot(&v) < 0.0 {
nu = 2.0 * std::f64::consts::PI - nu;
}
Ok(Kepler::new(a, eccen, incl, raan, w, Anomaly::True(nu)))
}
pub fn to_pv(&self) -> (Vec3, Vec3) {
let p = self.a * (1.0 - self.eccen.powi(2));
let r = p / (1.0 + self.eccen * self.nu.cos());
let r_pqw = Vec3::new(r * self.nu.cos(), r * self.nu.sin(), 0.0);
let v_pqw = Vec3::new(
-self.nu.sin(),
self.eccen + self.nu.cos(),
0.0) * (crate::consts::MU_EARTH/p).sqrt();
let q = Quat::from_axis_angle(&Vec3::z_axis(), self.raan) *
Quat::from_axis_angle(&Vec3::x_axis(), self.incl) *
Quat::from_axis_angle(&Vec3::z_axis(), self.w);
(q * r_pqw, q * v_pqw)
}
}
impl std::fmt::Display for Kepler {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f,
"Keplerian Elements:\n a = {:.0} m\n e = {:.3}\n i = {:.3} rad\n",
self.a, self.eccen, self.incl)?;
write!(f, " Ω = {:.3} rad\n ω = {:.3} rad\n ν = {:.3} rad\n", self.raan, self.w, self.nu)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_topv() {
let p = 11067790.0;
let eccen = 0.83285_f64;
let incl = 87.87_f64.to_radians();
let raan = 227.89_f64.to_radians();
let w = 53.38_f64.to_radians();
let nu = 92.335_f64.to_radians();
let a = p / (1.0 - eccen.powi(2));
let k = Kepler::new(a, eccen, incl, raan, w, Anomaly::True(nu));
let (r, v) = k.to_pv();
assert!((r*1.0e-3 - Vec3::new(6525.368, 6861.532, 6449.119)).norm() < 1e-3);
assert!((v*1.0e-3 - Vec3::new(4.902279, 5.533140, -1.975710)).norm() < 1e-3);
}
#[test]
fn test_frompv() {
let r = Vec3::new(6524.834, 6862.875, 6448.296)*1.0e3;
let v = Vec3::new(4.901327, 5.533756, -1.976341)*1.0e3;
let k = Kepler::from_pv(r, v).unwrap();
assert!((k.a - 36127343_f64).abs() < 1.0e3);
assert!((k.eccen - 0.83285).abs() < 1e-3);
assert!((k.incl - 87.87_f64.to_radians()).abs() < 1e-3);
assert!((k.raan - 227.89_f64.to_radians()).abs() < 1e-3);
assert!((k.w - 53.38_f64.to_radians()).abs() < 1e-3);
assert!((k.nu - 92.335_f64.to_radians()).abs() < 1e-3);
}
}