celestial_pointing/commands/
slist.rs1use super::{Command, CommandOutput};
2use crate::error::Result;
3use crate::model::PointingModel;
4use crate::observation::{Observation, PierSide};
5use crate::session::Session;
6use celestial_core::constants::{DEG_TO_RAD, RAD_TO_DEG};
7use celestial_core::Angle;
8
9pub struct Slist;
10
11impl Command for Slist {
12 fn name(&self) -> &str {
13 "SLIST"
14 }
15 fn description(&self) -> &str {
16 "List observations with residuals"
17 }
18
19 fn execute(&self, session: &mut Session, _args: &[&str]) -> Result<CommandOutput> {
20 let lat = session.latitude();
21 let header = format!(
22 "{:>5} {:>15} {:>15} {:>7} {:>7} {:>8} {:>8} {:>8} {:>8} {:>8}",
23 "", "*HA", "*Dec", "*Az", "*ZD", "dX", "dD", "dS", "dZ", "dR"
24 );
25 let mut output = header + "\n\n";
26 for (i, obs) in session.observations.iter().enumerate() {
27 let row = format_row(i + 1, obs, &session.model, lat);
28 output += &row;
29 output += "\n";
30 }
31 Ok(CommandOutput::Text(output))
32 }
33}
34
35fn format_row(num: usize, obs: &Observation, model: &PointingModel, lat: f64) -> String {
36 let h = obs.commanded_ha.radians();
37 let dec = obs.catalog_dec.radians();
38 let pier = obs.pier_side.sign();
39 let (model_dh, model_dd) = model.apply_equatorial(h, dec, lat, pier);
40 let (raw_dh, raw_dd) = compute_raw_residuals(obs);
41 let dh = raw_dh - model_dh;
42 let dd = raw_dd - model_dd;
43 let dx = dh * libm::cos(dec);
44 let (az, zd) = compute_az_zd(h, dec, lat);
45 let dr = libm::sqrt(dx * dx + dd * dd);
46 let pier_char = pier_indicator(obs.pier_side);
47 let mask_char = if obs.masked { "*" } else { "" };
48
49 format!(
50 "{:>4}{}{} {:>15} {:>15} {:>7.1} {:>7.1} {:>8.1} {:>8.1} {:>8.1} {:>8.1} {:>8.1}",
51 num,
52 pier_char,
53 mask_char,
54 format_hms(obs.commanded_ha),
55 format_dms(obs.catalog_dec),
56 az * RAD_TO_DEG,
57 zd * RAD_TO_DEG,
58 dx,
59 dd,
60 dx,
61 zd * RAD_TO_DEG,
62 dr
63 )
64}
65
66fn compute_raw_residuals(obs: &Observation) -> (f64, f64) {
67 let raw_dh = (obs.actual_ha - obs.commanded_ha).arcseconds();
68 let raw_dd = (obs.observed_dec - obs.catalog_dec).arcseconds();
69 (raw_dh, raw_dd)
70}
71
72fn compute_az_zd(h: f64, dec: f64, lat: f64) -> (f64, f64) {
73 let sin_alt = libm::sin(lat) * libm::sin(dec) + libm::cos(lat) * libm::cos(dec) * libm::cos(h);
74 let alt = libm::asin(sin_alt);
75 let zd = 90.0 * DEG_TO_RAD - alt;
76 let cos_alt = libm::cos(alt);
77 let (sin_az, cos_az) = if cos_alt.abs() < 1e-10 {
78 (0.0, 1.0)
79 } else {
80 let sa = -(libm::cos(dec) * libm::sin(h)) / cos_alt;
81 let ca = (libm::sin(dec) - libm::sin(lat) * sin_alt) / (libm::cos(lat) * cos_alt);
82 (sa, ca)
83 };
84 let az = libm::atan2(sin_az, cos_az);
85 (az, zd)
86}
87
88fn pier_indicator(pier_side: PierSide) -> &'static str {
89 match pier_side {
90 PierSide::West => "b",
91 PierSide::East => " ",
92 PierSide::Unknown => "?",
93 }
94}
95
96fn format_hms(angle: Angle) -> String {
97 let total_sec = angle.hours().abs() * 3600.0;
98 let h = (total_sec / 3600.0) as i32;
99 let m = ((total_sec - h as f64 * 3600.0) / 60.0) as i32;
100 let s = total_sec - h as f64 * 3600.0 - m as f64 * 60.0;
101 let sign = if angle.radians() < 0.0 { "-" } else { "+" };
102 format!("{}{:02} {:02} {:05.2}", sign, h, m, s)
103}
104
105fn format_dms(angle: Angle) -> String {
106 let total_arcsec = angle.degrees().abs() * 3600.0;
107 let d = (total_arcsec / 3600.0) as i32;
108 let m = ((total_arcsec - d as f64 * 3600.0) / 60.0) as i32;
109 let s = total_arcsec - d as f64 * 3600.0 - m as f64 * 60.0;
110 let sign = if angle.radians() < 0.0 { "-" } else { "+" };
111 format!("{}{:02} {:02} {:04.1}", sign, d, m, s)
112}