use crate::atmosphere::ScatteringFactor;
use crate::ext_qtty::pressure::Hectopascals;
use crate::ext_qtty::Quantity;
use crate::qtty::unit::Micrometer;
use crate::qtty::{Kilometers, Nanometers, OpticalDepths, Radians};
pub const DEFAULT_SCALE_HEIGHT: Kilometers = Kilometers::new(8.0);
const SEA_LEVEL_PRESSURE: Hectopascals = Hectopascals::new(1013.25);
pub fn rayleigh_optical_depth_bodhaine99(
wavelength: Nanometers,
surface_pressure: Hectopascals,
observer_altitude: Kilometers,
scale_height: Kilometers,
) -> OpticalDepths {
let lam_um = wavelength.to::<Micrometer>().value();
let p_atm: f64 = surface_pressure / SEA_LEVEL_PRESSURE;
let h_km = observer_altitude.value();
let scale_km = scale_height.value();
let l2 = lam_um * lam_um;
let inv_l2 = 1.0 / l2;
let tau_sea = 0.0021520 * (1.0455996 - 341.29061 * inv_l2 - 0.90230850 * l2)
/ (1.0 + 0.0027059889 * inv_l2 - 85.968563 * l2);
let height_factor = (-h_km / scale_km).exp();
OpticalDepths::new(p_atm * tau_sea * height_factor)
}
#[inline]
pub fn rayleigh_phase(theta: Radians) -> Quantity<ScatteringFactor> {
let c = theta.cos();
Quantity::<ScatteringFactor>::new(3.0 / (16.0 * core::f64::consts::PI) * (1.0 + c * c))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn matches_nsb_at_550nm_paranal_like() {
let tau = rayleigh_optical_depth_bodhaine99(
Nanometers::new(550.0),
Hectopascals::new(779.0),
Kilometers::new(2.4),
DEFAULT_SCALE_HEIGHT,
);
let v = tau.value();
assert!(v > 0.05 && v < 0.15, "tau={v}");
}
#[test]
fn phase_at_90deg_is_3_over_16pi() {
let p = rayleigh_phase(Radians::new(core::f64::consts::FRAC_PI_2));
let expected = 3.0 / (16.0 * core::f64::consts::PI);
assert!((p.value() - expected).abs() < 1e-15);
}
#[test]
fn phase_at_0deg_is_double_90deg() {
let p_0 = rayleigh_phase(Radians::new(0.0));
let p_90 = rayleigh_phase(Radians::new(core::f64::consts::FRAC_PI_2));
assert!((p_0.value() - 2.0 * p_90.value()).abs() < 1e-15);
}
}