use std::fmt::Display;
use crate::errors::PhysicsError;
use crate::frames::Frame;
use hifitime::{Duration, Epoch};
#[cfg(feature = "python")]
use pyo3::exceptions::PyTypeError;
#[cfg(feature = "python")]
use pyo3::prelude::*;
#[cfg(feature = "python")]
use pyo3::pyclass::CompareOp;
pub mod utils;
pub(crate) mod aberration;
pub use aberration::Aberration;
pub(crate) mod occultation;
pub use occultation::Occultation;
pub mod orbit;
pub mod orbit_equinoctial;
pub mod orbit_geodetic;
#[cfg(feature = "analysis")]
pub mod orbit_gradient;
pub mod orbit_mean_elements;
pub use crate::structure::location::{Location, TerrainMask};
pub type PhysicsResult<T> = Result<T, PhysicsError>;
#[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "python", pyclass)]
#[cfg_attr(feature = "python", pyo3(module = "anise.astro"))]
pub struct AzElRange {
pub epoch: Epoch,
pub azimuth_deg: f64,
pub elevation_deg: f64,
pub range_km: f64,
pub range_rate_km_s: f64,
pub mask_deg: Option<f64>,
pub obstructed_by: Option<Frame>,
pub light_time: Duration,
}
#[cfg_attr(feature = "python", pymethods)]
impl AzElRange {
pub fn is_valid(&self) -> bool {
self.azimuth_deg.is_finite() && self.elevation_deg.is_finite() && self.range_km > 1e-6
}
pub fn is_obstructed(&self) -> bool {
self.obstructed_by.is_some() || self.elevation_above_mask_deg() < 0.0
}
pub fn elevation_above_mask_deg(&self) -> f64 {
self.elevation_deg - self.mask_deg.unwrap_or(0.0)
}
}
#[cfg_attr(feature = "python", pymethods)]
#[cfg(feature = "python")]
impl AzElRange {
#[new]
#[pyo3(signature=(epoch, azimuth_deg, elevation_deg, range_km, range_rate_km_s, obstructed_by=None, mask_deg=None))]
pub fn py_new(
epoch: Epoch,
azimuth_deg: f64,
elevation_deg: f64,
range_km: f64,
range_rate_km_s: f64,
obstructed_by: Option<Frame>,
mask_deg: Option<f64>,
) -> Self {
use crate::constants::SPEED_OF_LIGHT_KM_S;
use hifitime::TimeUnits;
Self {
epoch,
azimuth_deg,
elevation_deg,
range_km,
range_rate_km_s,
obstructed_by,
mask_deg,
light_time: (range_km / SPEED_OF_LIGHT_KM_S).seconds(),
}
}
#[getter]
fn get_epoch(&self) -> PyResult<Epoch> {
Ok(self.epoch)
}
#[setter]
fn set_epoch(&mut self, epoch: Epoch) -> PyResult<()> {
self.epoch = epoch;
Ok(())
}
#[getter]
fn get_azimuth_deg(&self) -> PyResult<f64> {
Ok(self.azimuth_deg)
}
#[setter]
fn set_azimuth_deg(&mut self, azimuth_deg: f64) -> PyResult<()> {
self.azimuth_deg = azimuth_deg;
Ok(())
}
#[getter]
fn get_elevation_deg(&self) -> PyResult<f64> {
Ok(self.elevation_deg)
}
#[setter]
fn set_elevation_deg(&mut self, elevation_deg: f64) -> PyResult<()> {
self.elevation_deg = elevation_deg;
Ok(())
}
#[getter]
fn get_range_km(&self) -> PyResult<f64> {
Ok(self.range_km)
}
#[setter]
fn set_range_km(&mut self, range_km: f64) -> PyResult<()> {
use crate::constants::SPEED_OF_LIGHT_KM_S;
use hifitime::TimeUnits;
self.range_km = range_km;
self.light_time = (range_km / SPEED_OF_LIGHT_KM_S).seconds();
Ok(())
}
#[getter]
fn get_range_rate_km_s(&self) -> PyResult<f64> {
Ok(self.range_rate_km_s)
}
#[setter]
fn set_range_rate_km_s(&mut self, range_rate_km_s: f64) -> PyResult<()> {
self.range_rate_km_s = range_rate_km_s;
Ok(())
}
#[getter]
fn get_obstructed_by(&self) -> PyResult<Option<Frame>> {
Ok(self.obstructed_by)
}
#[setter]
fn set_obstructed_by(&mut self, obstructed_by: Option<Frame>) -> PyResult<()> {
self.obstructed_by = obstructed_by;
Ok(())
}
#[getter]
fn get_mask_deg(&self) -> PyResult<Option<f64>> {
Ok(self.mask_deg)
}
#[setter]
fn set_mask_deg(&mut self, mask_deg: Option<f64>) -> PyResult<()> {
self.mask_deg = mask_deg;
Ok(())
}
#[getter]
fn get_light_time(&self) -> PyResult<Duration> {
Ok(self.light_time)
}
fn __str__(&self) -> String {
format!("{self}")
}
fn __repr__(&self) -> String {
format!("{self} (@{self:p})")
}
fn __richcmp__(&self, other: &Self, op: CompareOp) -> Result<bool, PyErr> {
match op {
CompareOp::Eq => Ok(self == other),
CompareOp::Ne => Ok(self != other),
_ => Err(PyErr::new::<PyTypeError, _>(format!(
"{op:?} not available"
))),
}
}
#[allow(clippy::type_complexity)]
fn __getnewargs__(
&self,
) -> Result<(Epoch, f64, f64, f64, f64, Option<Frame>, Option<f64>), PyErr> {
Ok((
self.epoch,
self.azimuth_deg,
self.elevation_deg,
self.range_km,
self.range_rate_km_s,
self.obstructed_by,
self.mask_deg,
))
}
}
impl Display for AzElRange {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let obs = match self.obstructed_by {
None => "none".to_string(),
Some(frame) => format!("{frame:e}"),
};
write!(
f,
"{}: az.: {:.6} deg el.: {:.6} deg range: {:.6} km range-rate: {:.6} km/s obstruction: {}",
self.epoch, self.azimuth_deg, self.elevation_deg, self.range_km, self.range_rate_km_s, obs
)
}
}