use crate::error::{Result, AstroError};
pub fn refraction_bennett(altitude_deg: f64) -> Result<f64> {
if !(-90.0..=90.0).contains(&altitude_deg) {
return Err(AstroError::OutOfRange {
parameter: "altitude",
value: altitude_deg,
min: -90.0,
max: 90.0,
});
}
if altitude_deg < -0.5 {
return Ok(0.0); }
let h = altitude_deg;
let r_arcmin = 1.0 / ((h + 7.31 / (h + 4.4)).to_radians().tan());
Ok(r_arcmin / 60.0)
}
pub fn refraction_saemundsson(altitude_deg: f64, pressure_hpa: f64, temperature_c: f64) -> Result<f64> {
if !(-90.0..=90.0).contains(&altitude_deg) {
return Err(AstroError::OutOfRange {
parameter: "altitude",
value: altitude_deg,
min: -90.0,
max: 90.0,
});
}
if altitude_deg < -1.0 {
return Ok(0.0);
}
let h = altitude_deg;
let r_arcmin = 1.02 / ((h + 10.3 / (h + 5.11)).to_radians().tan());
let p_factor = pressure_hpa / 1010.0;
let t_factor = 283.0 / (273.0 + temperature_c);
Ok(r_arcmin * p_factor * t_factor / 60.0)
}
pub fn refraction_radio(
altitude_deg: f64,
pressure_hpa: f64,
temperature_c: f64,
humidity_percent: f64,
) -> Result<f64> {
if !(-90.0..=90.0).contains(&altitude_deg) {
return Err(AstroError::OutOfRange {
parameter: "altitude",
value: altitude_deg,
min: -90.0,
max: 90.0,
});
}
if !(0.0..=100.0).contains(&humidity_percent) {
return Err(AstroError::OutOfRange {
parameter: "humidity_percent",
value: humidity_percent,
min: 0.0,
max: 100.0,
});
}
if altitude_deg < -1.0 {
return Ok(0.0);
}
let es = 6.105 * (17.27 * temperature_c / (237.7 + temperature_c)).exp();
let e = humidity_percent / 100.0 * es;
let n_dry = 77.6 * pressure_hpa / (273.15 + temperature_c);
let n_wet = 3.73e5 * e / (273.15 + temperature_c).powi(2);
let n = n_dry + n_wet;
let cot_h = 1.0 / altitude_deg.to_radians().tan();
let r_arcsec = n * cot_h / 1e6 * 206265.0;
Ok(r_arcsec / 3600.0)
}
pub fn apparent_to_true_altitude(
apparent_altitude_deg: f64,
pressure_hpa: f64,
temperature_c: f64,
) -> Result<f64> {
let refraction = refraction_saemundsson(apparent_altitude_deg, pressure_hpa, temperature_c)?;
Ok(apparent_altitude_deg - refraction)
}
pub fn true_to_apparent_altitude(
true_altitude_deg: f64,
pressure_hpa: f64,
temperature_c: f64,
) -> Result<f64> {
if !(-90.0..=90.0).contains(&true_altitude_deg) {
return Err(AstroError::OutOfRange {
parameter: "altitude",
value: true_altitude_deg,
min: -90.0,
max: 90.0,
});
}
let mut apparent = true_altitude_deg;
for _ in 0..5 {
let refraction = refraction_saemundsson(apparent, pressure_hpa, temperature_c)?;
apparent = true_altitude_deg + refraction;
}
Ok(apparent)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_refraction_bennett_horizon() {
let r = refraction_bennett(0.0).unwrap();
assert!(r > 0.55 && r < 0.60); }
#[test]
fn test_refraction_bennett_zenith() {
let r = refraction_bennett(90.0).unwrap();
assert!(r < 0.001);
}
#[test]
fn test_refraction_bennett_45deg() {
let r = refraction_bennett(45.0).unwrap();
assert!(r > 0.015 && r < 0.020); }
#[test]
fn test_refraction_saemundsson_standard() {
let r = refraction_saemundsson(10.0, 1013.25, 10.0).unwrap();
assert!(r > 0.08 && r < 0.10); }
#[test]
fn test_refraction_pressure_effect() {
let r_low = refraction_saemundsson(10.0, 980.0, 10.0).unwrap();
let r_high = refraction_saemundsson(10.0, 1040.0, 10.0).unwrap();
assert!(r_high > r_low);
}
#[test]
fn test_refraction_temperature_effect() {
let r_cold = refraction_saemundsson(10.0, 1013.25, -10.0).unwrap();
let r_hot = refraction_saemundsson(10.0, 1013.25, 30.0).unwrap();
assert!(r_cold > r_hot);
}
#[test]
fn test_altitude_conversion_roundtrip() {
let true_alt = 15.0;
let apparent = true_to_apparent_altitude(true_alt, 1013.25, 10.0).unwrap();
let back_to_true = apparent_to_true_altitude(apparent, 1013.25, 10.0).unwrap();
assert!((back_to_true - true_alt).abs() < 0.001);
}
#[test]
fn test_radio_refraction() {
let r_radio = refraction_radio(10.0, 1013.25, 20.0, 50.0).unwrap();
let r_optical = refraction_saemundsson(10.0, 1013.25, 20.0).unwrap();
assert!(r_radio > 0.0);
assert!(r_optical > 0.0);
assert!(r_radio > r_optical);
}
}