Skip to main content

celestial_pointing/commands/
correct.rs

1use super::{Command, CommandOutput};
2use crate::error::Result;
3use crate::observation::PierSide;
4use crate::parser::parse_coordinates;
5use crate::session::Session;
6use celestial_core::Angle;
7
8pub struct Correct;
9
10impl Command for Correct {
11    fn name(&self) -> &str {
12        "CORRECT"
13    }
14    fn description(&self) -> &str {
15        "Compute actual sky position from encoder reading"
16    }
17
18    fn execute(&self, session: &mut Session, args: &[&str]) -> Result<CommandOutput> {
19        let (enc_ra, enc_dec) = parse_coordinates(args)?;
20        let lst = session.current_lst()?;
21        let lat = Angle::from_radians(session.latitude());
22        let ha = lst - enc_ra;
23        let pier = pier_from_ha(ha);
24        let (true_ra, true_dec) = session
25            .model
26            .command_to_target(enc_ra, enc_dec, lst, lat, pier);
27        let delta_ra = (true_ra - enc_ra).wrapped();
28        let delta_dec = (true_dec - enc_dec).wrapped();
29        Ok(CommandOutput::Text(format_result(
30            enc_ra, enc_dec, true_ra, true_dec, delta_ra, delta_dec,
31        )))
32    }
33}
34
35fn pier_from_ha(ha: Angle) -> PierSide {
36    if ha.radians() >= 0.0 {
37        PierSide::East
38    } else {
39        PierSide::West
40    }
41}
42
43fn format_result(
44    enc_ra: Angle,
45    enc_dec: Angle,
46    true_ra: Angle,
47    true_dec: Angle,
48    dra: Angle,
49    ddec: Angle,
50) -> String {
51    format!(
52        "Encoder:  {}  {}\nActual:   {}  {}\n  \u{0394}RA:  {:+.2}s\n  \u{0394}Dec: {:+.1}\"",
53        format_ra(enc_ra),
54        format_dec(enc_dec),
55        format_ra(true_ra),
56        format_dec(true_dec),
57        dra.arcseconds() / 15.0,
58        ddec.arcseconds(),
59    )
60}
61
62fn format_ra(a: Angle) -> String {
63    let total_h = a.normalized().hours();
64    let h = libm::floor(total_h) as u32;
65    let rem = (total_h - h as f64) * 60.0;
66    let m = libm::floor(rem) as u32;
67    let s = (rem - m as f64) * 60.0;
68    format!("{:02}h {:02}m {:05.2}s", h, m, s)
69}
70
71fn format_dec(a: Angle) -> String {
72    let deg = a.degrees();
73    let sign = if deg < 0.0 { '-' } else { '+' };
74    let abs = deg.abs();
75    let d = libm::floor(abs) as u32;
76    let rem = (abs - d as f64) * 60.0;
77    let m = libm::floor(rem) as u32;
78    let s = (rem - m as f64) * 60.0;
79    format!("{}{:02}\u{00b0} {:02}' {:04.1}\"", sign, d, m, s)
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85    use crate::session::Session;
86
87    #[test]
88    fn empty_model_encoder_equals_actual() {
89        let mut session = Session::new();
90        session.lst_override = Some(Angle::from_hours(14.0));
91        let args = vec!["12", "30", "00", "+45", "00", "00"];
92        let result = Correct.execute(&mut session, &args).unwrap();
93        match result {
94            CommandOutput::Text(s) => {
95                assert!(s.contains("Encoder:"));
96                assert!(s.contains("Actual:"));
97                assert!(s.contains("\u{0394}RA:  +0.00s"));
98                assert!(s.contains("\u{0394}Dec: +0.0\""));
99            }
100            _ => panic!("expected Text output"),
101        }
102    }
103
104    #[test]
105    fn pier_east_when_ha_positive() {
106        let ha = Angle::from_hours(2.0);
107        assert_eq!(pier_from_ha(ha), PierSide::East);
108    }
109
110    #[test]
111    fn pier_west_when_ha_negative() {
112        let ha = Angle::from_hours(-2.0);
113        assert_eq!(pier_from_ha(ha), PierSide::West);
114    }
115
116    #[test]
117    fn pier_east_when_ha_zero() {
118        let ha = Angle::from_hours(0.0);
119        assert_eq!(pier_from_ha(ha), PierSide::East);
120    }
121
122    #[test]
123    fn correct_requires_lst() {
124        let mut session = Session::new();
125        let args = vec!["12.5", "45.0"];
126        let result = Correct.execute(&mut session, &args);
127        assert!(result.is_err());
128    }
129
130    #[test]
131    fn correct_with_model_produces_nonzero_deltas() {
132        let mut session = Session::new();
133        session.lst_override = Some(Angle::from_hours(14.0));
134        session.model.add_term("IH").unwrap();
135        session.model.set_coefficients(&[30.0]).unwrap();
136        let args = vec!["12.5", "45.0"];
137        let result = Correct.execute(&mut session, &args).unwrap();
138        match result {
139            CommandOutput::Text(s) => {
140                assert!(!s.contains("\u{0394}RA:  +0.00s"));
141            }
142            _ => panic!("expected Text output"),
143        }
144    }
145
146    #[test]
147    fn correct_decimal_args() {
148        let mut session = Session::new();
149        session.lst_override = Some(Angle::from_hours(14.0));
150        let args = vec!["12.5", "45.0"];
151        let result = Correct.execute(&mut session, &args);
152        assert!(result.is_ok());
153    }
154}