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}