use core::fmt;
use core::str::FromStr;
use crate::constellation_data::ZONES;
use crate::coords::{precess, Epoch, Equatorial};
use crate::error::Error;
const B1875_JULIAN_YEAR: f64 = 1875.0013923;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Constellation {
And,
Ant,
Aps,
Aqr,
Aql,
Ara,
Ari,
Aur,
Boo,
Cae,
Cam,
Cnc,
CVn,
CMa,
CMi,
Cap,
Car,
Cas,
Cen,
Cep,
Cet,
Cha,
Cir,
Col,
Com,
CrA,
CrB,
Crv,
Crt,
Cru,
Cyg,
Del,
Dor,
Dra,
Equ,
Eri,
For,
Gem,
Gru,
Her,
Hor,
Hya,
Hyi,
Ind,
Lac,
Leo,
LMi,
Lep,
Lib,
Lup,
Lyn,
Lyr,
Men,
Mic,
Mon,
Mus,
Nor,
Oct,
Oph,
Ori,
Pav,
Peg,
Per,
Phe,
Pic,
Psc,
PsA,
Pup,
Pyx,
Ret,
Sge,
Sgr,
Sco,
Scl,
Sct,
Ser,
Sex,
Tau,
Tel,
Tri,
TrA,
Tuc,
UMa,
UMi,
Vel,
Vir,
Vol,
Vul,
}
const ABBREVIATIONS: [&str; 88] = [
"And", "Ant", "Aps", "Aqr", "Aql", "Ara", "Ari", "Aur", "Boo", "Cae", "Cam", "Cnc", "CVn",
"CMa", "CMi", "Cap", "Car", "Cas", "Cen", "Cep", "Cet", "Cha", "Cir", "Col", "Com", "CrA",
"CrB", "Crv", "Crt", "Cru", "Cyg", "Del", "Dor", "Dra", "Equ", "Eri", "For", "Gem", "Gru",
"Her", "Hor", "Hya", "Hyi", "Ind", "Lac", "Leo", "LMi", "Lep", "Lib", "Lup", "Lyn", "Lyr",
"Men", "Mic", "Mon", "Mus", "Nor", "Oct", "Oph", "Ori", "Pav", "Peg", "Per", "Phe", "Pic",
"Psc", "PsA", "Pup", "Pyx", "Ret", "Sge", "Sgr", "Sco", "Scl", "Sct", "Ser", "Sex", "Tau",
"Tel", "Tri", "TrA", "Tuc", "UMa", "UMi", "Vel", "Vir", "Vol", "Vul",
];
const NAMES: [&str; 88] = [
"Andromeda",
"Antlia",
"Apus",
"Aquarius",
"Aquila",
"Ara",
"Aries",
"Auriga",
"Boötes",
"Caelum",
"Camelopardalis",
"Cancer",
"Canes Venatici",
"Canis Major",
"Canis Minor",
"Capricornus",
"Carina",
"Cassiopeia",
"Centaurus",
"Cepheus",
"Cetus",
"Chamaeleon",
"Circinus",
"Columba",
"Coma Berenices",
"Corona Australis",
"Corona Borealis",
"Corvus",
"Crater",
"Crux",
"Cygnus",
"Delphinus",
"Dorado",
"Draco",
"Equuleus",
"Eridanus",
"Fornax",
"Gemini",
"Grus",
"Hercules",
"Horologium",
"Hydra",
"Hydrus",
"Indus",
"Lacerta",
"Leo",
"Leo Minor",
"Lepus",
"Libra",
"Lupus",
"Lynx",
"Lyra",
"Mensa",
"Microscopium",
"Monoceros",
"Musca",
"Norma",
"Octans",
"Ophiuchus",
"Orion",
"Pavo",
"Pegasus",
"Perseus",
"Phoenix",
"Pictor",
"Pisces",
"Piscis Austrinus",
"Puppis",
"Pyxis",
"Reticulum",
"Sagitta",
"Sagittarius",
"Scorpius",
"Sculptor",
"Scutum",
"Serpens",
"Sextans",
"Taurus",
"Telescopium",
"Triangulum",
"Triangulum Australe",
"Tucana",
"Ursa Major",
"Ursa Minor",
"Vela",
"Virgo",
"Volans",
"Vulpecula",
];
impl Constellation {
pub const ALL: [Constellation; 88] = [
Constellation::And,
Constellation::Ant,
Constellation::Aps,
Constellation::Aqr,
Constellation::Aql,
Constellation::Ara,
Constellation::Ari,
Constellation::Aur,
Constellation::Boo,
Constellation::Cae,
Constellation::Cam,
Constellation::Cnc,
Constellation::CVn,
Constellation::CMa,
Constellation::CMi,
Constellation::Cap,
Constellation::Car,
Constellation::Cas,
Constellation::Cen,
Constellation::Cep,
Constellation::Cet,
Constellation::Cha,
Constellation::Cir,
Constellation::Col,
Constellation::Com,
Constellation::CrA,
Constellation::CrB,
Constellation::Crv,
Constellation::Crt,
Constellation::Cru,
Constellation::Cyg,
Constellation::Del,
Constellation::Dor,
Constellation::Dra,
Constellation::Equ,
Constellation::Eri,
Constellation::For,
Constellation::Gem,
Constellation::Gru,
Constellation::Her,
Constellation::Hor,
Constellation::Hya,
Constellation::Hyi,
Constellation::Ind,
Constellation::Lac,
Constellation::Leo,
Constellation::LMi,
Constellation::Lep,
Constellation::Lib,
Constellation::Lup,
Constellation::Lyn,
Constellation::Lyr,
Constellation::Men,
Constellation::Mic,
Constellation::Mon,
Constellation::Mus,
Constellation::Nor,
Constellation::Oct,
Constellation::Oph,
Constellation::Ori,
Constellation::Pav,
Constellation::Peg,
Constellation::Per,
Constellation::Phe,
Constellation::Pic,
Constellation::Psc,
Constellation::PsA,
Constellation::Pup,
Constellation::Pyx,
Constellation::Ret,
Constellation::Sge,
Constellation::Sgr,
Constellation::Sco,
Constellation::Scl,
Constellation::Sct,
Constellation::Ser,
Constellation::Sex,
Constellation::Tau,
Constellation::Tel,
Constellation::Tri,
Constellation::TrA,
Constellation::Tuc,
Constellation::UMa,
Constellation::UMi,
Constellation::Vel,
Constellation::Vir,
Constellation::Vol,
Constellation::Vul,
];
#[must_use]
pub fn abbreviation(self) -> &'static str {
ABBREVIATIONS[self as usize]
}
#[must_use]
pub fn name(self) -> &'static str {
NAMES[self as usize]
}
}
impl fmt::Display for Constellation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
impl FromStr for Constellation {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
Constellation::ALL
.into_iter()
.find(|c| c.abbreviation().eq_ignore_ascii_case(s))
.ok_or_else(|| Error::UnknownConstellation(s.to_string()))
}
}
#[must_use]
pub fn constellation(coord: Equatorial) -> Constellation {
let b1875 = precess(coord, Epoch::OfDate(B1875_JULIAN_YEAR));
lookup_b1875(b1875.ra().hours(), b1875.dec().degrees())
}
fn lookup_b1875(ra_hours: f64, dec_degrees: f64) -> Constellation {
let ra = ra_hours.rem_euclid(24.0);
for &(ra_lo, ra_hi, dec_lo, c) in ZONES.iter() {
if dec_degrees >= dec_lo && ra >= ra_lo && ra < ra_hi {
return c;
}
}
Constellation::Oct
}
#[cfg(test)]
mod tests {
use super::*;
use crate::angle::{Angle, ParseMode};
fn j2000(ra: &str, dec: &str) -> Equatorial {
Equatorial::parse_j2000(ra, dec, ParseMode::Strict).unwrap()
}
#[test]
fn known_objects() {
assert_eq!(
constellation(j2000("00:42:44.3", "+41:16:09")),
Constellation::And
);
assert_eq!(
constellation(j2000("02:31:49", "+89:15:51")),
Constellation::UMi
);
assert_eq!(
constellation(j2000("21:08:47", "-88:57:23")),
Constellation::Oct
);
assert_eq!(
constellation(j2000("15:44:16", "+06:25:32")),
Constellation::Ser
);
assert_eq!(
constellation(j2000("18:21:19", "-02:53:56")),
Constellation::Ser
);
}
#[test]
fn poles_any_ra() {
for ra_deg in [0.0, 90.0, 123.456, 359.9] {
let north =
Equatorial::j2000(Angle::from_degrees(ra_deg), Angle::from_degrees(90.0)).unwrap();
let south =
Equatorial::j2000(Angle::from_degrees(ra_deg), Angle::from_degrees(-90.0)).unwrap();
assert_eq!(constellation(north), Constellation::UMi);
assert_eq!(constellation(south), Constellation::Oct);
}
}
#[test]
fn ra_wrap_pair() {
assert_eq!(
constellation(j2000("23:59:59.9", "+05:00:00")),
Constellation::Psc
);
assert_eq!(
constellation(j2000("00:00:00.1", "+05:00:00")),
Constellation::Psc
);
}
#[test]
fn of_date_input_honoured() {
let m31 = j2000("00:42:44.3", "+41:16:09");
let of_date = precess(m31, Epoch::OfDate(2026.5));
assert_eq!(constellation(of_date), Constellation::And);
}
#[test]
fn table_walk_half_open_convention() {
assert_eq!(lookup_b1875(4.0, 88.0), Constellation::UMi);
assert_eq!(lookup_b1875(4.0, 87.9999), Constellation::Cep);
assert_eq!(lookup_b1875(7.9999, 87.0), Constellation::Cep);
assert_eq!(lookup_b1875(8.0, 87.0), Constellation::UMi);
}
#[test]
fn table_walk_wrap_and_totality() {
assert_eq!(lookup_b1875(24.0, 89.0), lookup_b1875(0.0, 89.0));
assert_eq!(lookup_b1875(12.0, -90.0), Constellation::Oct);
let mut hits = 0usize;
let mut dec = -90.0;
while dec <= 90.0 {
let mut ra = 0.0;
while ra < 24.0 {
let c = lookup_b1875(ra, dec);
assert_eq!(c.abbreviation().len(), 3);
hits += 1;
ra += 0.5;
}
dec += 3.0;
}
assert_eq!(hits, 48 * 61);
}
#[test]
fn names_and_parsing() {
assert_eq!(Constellation::And.abbreviation(), "And");
assert_eq!(Constellation::And.name(), "Andromeda");
assert_eq!(Constellation::Boo.name(), "Boötes");
assert_eq!(Constellation::Cha.name(), "Chamaeleon");
assert_eq!(Constellation::Oph.name(), "Ophiuchus");
assert_eq!(Constellation::PsA.name(), "Piscis Austrinus");
assert_eq!(Constellation::UMi.to_string(), "Ursa Minor");
assert_eq!("umi".parse::<Constellation>().unwrap(), Constellation::UMi);
assert_eq!("UMI".parse::<Constellation>().unwrap(), Constellation::UMi);
assert!(matches!(
"Xyz".parse::<Constellation>(),
Err(Error::UnknownConstellation(s)) if s == "Xyz"
));
}
}