use super::metadata::{BandDiagnostic, NsbComponentMetadata};
use crate::components::zodiacal;
use crate::components::{airglow, moonlight, starlight};
use crate::site::{
CalibrationStatus, GenericClearSky, SiteProfile, SiteProfileConfig, SiteProfileTag,
};
use crate::units::photometry::SurfaceBrightness;
use crate::units::radiometry::{
PhotonsPerSquareCentimeterNanosecondSteradian as BandPhotonRadiance, S10s as S10,
};
use siderust::coordinates::centers::Geodetic;
use siderust::coordinates::frames::{EquatorialMeanJ2000, ECEF};
use siderust::coordinates::spherical::Direction as SphericalDirection;
use tempoch::{Time, UTC};
bitflags::bitflags! {
#[cfg_attr(feature = "python", pyo3::pyclass(frozen, module = "nsb", from_py_object))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ComponentMask: u8 {
const ZODIACAL = 0b0001;
const STARLIGHT = 0b0010;
const AIRGLOW = 0b0100;
const MOON = 0b1000;
#[cfg(nsb_bundled_production_starlight)]
const DEFAULT = Self::ZODIACAL.bits()
| Self::STARLIGHT.bits()
| Self::AIRGLOW.bits()
| Self::MOON.bits();
#[cfg(not(nsb_bundled_production_starlight))]
const DEFAULT = Self::ZODIACAL.bits()
| Self::AIRGLOW.bits()
| Self::MOON.bits();
const ALL = Self::DEFAULT.bits();
}
}
pub type Observer = Geodetic<ECEF>;
pub type Target = SphericalDirection<EquatorialMeanJ2000>;
pub(crate) fn validate_geometry(observer: Observer, target: Target) -> crate::Result<()> {
if !observer.lon.is_finite()
|| !observer.lat.is_finite()
|| !observer.height.is_finite()
|| !target.ra().is_finite()
|| !target.dec().is_finite()
{
return Err(crate::NsbError::OutOfRange(
"observer and target coordinates must be finite".into(),
));
}
Ok(())
}
#[cfg_attr(
feature = "python",
pyo3::pyclass(frozen, module = "nsb", skip_from_py_object)
)]
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct PointQuery {
pub observer: Observer,
pub time: Time<UTC>,
pub target: Target,
pub components: ComponentMask,
}
impl PointQuery {
pub fn new(observer: Observer, time: Time<UTC>, target: Target) -> Self {
Self {
observer,
time,
target,
components: ComponentMask::ALL,
}
}
pub fn with_components(mut self, components: ComponentMask) -> Self {
self.components = components;
self
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct NsbComponent {
pub name: &'static str,
pub integrated: BandPhotonRadiance,
pub b_flux_s10: S10,
pub v_flux_s10: S10,
pub relative_uncertainty: Option<f64>,
pub statistical_uncertainty: Option<BandPhotonRadiance>,
pub systematic_uncertainty: Option<BandPhotonRadiance>,
pub total_uncertainty: Option<BandPhotonRadiance>,
pub metadata: NsbComponentMetadata,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct NsbComponentDescriptor {
pub name: &'static str,
pub metadata: NsbComponentMetadata,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct NsbResult {
pub integrated: BandPhotonRadiance,
pub b_mag: SurfaceBrightness,
pub v_mag: SurfaceBrightness,
pub components: Vec<NsbComponent>,
pub band_diagnostic: BandDiagnostic,
}
#[cfg_attr(
feature = "python",
pyo3::pyclass(frozen, module = "nsb", skip_from_py_object)
)]
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct NsbModelConfig {
moonlight_model: moonlight::MoonlightModel,
airglow_selection: airglow::AirglowSelection,
zodiacal_model: zodiacal::ZodiacalModel,
site_profile: SiteProfileConfig,
starlight_product: Option<starlight::StarlightProduct>,
solar_activity: crate::solar_activity::SolarActivitySource,
airglow_geometry: airglow::AirglowGeometryModel,
zodiacal_extinction: zodiacal::ZodiacalExtinction,
}
impl NsbModelConfig {
pub fn generic_clear_sky() -> Self {
Self {
moonlight_model: moonlight::MoonlightModel::Jones2013Spectral,
airglow_selection: airglow::AirglowSelection::Automatic,
zodiacal_model: zodiacal::ZodiacalModel::Leinert1998,
site_profile: SiteProfile::<GenericClearSky>::generic_clear_sky().erase(),
starlight_product: default_starlight_product(),
solar_activity: crate::solar_activity::SolarActivitySource::Automatic,
airglow_geometry: airglow::AirglowGeometryModel::default(),
zodiacal_extinction: zodiacal::ZodiacalExtinction::Noll2012Approx,
}
}
pub fn with_moonlight_model(mut self, model: moonlight::MoonlightModel) -> Self {
self.moonlight_model = model;
self
}
pub const fn moonlight_model(&self) -> moonlight::MoonlightModel {
self.moonlight_model
}
pub fn with_airglow_model(mut self, model: airglow::AirglowModel) -> Self {
self.airglow_selection = airglow::AirglowSelection::Explicit(model);
self
}
pub fn with_airglow_selection(mut self, selection: airglow::AirglowSelection) -> Self {
self.airglow_selection = selection;
self
}
pub const fn airglow_selection(&self) -> airglow::AirglowSelection {
self.airglow_selection
}
pub const fn airglow_model(&self) -> Option<airglow::AirglowModel> {
self.airglow_selection.requested_model()
}
pub fn with_zodiacal_model(mut self, model: zodiacal::ZodiacalModel) -> Self {
self.zodiacal_model = model;
self
}
pub const fn zodiacal_model(&self) -> zodiacal::ZodiacalModel {
self.zodiacal_model
}
pub fn with_zodiacal_extinction(mut self, extinction: zodiacal::ZodiacalExtinction) -> Self {
self.zodiacal_extinction = extinction;
self
}
pub const fn zodiacal_extinction(&self) -> zodiacal::ZodiacalExtinction {
self.zodiacal_extinction
}
pub fn with_site_profile<P: SiteProfileTag>(mut self, site_profile: SiteProfile<P>) -> Self {
self.site_profile = site_profile.erase();
self
}
pub(crate) fn site_profile(&self) -> &SiteProfileConfig {
&self.site_profile
}
pub fn site_profile_name(&self) -> &str {
self.site_profile.name()
}
pub const fn airglow_calibration_status(&self) -> CalibrationStatus {
self.site_profile.calibration_status()
}
pub const fn is_airglow_site_calibrated(&self) -> bool {
self.site_profile.is_site_calibrated()
}
pub fn with_starlight_product(
mut self,
starlight_product: starlight::StarlightProduct,
) -> Self {
self.starlight_product = Some(starlight_product);
self
}
pub fn starlight_product(&self) -> Option<&starlight::StarlightProduct> {
self.starlight_product.as_ref()
}
pub fn with_solar_radio_flux(mut self, flux: crate::units::SolarFluxUnits) -> Self {
self.solar_activity = crate::solar_activity::SolarActivitySource::Explicit(flux);
self
}
pub fn with_f107_store(
mut self,
store: std::sync::Arc<crate::solar_activity::F107Store>,
) -> Self {
self.solar_activity = crate::solar_activity::SolarActivitySource::Dataset(store);
self
}
pub fn solar_activity(&self) -> &crate::solar_activity::SolarActivitySource {
&self.solar_activity
}
pub fn with_airglow_geometry(mut self, geometry: airglow::AirglowGeometryModel) -> Self {
self.airglow_geometry = geometry;
self
}
pub fn airglow_geometry(&self) -> &airglow::AirglowGeometryModel {
&self.airglow_geometry
}
}
impl Default for NsbModelConfig {
fn default() -> Self {
Self::generic_clear_sky()
}
}
#[cfg(nsb_bundled_production_starlight)]
fn default_starlight_product() -> Option<starlight::StarlightProduct> {
Some(starlight::StarlightProduct::BundledProductionGaiaDr3)
}
#[cfg(not(nsb_bundled_production_starlight))]
fn default_starlight_product() -> Option<starlight::StarlightProduct> {
None
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct PreparedPointQuery {
pub(crate) observer: Observer,
pub(crate) target: Target,
pub(crate) components: ComponentMask,
}
#[cfg(test)]
pub(crate) mod test_support {
use super::NsbModelConfig;
pub(crate) fn without_starlight_product(mut config: NsbModelConfig) -> NsbModelConfig {
config.starlight_product = None;
config
}
}