Skip to main content

celestial_pointing/commands/
slist.rs

1use 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}