use super::calibration::AirglowContinuum;
use super::continuum::{
evaluate_continuum, evaluate_integrated_continuum_with_night_phase,
validate_integrated_continuum_inputs, AirglowEvaluationContext,
};
use super::domain::AirglowNightPhase;
use super::geometry::{target_altitude, AirglowGeometryModel, VanRhijnConfig};
use super::output::AirglowOutputs;
use super::units::{SolarFluxUnits, DEFAULT_SOLAR_RADIO_FLUX};
use crate::error::Result;
use crate::site::AtmosphericConditions;
use crate::units::radiometry::PhotonsPerSquareCentimeterNanosecondSteradian as BandPhotonRadiance;
use crate::units::ScaleFactors;
use siderust::coordinates::centers::Geodetic;
use siderust::coordinates::frames::{EquatorialMeanJ2000, ECEF};
use siderust::coordinates::spherical::Direction as SphericalDirection;
use std::sync::Arc;
use tempoch::{Time, UTC};
#[cfg_attr(
feature = "python",
pyo3::pyclass(
eq,
frozen,
rename_all = "SCREAMING_SNAKE_CASE",
module = "nsb",
from_py_object
)
)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum AirglowModel {
ParanalPalaceV1,
}
impl AirglowModel {
pub const fn as_str(self) -> &'static str {
match self {
Self::ParanalPalaceV1 => "paranal-palace-v1",
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct Airglow {
location: Geodetic<ECEF>,
continuum: Arc<AirglowContinuum>,
atmosphere: AtmosphericConditions,
geometry: AirglowGeometryModel,
solar_radio_flux: SolarFluxUnits,
scale: ScaleFactors,
}
impl Airglow {
pub(crate) fn with_shared_continuum(
location: Geodetic<ECEF>,
continuum: Arc<AirglowContinuum>,
) -> Self {
let geometry = AirglowGeometryModel::VanRhijn(VanRhijnConfig::from_continuum_height(
continuum.representative_emission_height_km(),
));
Self {
location,
continuum,
atmosphere: AtmosphericConditions::generic_clear_sky(location),
geometry,
solar_radio_flux: DEFAULT_SOLAR_RADIO_FLUX,
scale: ScaleFactors::new(1.0),
}
}
pub fn with_atmosphere(mut self, atmosphere: AtmosphericConditions) -> Self {
self.atmosphere = atmosphere;
self
}
pub fn with_geometry(mut self, geometry: AirglowGeometryModel) -> Self {
self.geometry = geometry;
self
}
pub fn with_solar_radio_flux(mut self, flux: SolarFluxUnits) -> Self {
self.solar_radio_flux = flux;
self
}
pub fn with_scale(mut self, scale: ScaleFactors) -> Self {
self.scale = scale;
self
}
pub fn compute(
&self,
time: Time<UTC>,
target: SphericalDirection<EquatorialMeanJ2000>,
) -> Result<AirglowOutputs> {
let altitude = target_altitude(time, self.location, target);
evaluate_continuum(
&self.continuum,
time,
altitude,
AirglowEvaluationContext {
location: self.location,
atmosphere: self.atmosphere,
geometry: self.geometry.clone(),
solar_radio_flux: self.solar_radio_flux,
user_scale: self.scale,
},
)
}
pub(crate) fn compute_integrated_with_night_phase(
&self,
time: Time<UTC>,
target: SphericalDirection<EquatorialMeanJ2000>,
phase: AirglowNightPhase,
solar_radio_flux: SolarFluxUnits,
) -> Result<BandPhotonRadiance> {
let altitude = target_altitude(time, self.location, target);
evaluate_integrated_continuum_with_night_phase(
&self.continuum,
time,
altitude,
AirglowEvaluationContext {
location: self.location,
atmosphere: self.atmosphere,
geometry: self.geometry.clone(),
solar_radio_flux,
user_scale: self.scale,
},
phase,
)
}
pub(crate) fn validate_inputs_for_query(
&self,
time: Time<UTC>,
target: SphericalDirection<EquatorialMeanJ2000>,
solar_radio_flux: SolarFluxUnits,
) -> Result<()> {
let altitude = target_altitude(time, self.location, target);
validate_integrated_continuum_inputs(
altitude,
&AirglowEvaluationContext {
location: self.location,
atmosphere: self.atmosphere,
geometry: self.geometry.clone(),
solar_radio_flux,
user_scale: self.scale,
},
)
}
}
#[cfg(test)]
pub(crate) mod test_support {
use super::super::calibration::load_builtin_standard;
use super::*;
use crate::site::SiteProfileConfig;
pub(crate) fn standard_clear_sky(location: Geodetic<ECEF>) -> Result<Airglow> {
let continuum = Arc::new(load_builtin_standard()?);
Ok(Airglow::with_shared_continuum(location, continuum)
.with_atmosphere(AtmosphericConditions::generic_clear_sky(location)))
}
pub(crate) fn for_site_profile(
location: Geodetic<ECEF>,
site_profile: &SiteProfileConfig,
) -> Result<Airglow> {
let profile = site_profile.resolve(location);
let continuum = Arc::new(load_builtin_standard()?);
Ok(Airglow::with_shared_continuum(location, continuum)
.with_atmosphere(profile.atmosphere)
.with_scale(profile.airglow.scale))
}
pub(crate) fn with_continuum(location: Geodetic<ECEF>, continuum: AirglowContinuum) -> Airglow {
Airglow::with_shared_continuum(location, Arc::new(continuum))
}
pub(crate) fn geometry(model: &Airglow) -> &AirglowGeometryModel {
&model.geometry
}
}