erfa_rust/
G5_safe.rs

1// G5
2//   atoc13.c  → eraAtoc13_safe
3//   atoi13.c  → eraAtoi13_safe
4//   atoiq.c   → eraAtoiq_safe
5
6
7use crate::G1_safe::eraAnp_safe;
8use crate::G29_safe::eraS2c_safe;
9use crate::G2_safe::eraApco13_safe;
10use crate::G3_safe::eraApio13_safe;
11use crate::G4_safe::eraAticq_safe;
12use crate::G7_safe::eraC2s_safe;
13use crate::H1_safe::eraASTROM;
14
15pub type ErfaResult<T> = Result<T, ()>;
16
17/*----------------------------------------------------------------------
18 *  G5/atoc13.c  →  eraAtoc13_safe
19 *--------------------------------------------------------------------*/
20/// Observed place → ICRS astrometric RA,Dec (2013 models).
21/// Returns (rc, dc, eo, j) where:
22/// - rc, dc: ICRS astrometric RA,Dec (radians)
23/// - eo: equation of the origins (radians)
24/// - j: UT1 conversion status from eraApco13 (0 or +1)
25///
26/// NOTE: The original C function eraAtoc13 computes but discards the
27/// equation of the origins (eo) value. This safe Rust version corrects
28/// this oversight by including eo in the return tuple. The eo value is
29/// calculated by eraApco13 and represents the equation of the origins,
30/// which is the distance between the CIO and the equinox along the
31/// celestial equator. While the original C API omits this value
32/// (likely for historical API compatibility reasons), I include it
33/// here as it may be useful for certain astronomical calculations and
34/// comes at no additional computational cost.
35
36pub fn eraAtoc13_safe(
37    type_: &str,
38    ob1: f64,
39    ob2: f64,
40    utc1: f64,
41    utc2: f64,
42    dut1: f64,
43    elong: f64,
44    phi: f64,
45    hm: f64,
46    xp: f64,
47    yp: f64,
48    phpa: f64,
49    tc: f64,
50    rh: f64,
51    wl: f64,
52) -> ErfaResult<(f64, f64, f64, i32)> {
53    // Star-independent astrometry parameters
54    let mut astrom = eraASTROM::default();
55
56    // Get astrometry parameters for ICRS ↔ observed; eo is computed here
57    let (eo, j) = eraApco13_safe(
58        utc1,
59        utc2,
60        dut1,
61        elong,
62        phi,
63        hm,
64        xp,
65        yp,
66        phpa,
67        tc,
68        rh,
69        wl,
70        &mut astrom,
71    )?;
72
73    // Observed → CIRS
74    let (ri, di) = eraAtoiq_safe(type_, ob1, ob2, &astrom)?;
75
76    // CIRS → ICRS
77    let (rc, dc) = eraAticq_safe(ri, di, &astrom)?;
78
79    Ok((rc, dc, eo, j))
80}
81
82/*----------------------------------------------------------------------
83 *  G5/atoi13.c  →  eraAtoi13_safe
84 *--------------------------------------------------------------------*/
85
86// Observed place → CIRS (2013 models). Returns (ri, di, j).
87pub fn eraAtoi13_safe(
88    type_: &str,
89    ob1: f64,
90    ob2: f64,
91    utc1: f64,
92    utc2: f64,
93    dut1: f64,
94    elong: f64,
95    phi: f64,
96    hm: f64,
97    xp: f64,
98    yp: f64,
99    phpa: f64,
100    tc: f64,
101    rh: f64,
102    wl: f64,
103) -> ErfaResult<(f64, f64, i32)> {
104    // Star-independent astrometry parameters
105    let mut astrom = eraASTROM::default();
106
107    // Get astrometry parameters for CIRS ↔ observed
108    let j = eraApio13_safe(
109        utc1,
110        utc2,
111        dut1,
112        elong,
113        phi,
114        hm,
115        xp,
116        yp,
117        phpa,
118        tc,
119        rh,
120        wl,
121        &mut astrom,
122    )?;
123
124    // Observed → CIRS
125    let (ri, di) = eraAtoiq_safe(type_, ob1, ob2, &astrom)?;
126    Ok((ri, di, j))
127}
128
129/*----------------------------------------------------------------------
130 *  G5/atoiq.c  →  eraAtoiq_safe
131 *--------------------------------------------------------------------*/
132
133// Quick observed place → CIRS using supplied astrometry parameters.
134pub fn eraAtoiq_safe(
135    type_: &str,
136    ob1: f64,
137    ob2: f64,
138    astrom: &eraASTROM,
139) -> ErfaResult<(f64, f64)> {
140    const SELMIN: f64 = 0.05; // minimum proxy for refraction clamp
141
142    // Deref astrom once for convenience
143    let a = astrom;
144
145    // Coordinate kind (first char only)
146    let mut c = type_.as_bytes().get(0).map(|b| *b as char).unwrap_or('A');
147
148    // Work variables
149    let mut c1 = ob1;
150    let c2 = ob2;
151
152    let sphi = a.sphi;
153    let cphi = a.cphi;
154
155    // Standardize coordinate code
156    c = match c {
157        'r' | 'R' => 'R',
158        'h' | 'H' => 'H',
159        _ => 'A',
160    };
161
162    // Cartesian vector of the line of sight (S=0,E=90)
163    let (xaeo, yaeo, zaeo) = if c == 'A' {
164        // Input is Az, ZD.
165        let ce = c2.sin();
166        (-c1.cos() * ce, c1.sin() * ce, c2.cos())
167    } else {
168        // If RA,Dec convert to HA,Dec.
169        if c == 'R' {
170            c1 = a.eral - c1;
171        }
172        // To Cartesian -HA,Dec.
173        let v0 = eraS2c_safe(-c1, c2)?; // [xmhdo, ymhdo, zmhdo]
174        let xmhdo = v0[0];
175        let ymhdo = v0[1];
176        let zmhdo = v0[2];
177
178        // To Cartesian Az,El (S=0,E=90).
179        (
180            sphi * xmhdo - cphi * zmhdo,
181            ymhdo,
182            cphi * xmhdo + sphi * zmhdo,
183        )
184    };
185
186    // Azimuth (S=0,E=90)
187    let az = if xaeo != 0.0 || yaeo != 0.0 {
188        yaeo.atan2(xaeo)
189    } else {
190        0.0
191    };
192
193    // Sine of observed ZD, and observed ZD
194    let sz = (xaeo * xaeo + yaeo * yaeo).sqrt();
195    let zdo = sz.atan2(zaeo);
196
197    // Refraction
198    let refa = a.refa;
199    let refb = a.refb;
200    let tz = sz / if zaeo > SELMIN { zaeo } else { SELMIN };
201    let dref = (refa + refb * tz * tz) * tz;
202    let zdt = zdo + dref;
203
204    // To Cartesian Az,ZD after refraction
205    let ce = zdt.sin();
206    let xaet = az.cos() * ce;
207    let yaet = az.sin() * ce;
208    let zaet = zdt.cos();
209
210    // Cartesian Az,ZD → Cartesian -HA,Dec
211    let xmhda = sphi * xaet + cphi * zaet;
212    let ymhda = yaet;
213    let zmhda = -cphi * xaet + sphi * zaet;
214
215    // Diurnal aberration
216    let f = 1.0 + a.diurab * ymhda;
217    let xhd = f * xmhda;
218    let yhd = f * (ymhda - a.diurab);
219    let zhd = f * zmhda;
220
221    // Polar motion
222    let sx = a.xpl.sin();
223    let cx = a.xpl.cos();
224    let sy = a.ypl.sin();
225    let cy = a.ypl.cos();
226    let v = [
227        cx * xhd + sx * sy * yhd - sx * cy * zhd,
228        cy * yhd + sy * zhd,
229        sx * xhd - cx * sy * yhd + cx * cy * zhd,
230    ];
231
232    // To spherical -HA,Dec
233    let (hma, di) = eraC2s_safe(&v)?;
234
235    // Right ascension
236    let ri = eraAnp_safe(a.eral + hma)?;
237
238    Ok((ri, di))
239}