use pyo3::prelude::*;
use siderust::bodies::catalog;
use siderust::bodies::Star;
use siderust::coordinates::spherical::direction;
use siderust::qtty::*;
use siderust::time::ModifiedJulianDate;
use siderust::AltitudeProvider;
use siderust::AzimuthProvider;
use crate::errors::unknown_star_error;
use crate::observer::PyObserver;
#[pyclass(name = "Star", module = "siderust", from_py_object)]
#[derive(Clone)]
pub struct PyStar {
pub(crate) inner: Star<'static>,
}
impl PyStar {
pub fn from_inner(inner: Star<'static>) -> Self {
Self { inner }
}
}
fn lookup_star(name: &str) -> Option<Star<'static>> {
let lower = name.to_lowercase();
match lower.as_str() {
"sirius" => Some(catalog::SIRIUS.clone()),
"vega" => Some(catalog::VEGA.clone()),
"polaris" => Some(catalog::POLARIS.clone()),
"canopus" => Some(catalog::CANOPUS.clone()),
"arcturus" => Some(catalog::ARCTURUS.clone()),
"rigel" => Some(catalog::RIGEL.clone()),
"betelgeuse" => Some(catalog::BETELGEUSE.clone()),
"procyon" => Some(catalog::PROCYON.clone()),
"aldebaran" => Some(catalog::ALDEBARAN.clone()),
"altair" => Some(catalog::ALTAIR.clone()),
_ => None,
}
}
#[pymethods]
impl PyStar {
#[staticmethod]
fn catalog(name: &str) -> PyResult<Self> {
lookup_star(name)
.map(Self::from_inner)
.ok_or_else(|| unknown_star_error(name))
}
#[staticmethod]
fn from_ra_dec(name: &str, ra_deg: f64, dec_deg: f64) -> Self {
use siderust::coordinates::centers::Geocentric;
use siderust::coordinates::frames::EquatorialMeanJ2000;
use siderust::targets::CoordinateWithPM;
let pos = affn::spherical::Position::<Geocentric, EquatorialMeanJ2000, LightYear>::new(
Degrees::new(ra_deg),
Degrees::new(dec_deg),
LightYears::new(1.0), );
let coord = CoordinateWithPM::new_static(pos, siderust::J2000);
Self {
inner: Star::new(
name.to_string(),
LightYears::new(1.0),
SolarMasses::new(1.0),
siderust::qtty::length::nominal::SolarRadiuses::new(1.0),
SolarLuminosities::new(1.0),
coord,
),
}
}
#[getter]
fn name(&self) -> &str {
&self.inner.name
}
#[getter]
fn distance_ly(&self) -> f64 {
self.inner.distance.value()
}
#[getter]
fn mass_solar(&self) -> f64 {
self.inner.mass.value()
}
#[getter]
fn luminosity_solar(&self) -> f64 {
self.inner.luminosity.value()
}
#[getter]
fn ra_deg(&self) -> f64 {
let icrs: direction::ICRS = (&self.inner).into();
icrs.azimuth.to::<Degree>().value()
}
#[getter]
fn dec_deg(&self) -> f64 {
let icrs: direction::ICRS = (&self.inner).into();
icrs.polar.to::<Degree>().value()
}
fn altitude_at(&self, observer: &PyObserver, mjd: f64) -> f64 {
self.inner
.altitude_at(&observer.inner, ModifiedJulianDate::new(mjd))
.to::<Degree>()
.value()
}
fn azimuth_at(&self, observer: &PyObserver, mjd: f64) -> f64 {
self.inner
.azimuth_at(&observer.inner, ModifiedJulianDate::new(mjd))
.to::<Degree>()
.value()
}
fn track(&self, jd: f64) -> crate::target::PyTarget {
crate::target::track_star(self, jd)
}
fn __repr__(&self) -> String {
format!("Star('{}')", self.inner.name)
}
fn __str__(&self) -> String {
format!(
"{} (RA={:.4}°, Dec={:.4}°, d={:.2} ly)",
self.inner.name,
self.ra_deg(),
self.dec_deg(),
self.inner.distance.value()
)
}
fn __eq__(&self, other: &PyStar) -> bool {
self.inner.name == other.inner.name
}
fn __hash__(&self) -> u64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
self.inner.name.hash(&mut hasher);
hasher.finish()
}
fn to_dict<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, pyo3::types::PyDict>> {
let d = pyo3::types::PyDict::new(py);
d.set_item("name", self.name())?;
d.set_item("ra_deg", self.ra_deg())?;
d.set_item("dec_deg", self.dec_deg())?;
d.set_item("distance_ly", self.distance_ly())?;
d.set_item("mass_solar", self.mass_solar())?;
d.set_item("luminosity_solar", self.luminosity_solar())?;
Ok(d)
}
#[staticmethod]
fn from_dict(d: &Bound<'_, pyo3::types::PyDict>) -> PyResult<Self> {
let name: String = d
.get_item("name")?
.ok_or_else(|| pyo3::exceptions::PyValueError::new_err("missing 'name'"))?
.extract()?;
let ra: f64 = d
.get_item("ra_deg")?
.ok_or_else(|| pyo3::exceptions::PyValueError::new_err("missing 'ra_deg'"))?
.extract()?;
let dec: f64 = d
.get_item("dec_deg")?
.ok_or_else(|| pyo3::exceptions::PyValueError::new_err("missing 'dec_deg'"))?
.extract()?;
Ok(Self::from_ra_dec(&name, ra, dec))
}
}