celestial_pointing/commands/
correct.rs1use 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}