use crate::{NsbError, Result};
use siderust::atmosphere::{
rayleigh_optical_depth_bodhaine99, AtmosphereProfile, MieParams, DEFAULT_SCALE_HEIGHT,
};
use siderust::coordinates::centers::Geodetic;
use siderust::coordinates::frames::ECEF;
use siderust::qtty::{Hectopascals, Kilometers, Nanometers, OpticalDepths};
#[derive(Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub struct AtmosphericConditions {
pub surface_pressure: Hectopascals,
pub rayleigh_scale_height: Kilometers,
pub mie_params: MieParams,
}
pub(crate) fn rayleigh_optical_depth_local_pressure(
wavelength: Nanometers,
atmosphere: AtmosphericConditions,
) -> OpticalDepths {
rayleigh_optical_depth_bodhaine99(
wavelength,
atmosphere.surface_pressure,
Kilometers::new(0.0),
atmosphere.rayleigh_scale_height,
)
}
impl AtmosphericConditions {
pub fn from_profile_without_altitude(profile: AtmosphereProfile) -> Self {
Self {
surface_pressure: profile.surface_pressure,
rayleigh_scale_height: profile.rayleigh_scale_height,
mie_params: profile.mie_params,
}
}
pub fn generic_clear_sky(location: Geodetic<ECEF>) -> Self {
let altitude_m = location.height.value().max(0.0);
let pressure = 1013.25 * (-altitude_m / 8_400.0).exp();
Self {
surface_pressure: Hectopascals::new(pressure),
rayleigh_scale_height: DEFAULT_SCALE_HEIGHT,
mie_params: MieParams::PARANAL,
}
}
pub fn clear_sky_with_pressure(surface_pressure: Hectopascals) -> Result<Self> {
let conditions = Self {
surface_pressure,
rayleigh_scale_height: DEFAULT_SCALE_HEIGHT,
mie_params: MieParams::PARANAL,
};
conditions.validate()?;
Ok(conditions)
}
pub fn paranal_average() -> Self {
Self::from_profile_without_altitude(AtmosphereProfile::EL_PARANAL)
}
pub(crate) fn validate(&self) -> Result<()> {
validate_positive_finite(self.surface_pressure.value(), "surface pressure")?;
validate_positive_finite(self.rayleigh_scale_height.value(), "Rayleigh scale height")?;
let mie = self.mie_params;
if !mie.tau0.value().is_finite() || mie.tau0.value() < 0.0 {
return Err(NsbError::OutOfRange(
"Mie optical depth must be finite and nonnegative".to_owned(),
));
}
if !mie.alpha.is_finite() {
return Err(NsbError::OutOfRange(
"Mie wavelength exponent must be finite".to_owned(),
));
}
validate_positive_finite(mie.lambda_ref.value(), "Mie reference wavelength")
}
}
fn validate_positive_finite(value: f64, field: &str) -> Result<()> {
if !value.is_finite() || value <= 0.0 {
return Err(NsbError::OutOfRange(format!(
"{field} must be finite and greater than zero"
)));
}
Ok(())
}