use crate::Eulumdat;
use std::f64::consts::PI;
pub struct TypeBConversion;
impl TypeBConversion {
pub fn type_c_to_type_b(c_deg: f64, gamma_deg: f64) -> (f64, f64) {
let c = c_deg.to_radians();
let gamma = gamma_deg.to_radians();
let sin_gamma = gamma.sin();
let cos_gamma = gamma.cos();
let v = (sin_gamma * c.cos()).asin();
let h = sin_gamma.mul_add(c.sin(), 0.0).atan2(cos_gamma);
(h.to_degrees(), v.to_degrees())
}
pub fn type_b_to_type_c(h_deg: f64, v_deg: f64) -> (f64, f64) {
let h = h_deg.to_radians();
let v = v_deg.to_radians();
let cos_v = v.cos();
let cos_h = h.cos();
let gamma = (cos_v * cos_h).acos();
let mut c = h.sin().atan2(v.sin() * cos_h);
if c < 0.0 {
c += 2.0 * PI;
}
(c.to_degrees(), gamma.to_degrees())
}
pub fn intensity_at_type_b(ldt: &Eulumdat, h_deg: f64, v_deg: f64) -> f64 {
let (c_deg, gamma_deg) = Self::type_b_to_type_c(h_deg, v_deg);
ldt.sample(c_deg, gamma_deg)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_roundtrip_c_to_b_to_c() {
let test_cases = [
(0.0, 0.0), (0.0, 45.0), (0.0, 90.0), (90.0, 45.0), (180.0, 60.0), (270.0, 30.0), ];
for (c_orig, gamma_orig) in &test_cases {
let (h, v) = TypeBConversion::type_c_to_type_b(*c_orig, *gamma_orig);
let (c_back, gamma_back) = TypeBConversion::type_b_to_type_c(h, v);
let c_norm_orig = c_orig % 360.0;
let c_norm_back = c_back % 360.0;
assert!(
(gamma_back - gamma_orig).abs() < 1e-10,
"Gamma roundtrip failed for C={}, γ={}: got γ={}",
c_orig,
gamma_orig,
gamma_back
);
if *gamma_orig > 1e-10 {
assert!(
(c_norm_back - c_norm_orig).abs() < 1e-10
|| (c_norm_back - c_norm_orig).abs() > 359.999,
"C roundtrip failed for C={}, γ={}: got C={}",
c_orig,
gamma_orig,
c_back
);
}
}
}
#[test]
fn test_roundtrip_b_to_c_to_b() {
let exact_cases = [
(0.0, 0.0), (30.0, 0.0), (0.0, 30.0), ];
for (h_orig, v_orig) in &exact_cases {
let (c, gamma) = TypeBConversion::type_b_to_type_c(*h_orig, *v_orig);
let (h_back, v_back) = TypeBConversion::type_c_to_type_b(c, gamma);
assert!(
(h_back - h_orig).abs() < 1e-10,
"H roundtrip failed for H={}, V={}: got H={}",
h_orig,
v_orig,
h_back
);
assert!(
(v_back - v_orig).abs() < 1e-10,
"V roundtrip failed for H={}, V={}: got V={}",
h_orig,
v_orig,
v_back
);
}
let off_axis_cases = [
(-45.0, 20.0), (60.0, -30.0), ];
for (h_orig, v_orig) in &off_axis_cases {
let (c, gamma) = TypeBConversion::type_b_to_type_c(*h_orig, *v_orig);
assert!(
(0.0..=180.0).contains(&gamma),
"gamma out of range for H={}, V={}: {}",
h_orig,
v_orig,
gamma
);
assert!(
(0.0..360.0).contains(&c),
"C out of range for H={}, V={}: {}",
h_orig,
v_orig,
c
);
}
}
#[test]
fn test_nadir_maps_to_origin() {
let (h, v) = TypeBConversion::type_c_to_type_b(0.0, 0.0);
assert!(h.abs() < 1e-10, "H should be 0 at nadir, got {}", h);
assert!(v.abs() < 1e-10, "V should be 0 at nadir, got {}", v);
}
#[test]
fn test_horizontal_front() {
let (h, v) = TypeBConversion::type_c_to_type_b(0.0, 90.0);
assert!((v - 90.0).abs() < 1e-10, "V should be 90°, got {}", v);
assert!(h.abs() < 1e-10, "H should be 0°, got {}", h);
}
#[test]
fn test_intensity_at_type_b() {
let ldt = Eulumdat {
c_angles: vec![0.0, 90.0, 180.0, 270.0],
g_angles: vec![0.0, 45.0, 90.0],
intensities: vec![
vec![100.0, 80.0, 20.0],
vec![100.0, 70.0, 15.0],
vec![100.0, 80.0, 20.0],
vec![100.0, 70.0, 15.0],
],
..Default::default()
};
let i = TypeBConversion::intensity_at_type_b(&ldt, 0.0, 0.0);
assert!(
(i - 100.0).abs() < 1.0,
"Nadir intensity should be ~100, got {}",
i
);
}
}