use crate::{coord, time};
pub struct Sun;
pub const SUN: Sun = Sun;
impl Sun {
pub fn locationcart(&self, d: time::Date) -> (f64, f64, f64) {
let (x, y, z) = EARTH.locationcart(d);
(-x, -y, -z)
}
pub fn location(&self, d: time::Date) -> coord::Coord {
let (x, y, z) = self.locationcart(d);
coord::Coord::from_cartesian(x, y, z)
}
pub fn distance(&self, d: time::Date) -> f64 {
let (tx, ty, tz) = self.locationcart(d);
(tx * tx + ty * ty + tz * tz).sqrt()
}
pub fn angdia(&self, d: time::Date) -> time::Angle {
time::Angle::from_degrees(0.5333333333) / self.distance(d)
}
pub fn magnitude(&self, d: time::Date) -> f64 {
5.0 * self.distance(d).log10() - 26.74
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Planet {
pub name: &'static str,
pub a: f64,
pub e: f64,
pub i: f64,
pub l: f64,
pub w: f64,
pub o: f64,
pub rates: [f64; 6],
pub extra: Option<(f64, f64, f64, f64)>,
pub theta0: time::Angle,
pub v0: f64,
}
impl Planet {
pub fn locationcart(&self, d: time::Date) -> (f64, f64, f64) {
let t = d.centuries();
let a = self.a + self.rates[0] * t;
let e = self.e + self.rates[1] * t;
let i = time::Angle::from_degrees(self.i + self.rates[2] * t);
let l = time::Angle::from_degrees(self.l + self.rates[3] * t);
let w = time::Angle::from_degrees(self.w + self.rates[4] * t);
let o = time::Angle::from_degrees(self.o + self.rates[5] * t);
let ww = w - o;
let mut m = (l - w).degrees();
if let Some((b, c, s, f)) = self.extra {
m = m + b * t * t + c * ((f * t).to_radians().cos()) + s * ((f * t).to_radians().sin());
}
m = time::Angle::from_degrees(m).to_latitude().degrees();
fn kepler(m: f64, e: f64, ee: f64) -> f64 {
let dm = m - (ee - e.to_degrees() * (ee.to_radians().sin()));
dm / (1.0 - e * (ee.to_radians()).cos())
}
let mut ee = m + 57.29578 * e * (m.to_radians().sin());
let mut de: f64 = 1.0;
while de.abs() > 1e-7 {
de = kepler(m, e, ee);
ee += de;
}
let xp = a * ((ee.to_radians()).cos() - e);
let yp = a * (1.0 - e * e).sqrt() * (ee.to_radians().sin());
let xecl = (ww.cos() * o.cos() - ww.sin() * o.sin() * i.cos()) * xp
+ (-ww.sin() * o.cos() - ww.cos() * o.sin() * i.cos()) * yp;
let yecl = (ww.cos() * o.sin() + ww.sin() * o.cos() * i.cos()) * xp
+ (-ww.sin() * o.sin() + ww.cos() * o.cos() * i.cos()) * yp;
let zecl = (ww.sin() * i.sin()) * xp + (ww.cos() * i.sin()) * yp;
let eps = 23.43928_f64.to_radians();
let tx = xecl;
let ty = eps.cos() * yecl - eps.sin() * zecl;
let tz = eps.sin() * yecl + eps.cos() * zecl;
(tx, ty, tz)
}
pub fn location(&self, d: time::Date) -> coord::Coord {
let c = self.locationcart(d);
let e = EARTH.locationcart(d);
coord::Coord::from_cartesian(c.0 - e.0, c.1 - e.1, c.2 - e.2)
}
pub fn distance(&self, d: time::Date) -> f64 {
let c = self.locationcart(d);
let e = EARTH.locationcart(d);
let (tx, ty, tz) = (c.0 - e.0, c.1 - e.1, c.2 - e.2);
(tx * tx + ty * ty + tz * tz).sqrt()
}
pub fn angdia(&self, d: time::Date) -> time::Angle {
self.theta0 / self.distance(d)
}
fn sun_distance(&self, d: time::Date) -> f64 {
let (tx, ty, tz) = self.locationcart(d);
(tx * tx + ty * ty + tz * tz).sqrt()
}
pub fn magnitude(&self, d: time::Date) -> f64 {
5.0 * ((self.distance(d) * self.sun_distance(d)) / self.illumfrac(d).sqrt()).log10()
+ self.v0
}
pub fn phaseangle(&self, d: time::Date) -> time::Angle {
let sep = SUN.location(d).dist(self.location(d));
let (tx, ty, tz) = self.locationcart(d);
let sp = (tx * tx + ty * ty + tz * tz).sqrt();
let upa = time::Angle::asin(SUN.distance(d) * (sep.sin() / sp));
if (tx * tx + ty * ty + tz * tz).sqrt() < 1.0 {
upa
} else {
upa + time::Angle::from_degrees(180.0)
}
}
pub fn illumfrac(&self, d: time::Date) -> f64 {
0.5 * (1.0 - self.phaseangle(d).cos())
}
}
pub const MERCURY: Planet = Planet {
name: "Mercury",
a: 0.38709927,
e: 0.20563593,
i: 7.00497902,
l: 252.25032350,
w: 77.45779628,
o: 48.33076593,
rates: [
0.00000037,
0.00001906,
-0.00594749,
149472.67411175,
0.16047689,
-0.12534081,
],
extra: None,
theta0: time::Angle::from_degrees(0.0017972222),
v0: -0.42,
};
pub const VENUS: Planet = Planet {
name: "Venus",
a: 0.72333566,
e: 0.00677672,
i: 3.39467605,
l: 181.97909950,
w: 131.60246718,
o: 76.67984255,
rates: [
0.00000390,
-0.00004107,
-0.00078890,
58517.81538729,
0.00268329,
-0.27769418,
],
extra: None,
theta0: time::Angle::from_degrees(0.0047),
v0: -4.4,
};
pub const EARTH: Planet = Planet {
name: "Earth",
a: 1.00000261,
e: 0.01671123,
i: -0.00001531,
l: 100.46457166,
w: 102.93768193,
o: 0.0,
rates: [
0.00000562,
-0.00004392,
-0.01294668,
35999.37244981,
0.32327364,
0.0,
],
extra: None,
theta0: time::Angle::from_degrees(180.0),
v0: -12.0,
};
pub const MARS: Planet = Planet {
name: "Mars",
a: 1.52371034,
e: 0.09339410,
i: 1.84969142,
l: -4.55343205,
w: -23.94362959,
o: 49.55953891,
rates: [
0.00001847,
0.00007882,
-0.00813131,
19140.30268499,
0.44441088,
-0.29257343,
],
extra: None,
theta0: time::Angle::from_degrees(0.0026),
v0: -1.52,
};
pub const JUPITER: Planet = Planet {
name: "Jupiter",
a: 5.20248019,
e: 0.04853590,
i: 1.29861416,
l: 34.33479152,
w: 14.27495244,
o: 100.29282654,
rates: [
-0.00002864,
0.00018026,
-0.00322699,
3034.90371757,
0.18199196,
0.13024619,
],
extra: Some((-0.00012452, 0.06064060, -0.35635438, 38.35125000)),
theta0: time::Angle::from_degrees(0.05465),
v0: -9.4,
};
pub const SATURN: Planet = Planet {
name: "Saturn",
a: 9.54149883,
e: 0.05550825,
i: 2.49424102,
l: 50.07571329,
w: 92.86136063,
o: 113.63998702,
rates: [
-0.00003065,
-0.00032044,
0.00451969,
1222.11494724,
0.54179478,
-0.25015002,
],
extra: Some((0.00025899, -0.13434469, 0.87320147, 38.35125000)),
theta0: time::Angle::from_degrees(0.046),
v0: -8.9,
};
pub const URANUS: Planet = Planet {
name: "Uranus",
a: 19.18797948,
e: 0.04685740,
i: 0.77298127,
l: 314.20276625,
w: 172.43404441,
o: 73.96250215,
rates: [
-0.00020455,
-0.00001550,
-0.00180155,
428.49512595,
0.09266985,
0.05739699,
],
extra: Some((0.00058331, -0.97731848, 0.17689245, 7.67025000)),
theta0: time::Angle::from_degrees(0.0182777777),
v0: -7.19,
};
pub const NEPTUNE: Planet = Planet {
name: "Neptune",
a: 30.06952752,
e: 0.00895439,
i: 1.77005520,
l: 304.22289287,
w: 46.68158724,
o: 131.78635853,
rates: [
0.00006447,
0.00000818,
0.00022400,
218.46515314,
0.01009938,
-0.00606302,
],
extra: Some((-0.00041348, 0.68346318, -0.10162547, 7.67025000)),
theta0: time::Angle::from_degrees(0.0172777777),
v0: -6.87,
};
pub const PLUTO: Planet = Planet {
name: "Pluto",
a: 39.48686035,
e: 0.24885238,
i: 17.14104260,
l: 238.96535011,
w: 224.09702598,
o: 110.30167986,
rates: [
0.00449751,
0.00006016,
0.00000501,
145.18042903,
-0.00968827,
-0.00809981,
],
extra: None,
theta0: time::Angle::from_degrees(0.0022777777),
v0: -1.0,
};
pub const PLANETS: [&Planet; 9] = [
&MERCURY, &VENUS, &EARTH, &MARS, &JUPITER, &SATURN, &URANUS, &NEPTUNE, &PLUTO,
];
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sunpos() {
assert_eq!(
SUN.location(time::Date::from_julian(2268932.541667)),
coord::Coord::from_equatorial(
time::Angle::from_degminsec(298, 29, 42.42),
time::Angle::from_degminsec(-21, 4, 0.91664)
)
);
}
#[test]
fn test_lambdasun() {
assert_eq!(
SUN.location(time::Date::from_calendar(
1980,
7,
27,
time::Angle::default()
))
.ecliptic(time::Date::from_calendar(
1980,
7,
27,
time::Angle::default()
))
.0,
time::Angle::from_degminsec(124, 23, 40.8)
)
}
#[test]
fn test_planet() {
assert_eq!(
VENUS.location(time::Date::from_calendar(
2025,
3,
12,
time::Angle::default()
)),
coord::Coord::from_equatorial(
time::Angle::from_clock(0, 17, 44.5),
time::Angle::from_degminsec(10, 54, 50.7)
)
);
assert_eq!(
JUPITER.location(time::Date::from_julian(2460748.41871)),
coord::Coord::from_equatorial(
time::Angle::from_clock(4, 47, 10.5),
time::Angle::from_degminsec(22, 01, 7.7)
)
);
assert_eq!(
MARS.distance(time::Date::from_julian(2460748.41871)),
0.9721731869765856
);
assert_eq!(
JUPITER.distance(time::Date::from_julian(2460748.41871)),
5.183932727328779
);
}
#[test]
fn test_phase() {
assert_eq!(
VENUS.illumfrac(time::Date::from_calendar(
2025,
03,
24,
time::Angle::default()
)),
0.010520980535268565
);
assert_eq!(
MARS.illumfrac(time::Date::from_calendar(
2025,
03,
24,
time::Angle::default()
)),
0.9103262022711702
);
assert_eq!(
VENUS.illumfrac(time::Date::from_calendar(
1996,
07,
22,
time::Angle::default()
)),
0.30982782608980997
);
}
}