use super::direct::{validate_wavelength, DirectTransmission};
use super::extinction::OpticalDepthBreakdown;
use super::geometry::DirectPathGeometry;
use super::metadata::TransportModelMetadata;
use super::origin::RadianceOrigin;
use crate::error::{NsbError, Result};
use crate::site::AtmosphericConditions;
use crate::units::dimensionless::Transmittances;
use crate::units::radiometry::{
PhotonsPerSquareCentimeterNanosecondSteradianNanometer as PhotonSpectralRadiance,
WattsPerSquareMeterSteradianNanometer as EnergySpectralRadiance,
};
use siderust::qtty::{Nanometers, OpticalDepths};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum AirmassModel {
#[default]
Young1994,
KrisciunasSchaefer1991,
PlaneParallel,
Rozenberg1966,
}
impl AirmassModel {
pub const fn as_str(self) -> &'static str {
match self {
Self::Young1994 => "young-1994",
Self::KrisciunasSchaefer1991 => "krisciunas-schaefer-1991",
Self::PlaneParallel => "plane-parallel",
Self::Rozenberg1966 => "rozenberg-1966",
}
}
}
#[derive(Debug, Clone, PartialEq, Default)]
#[non_exhaustive]
pub enum TransportModel {
#[default]
Identity,
Direct(DirectTransmission),
}
impl TransportModel {
pub const fn identity() -> Self {
Self::Identity
}
pub const fn direct_rayleigh_mie() -> Self {
Self::Direct(DirectTransmission::rayleigh_mie_young1994())
}
pub const fn direct_rayleigh_mie_ozone() -> Self {
Self::Direct(DirectTransmission::rayleigh_mie_ozone_young1994())
}
pub const fn as_str(&self) -> &'static str {
match self {
Self::Identity => "identity",
Self::Direct(_) => "direct-transmission",
}
}
pub fn metadata(&self) -> TransportModelMetadata {
match self {
Self::Identity => TransportModelMetadata::identity(),
Self::Direct(model) => model.metadata(),
}
}
pub fn optical_depth(
&self,
wavelength: Nanometers,
atmosphere: AtmosphericConditions,
) -> Result<OpticalDepthBreakdown> {
match self {
Self::Identity => {
validate_wavelength(wavelength)?;
Ok(OpticalDepthBreakdown {
rayleigh: OpticalDepths::new(0.0),
mie: OpticalDepths::new(0.0),
absorption: OpticalDepths::new(0.0),
total: OpticalDepths::new(0.0),
})
}
Self::Direct(model) => model.optical_depth(wavelength, atmosphere),
}
}
pub fn transmission(
&self,
wavelength: Nanometers,
geometry: DirectPathGeometry,
atmosphere: AtmosphericConditions,
) -> Result<Transmittances> {
match self {
Self::Identity => {
validate_wavelength(wavelength)?;
Ok(Transmittances::new(1.0))
}
Self::Direct(model) => model.transmission(wavelength, geometry, atmosphere),
}
}
pub fn apply_energy_spectral(
&self,
origin: RadianceOrigin,
incident: EnergySpectralRadiance,
wavelength: Nanometers,
geometry: DirectPathGeometry,
atmosphere: AtmosphericConditions,
) -> Result<EnergySpectralRadiance> {
let scale = self.propagation_scale(origin, wavelength, geometry, atmosphere)?;
Ok(EnergySpectralRadiance::new(incident.value() * scale))
}
pub fn apply_photon_spectral(
&self,
origin: RadianceOrigin,
incident: PhotonSpectralRadiance,
wavelength: Nanometers,
geometry: DirectPathGeometry,
atmosphere: AtmosphericConditions,
) -> Result<PhotonSpectralRadiance> {
let scale = self.propagation_scale(origin, wavelength, geometry, atmosphere)?;
Ok(PhotonSpectralRadiance::new(incident.value() * scale))
}
fn ensure_origin_compatible(&self, origin: RadianceOrigin) -> Result<()> {
match self {
Self::Identity => Ok(()),
Self::Direct(_) => {
if origin.allows_celestial_direct_transmission() {
Ok(())
} else {
Err(NsbError::Unsupported(format!(
"transport model `{}` is a celestial top-of-atmosphere direct path; \
radiance origin `{}` requires a different propagation model \
(airglow in-atmosphere emission or legacy pre-scattered moonlight)",
self.as_str(),
origin.as_str()
)))
}
}
}
}
fn propagation_scale(
&self,
origin: RadianceOrigin,
wavelength: Nanometers,
geometry: DirectPathGeometry,
atmosphere: AtmosphericConditions,
) -> Result<f64> {
self.ensure_origin_compatible(origin)?;
match self {
Self::Identity => {
validate_wavelength(wavelength)?;
Ok(1.0)
}
Self::Direct(model) => model.scale_factor(wavelength, geometry, atmosphere),
}
}
}