use core::f64::consts::TAU;
use affn::cartesian::{Direction, Position, Velocity};
use affn::{ReferenceCenter, ReferenceFrame};
use qtty::angular::Radians;
use qtty::length::Meter;
use qtty::time::{Second, Seconds};
use qtty::{Per, Quantity};
type MeterPerSecond = Per<Meter, Second>;
pub fn beta_angle<C, F>(
sat_pos: &Position<C, F, Meter>,
sat_vel: &Velocity<F, MeterPerSecond>,
sun_dir: &Direction<F>,
) -> Radians
where
C: ReferenceCenter,
F: ReferenceFrame,
{
let r_hat = sat_pos.direction_unchecked();
let v_hat = Direction::<F>::new(
sat_vel.x().value(),
sat_vel.y().value(),
sat_vel.z().value(),
);
let h = r_hat.cross(&v_hat).expect("non-degenerate orbit");
let sb = h.dot(sun_dir).clamp(-1.0, 1.0);
Quantity::new(sb.asin())
}
pub fn local_solar_time(sub_sat_lon: Radians, sun_lon: Radians) -> Seconds {
let diff = (sub_sat_lon - sun_lon).rem_euclid(TAU).value();
let hours: f64 = 12.0 + diff * 24.0 / TAU;
let h = hours.rem_euclid(24.0);
Quantity::new(h * 3600.0)
}
pub fn ltan(raan: Radians, sun_ra: Radians) -> Seconds {
let diff = (raan - sun_ra).rem_euclid(TAU).value();
let hours: f64 = (12.0 + diff * 24.0 / TAU).rem_euclid(24.0);
Quantity::new(hours * 3600.0)
}
#[cfg(test)]
mod tests {
use super::MeterPerSecond;
use super::*;
use affn::cartesian::{Direction, Position, Velocity};
use affn::DeriveReferenceCenter;
use affn::DeriveReferenceFrame;
use approx::assert_abs_diff_eq;
use core::f64::consts::PI;
use qtty::angular::Radians;
use qtty::length::Meter;
use qtty::Quantity;
#[derive(Debug, Clone, Copy, DeriveReferenceCenter)]
struct C;
#[derive(Debug, Clone, Copy, DeriveReferenceFrame)]
struct F;
type P = Position<C, F, Meter>;
type V = Velocity<F, MeterPerSecond>;
fn pos(x: f64, y: f64, z: f64) -> P {
P::new(x, y, z)
}
fn vel(x: f64, y: f64, z: f64) -> V {
V::new(x, y, z)
}
fn rad(x: f64) -> Radians {
Quantity::new(x)
}
#[test]
fn beta_angle_in_plane_is_zero() {
let r = pos(7e6, 0.0, 0.0);
let v = vel(0.0, 7500.0, 0.0);
let s = Direction::<F>::new(1.0, 0.0, 0.0);
let beta = beta_angle(&r, &v, &s);
assert_abs_diff_eq!(beta.value(), 0.0, epsilon = 1e-12);
}
#[test]
fn beta_angle_perpendicular_is_pi_over_2() {
let r = pos(7e6, 0.0, 0.0);
let v = vel(0.0, 7500.0, 0.0);
let s = Direction::<F>::new(0.0, 0.0, 1.0);
let beta = beta_angle(&r, &v, &s);
assert_abs_diff_eq!(beta.value(), PI / 2.0, epsilon = 1e-12);
}
#[test]
fn local_solar_time_noon_at_subsolar() {
let t = local_solar_time(rad(1.234), rad(1.234));
assert_abs_diff_eq!(t.value(), 12.0 * 3600.0, epsilon = 1e-6);
}
#[test]
fn local_solar_time_wraps_around_midnight() {
let t = local_solar_time(rad(0.0), rad(PI));
let h = t.value() / 3600.0;
assert!(h < 1e-9 || (h - 24.0).abs() < 1e-9);
}
#[test]
fn ltan_offset_progresses_with_raan() {
let t1 = ltan(rad(0.0), rad(0.0));
let t2 = ltan(rad(PI / 2.0), rad(0.0));
assert_abs_diff_eq!(t2.value() - t1.value(), 6.0 * 3600.0, epsilon = 1e-6);
}
}