sidereon_core/rtcm/ephemeris.rs
1//! RTCM 3 broadcast ephemeris messages 1019 (GPS) and 1020 (GLONASS).
2//!
3//! Message 1019 (RTCM 10403.3 Table 3.5-21) carries one complete set of GPS
4//! LNAV ephemeris and clock parameters; message 1020 (Table 3.5-22) carries one
5//! GLONASS satellite's ephemeris. The GLONASS message stores its orbit terms in
6//! sign-and-magnitude form (the leading bit is the sign), which the bit reader's
7//! [`super::bits::BitReader::ism`] handles.
8//!
9//! Every field is stored as its raw transmitted integer (the `DFxxx` quantity),
10//! preserving the integer-vs-sign-magnitude distinction exactly, so the body
11//! round-trips byte-for-byte. The standard per-field scale factors are noted in
12//! the struct docs; applying them yields the engineering-unit ephemeris that
13//! [`crate::broadcast`] consumes.
14
15use crate::error::{Error, Result};
16use crate::id::{GnssSatelliteId, GnssSystem};
17
18use super::bits::{BitReader, BitWriter};
19use super::DecodeResult;
20
21/// A decoded GPS broadcast ephemeris (message 1019).
22///
23/// Angular quantities are in semicircles (scale noted per field), harmonic
24/// correction terms in radians, distances in meters, and clock terms in
25/// seconds, each recovered by multiplying the raw integer by its scale factor.
26#[derive(Clone, Copy, Debug, PartialEq, Eq)]
27pub struct GpsEphemeris {
28 /// GPS satellite PRN (DF009).
29 pub satellite_id: u8,
30 /// GPS week number (DF076, 10 bits).
31 pub week_number: u16,
32 /// SV accuracy / URA index (DF077, 4 bits).
33 pub sv_accuracy: u8,
34 /// Code on L2 (DF078, 2 bits).
35 pub code_on_l2: u8,
36 /// Rate of inclination angle IDOT (DF079, int14, scale 2^-43 semicircles/s).
37 pub idot: i32,
38 /// Issue of data, ephemeris (DF071, 8 bits).
39 pub iode: u8,
40 /// Clock data reference time t_oc (DF081, uint16, scale 2^4 s).
41 pub t_oc: u16,
42 /// Clock drift rate a_f2 (DF082, int8, scale 2^-55 s/s^2).
43 pub a_f2: i16,
44 /// Clock drift a_f1 (DF083, int16, scale 2^-43 s/s).
45 pub a_f1: i32,
46 /// Clock bias a_f0 (DF084, int22, scale 2^-31 s).
47 pub a_f0: i32,
48 /// Issue of data, clock (DF085, 10 bits).
49 pub iodc: u16,
50 /// Orbit-radius sine correction C_rs (DF086, int16, scale 2^-5 m).
51 pub c_rs: i32,
52 /// Mean-motion difference dn (DF087, int16, scale 2^-43 semicircles/s).
53 pub delta_n: i32,
54 /// Mean anomaly at reference time M_0 (DF088, int32, scale 2^-31 semicircles).
55 pub m0: i64,
56 /// Latitude-argument cosine correction C_uc (DF089, int16, scale 2^-29 rad).
57 pub c_uc: i32,
58 /// Eccentricity e (DF090, uint32, scale 2^-33).
59 pub eccentricity: u64,
60 /// Latitude-argument sine correction C_us (DF091, int16, scale 2^-29 rad).
61 pub c_us: i32,
62 /// Square root of the semi-major axis sqrt(A) (DF092, uint32, scale 2^-19).
63 pub sqrt_a: u64,
64 /// Ephemeris reference time t_oe (DF093, uint16, scale 2^4 s).
65 pub t_oe: u16,
66 /// Inclination cosine correction C_ic (DF094, int16, scale 2^-29 rad).
67 pub c_ic: i32,
68 /// Longitude of ascending node Omega_0 (DF095, int32, scale 2^-31 semicircles).
69 pub omega0: i64,
70 /// Inclination sine correction C_is (DF096, int16, scale 2^-29 rad).
71 pub c_is: i32,
72 /// Inclination at reference time i_0 (DF097, int32, scale 2^-31 semicircles).
73 pub i0: i64,
74 /// Orbit-radius cosine correction C_rc (DF098, int16, scale 2^-5 m).
75 pub c_rc: i32,
76 /// Argument of perigee omega (DF099, int32, scale 2^-31 semicircles).
77 pub omega: i64,
78 /// Rate of right ascension Omega-dot (DF100, int24, scale 2^-43 semicircles/s).
79 pub omega_dot: i32,
80 /// Group delay differential t_GD (DF101, int8, scale 2^-31 s).
81 pub t_gd: i16,
82 /// SV health (DF102, 6 bits).
83 pub sv_health: u8,
84 /// L2 P-data flag (DF103).
85 pub l2_p_data_flag: bool,
86 /// Fit-interval flag (DF137).
87 pub fit_interval: bool,
88}
89
90impl GpsEphemeris {
91 /// The satellite identifier for this ephemeris.
92 pub fn satellite(&self) -> Result<GnssSatelliteId> {
93 GnssSatelliteId::new(GnssSystem::Gps, self.satellite_id)
94 .map_err(|e| Error::Parse(format!("invalid GPS PRN in 1019: {e}")))
95 }
96
97 /// Decode a message 1019 body (without the transport frame).
98 pub fn decode(body: &[u8]) -> Result<Self> {
99 Self::decode_inner(body).map_err(Into::into)
100 }
101
102 pub(crate) fn decode_inner(body: &[u8]) -> DecodeResult<Self> {
103 let mut r = BitReader::new(body);
104 let message_number = r.u(12)? as u16;
105 if message_number != 1019 {
106 return Err(Error::Parse(format!(
107 "message {message_number} is not GPS ephemeris 1019"
108 ))
109 .into());
110 }
111 Ok(Self {
112 satellite_id: r.u(6)? as u8,
113 week_number: r.u(10)? as u16,
114 sv_accuracy: r.u(4)? as u8,
115 code_on_l2: r.u(2)? as u8,
116 idot: r.i(14)? as i32,
117 iode: r.u(8)? as u8,
118 t_oc: r.u(16)? as u16,
119 a_f2: r.i(8)? as i16,
120 a_f1: r.i(16)? as i32,
121 a_f0: r.i(22)? as i32,
122 iodc: r.u(10)? as u16,
123 c_rs: r.i(16)? as i32,
124 delta_n: r.i(16)? as i32,
125 m0: r.i(32)?,
126 c_uc: r.i(16)? as i32,
127 eccentricity: r.u(32)?,
128 c_us: r.i(16)? as i32,
129 sqrt_a: r.u(32)?,
130 t_oe: r.u(16)? as u16,
131 c_ic: r.i(16)? as i32,
132 omega0: r.i(32)?,
133 c_is: r.i(16)? as i32,
134 i0: r.i(32)?,
135 c_rc: r.i(16)? as i32,
136 omega: r.i(32)?,
137 omega_dot: r.i(24)? as i32,
138 t_gd: r.i(8)? as i16,
139 sv_health: r.u(6)? as u8,
140 l2_p_data_flag: r.flag()?,
141 fit_interval: r.flag()?,
142 })
143 }
144
145 /// Encode this GPS ephemeris body (without the transport frame).
146 pub fn encode(&self) -> Vec<u8> {
147 let mut w = BitWriter::new();
148 w.push_u(1019, 12);
149 w.push_u(u64::from(self.satellite_id), 6);
150 w.push_u(u64::from(self.week_number), 10);
151 w.push_u(u64::from(self.sv_accuracy), 4);
152 w.push_u(u64::from(self.code_on_l2), 2);
153 w.push_i(i64::from(self.idot), 14);
154 w.push_u(u64::from(self.iode), 8);
155 w.push_u(u64::from(self.t_oc), 16);
156 w.push_i(i64::from(self.a_f2), 8);
157 w.push_i(i64::from(self.a_f1), 16);
158 w.push_i(i64::from(self.a_f0), 22);
159 w.push_u(u64::from(self.iodc), 10);
160 w.push_i(i64::from(self.c_rs), 16);
161 w.push_i(i64::from(self.delta_n), 16);
162 w.push_i(self.m0, 32);
163 w.push_i(i64::from(self.c_uc), 16);
164 w.push_u(self.eccentricity, 32);
165 w.push_i(i64::from(self.c_us), 16);
166 w.push_u(self.sqrt_a, 32);
167 w.push_u(u64::from(self.t_oe), 16);
168 w.push_i(i64::from(self.c_ic), 16);
169 w.push_i(self.omega0, 32);
170 w.push_i(i64::from(self.c_is), 16);
171 w.push_i(self.i0, 32);
172 w.push_i(i64::from(self.c_rc), 16);
173 w.push_i(self.omega, 32);
174 w.push_i(i64::from(self.omega_dot), 24);
175 w.push_i(i64::from(self.t_gd), 8);
176 w.push_u(u64::from(self.sv_health), 6);
177 w.push_flag(self.l2_p_data_flag);
178 w.push_flag(self.fit_interval);
179 w.into_bytes()
180 }
181}
182
183/// A decoded GLONASS broadcast ephemeris (message 1020).
184///
185/// The orbit position / velocity / acceleration terms use sign-and-magnitude
186/// integers (DF111..DF119). Every field below is the raw transmitted integer;
187/// the noted scale factors recover km, km/s, km/s^2, and seconds.
188#[derive(Clone, Copy, Debug, PartialEq, Eq)]
189pub struct GlonassEphemeris {
190 /// GLONASS satellite slot number (DF038, 6 bits).
191 pub satellite_id: u8,
192 /// Frequency channel number (DF040, 5 bits; the wire value is k + 7).
193 pub frequency_channel: u8,
194 /// Almanac health C_n (DF104).
195 pub almanac_health: bool,
196 /// Almanac health availability (DF105).
197 pub almanac_health_availability: bool,
198 /// P1 flag (DF106, 2 bits).
199 pub p1: u8,
200 /// Frame time t_k (DF107, 12 bits).
201 pub t_k: u16,
202 /// MSB of the B_n health word (DF108).
203 pub b_n_msb: bool,
204 /// P2 flag (DF109).
205 pub p2: bool,
206 /// Ephemeris reference time t_b (DF110, 7 bits).
207 pub t_b: u8,
208 /// X-velocity (DF111, sign-magnitude 24-bit, scale 2^-20 km/s).
209 pub xn_dot: i32,
210 /// X-position (DF112, sign-magnitude 27-bit, scale 2^-11 km).
211 pub xn: i32,
212 /// X-acceleration (DF113, sign-magnitude 5-bit, scale 2^-30 km/s^2).
213 pub xn_dot_dot: i8,
214 /// Y-velocity (DF114, sign-magnitude 24-bit, scale 2^-20 km/s).
215 pub yn_dot: i32,
216 /// Y-position (DF115, sign-magnitude 27-bit, scale 2^-11 km).
217 pub yn: i32,
218 /// Y-acceleration (DF116, sign-magnitude 5-bit, scale 2^-30 km/s^2).
219 pub yn_dot_dot: i8,
220 /// Z-velocity (DF117, sign-magnitude 24-bit, scale 2^-20 km/s).
221 pub zn_dot: i32,
222 /// Z-position (DF118, sign-magnitude 27-bit, scale 2^-11 km).
223 pub zn: i32,
224 /// Z-acceleration (DF119, sign-magnitude 5-bit, scale 2^-30 km/s^2).
225 pub zn_dot_dot: i8,
226 /// P3 flag (DF120).
227 pub p3: bool,
228 /// Relative carrier-frequency offset gamma_n (DF121, sign-magnitude 11-bit,
229 /// scale 2^-40).
230 pub gamma_n: i16,
231 /// GLONASS-M P flag (DF122, 2 bits).
232 pub m_p: u8,
233 /// Third-string l_n health flag (DF123).
234 pub m_l_n_third: bool,
235 /// Clock bias tau_n (DF124, sign-magnitude 22-bit, scale 2^-30 s).
236 pub tau_n: i32,
237 /// Inter-frequency bias delta_tau_n (DF125, sign-magnitude 5-bit, scale
238 /// 2^-30 s).
239 pub delta_tau_n: i8,
240 /// Age of operation E_n (DF126, 5 bits, days).
241 pub e_n: u8,
242 /// GLONASS-M P4 flag (DF127).
243 pub m_p4: bool,
244 /// GLONASS-M F_t accuracy index (DF128, 4 bits).
245 pub m_f_t: u8,
246 /// GLONASS-M N_t calendar day number (DF129, 11 bits).
247 pub m_n_t: u16,
248 /// GLONASS-M M satellite type (DF130, 2 bits).
249 pub m_m: u8,
250 /// Additional data availability (DF131).
251 pub additional_data_available: bool,
252 /// N_A almanac reference day (DF132, 11 bits).
253 pub n_a: u16,
254 /// System time scale offset tau_c (DF133, sign-magnitude 32-bit, scale
255 /// 2^-31 s).
256 pub tau_c: i64,
257 /// GLONASS-M N_4 four-year interval number (DF134, 5 bits).
258 pub m_n4: u8,
259 /// GLONASS-M tau_GPS offset to GPS time (DF135, sign-magnitude 22-bit, scale
260 /// 2^-30 s).
261 pub m_tau_gps: i32,
262 /// Fifth-string l_n health flag (DF136).
263 pub m_l_n_fifth: bool,
264 /// Reserved field DF001 (7 bits), preserved for exact round-trip.
265 pub reserved: u8,
266}
267
268impl GlonassEphemeris {
269 /// The satellite identifier for this ephemeris.
270 pub fn satellite(&self) -> Result<GnssSatelliteId> {
271 GnssSatelliteId::new(GnssSystem::Glonass, self.satellite_id)
272 .map_err(|e| Error::Parse(format!("invalid GLONASS slot in 1020: {e}")))
273 }
274
275 /// Decode a message 1020 body (without the transport frame).
276 pub fn decode(body: &[u8]) -> Result<Self> {
277 Self::decode_inner(body).map_err(Into::into)
278 }
279
280 pub(crate) fn decode_inner(body: &[u8]) -> DecodeResult<Self> {
281 let mut r = BitReader::new(body);
282 let message_number = r.u(12)? as u16;
283 if message_number != 1020 {
284 return Err(Error::Parse(format!(
285 "message {message_number} is not GLONASS ephemeris 1020"
286 ))
287 .into());
288 }
289 Ok(Self {
290 satellite_id: r.u(6)? as u8,
291 frequency_channel: r.u(5)? as u8,
292 almanac_health: r.flag()?,
293 almanac_health_availability: r.flag()?,
294 p1: r.u(2)? as u8,
295 t_k: r.u(12)? as u16,
296 b_n_msb: r.flag()?,
297 p2: r.flag()?,
298 t_b: r.u(7)? as u8,
299 xn_dot: r.ism(24)? as i32,
300 xn: r.ism(27)? as i32,
301 xn_dot_dot: r.ism(5)? as i8,
302 yn_dot: r.ism(24)? as i32,
303 yn: r.ism(27)? as i32,
304 yn_dot_dot: r.ism(5)? as i8,
305 zn_dot: r.ism(24)? as i32,
306 zn: r.ism(27)? as i32,
307 zn_dot_dot: r.ism(5)? as i8,
308 p3: r.flag()?,
309 gamma_n: r.ism(11)? as i16,
310 m_p: r.u(2)? as u8,
311 m_l_n_third: r.flag()?,
312 tau_n: r.ism(22)? as i32,
313 delta_tau_n: r.ism(5)? as i8,
314 e_n: r.u(5)? as u8,
315 m_p4: r.flag()?,
316 m_f_t: r.u(4)? as u8,
317 m_n_t: r.u(11)? as u16,
318 m_m: r.u(2)? as u8,
319 additional_data_available: r.flag()?,
320 n_a: r.u(11)? as u16,
321 tau_c: r.ism(32)?,
322 m_n4: r.u(5)? as u8,
323 m_tau_gps: r.ism(22)? as i32,
324 m_l_n_fifth: r.flag()?,
325 reserved: r.u(7)? as u8,
326 })
327 }
328
329 /// Encode this GLONASS ephemeris body (without the transport frame).
330 pub fn encode(&self) -> Vec<u8> {
331 let mut w = BitWriter::new();
332 w.push_u(1020, 12);
333 w.push_u(u64::from(self.satellite_id), 6);
334 w.push_u(u64::from(self.frequency_channel), 5);
335 w.push_flag(self.almanac_health);
336 w.push_flag(self.almanac_health_availability);
337 w.push_u(u64::from(self.p1), 2);
338 w.push_u(u64::from(self.t_k), 12);
339 w.push_flag(self.b_n_msb);
340 w.push_flag(self.p2);
341 w.push_u(u64::from(self.t_b), 7);
342 w.push_ism(i64::from(self.xn_dot), 24);
343 w.push_ism(i64::from(self.xn), 27);
344 w.push_ism(i64::from(self.xn_dot_dot), 5);
345 w.push_ism(i64::from(self.yn_dot), 24);
346 w.push_ism(i64::from(self.yn), 27);
347 w.push_ism(i64::from(self.yn_dot_dot), 5);
348 w.push_ism(i64::from(self.zn_dot), 24);
349 w.push_ism(i64::from(self.zn), 27);
350 w.push_ism(i64::from(self.zn_dot_dot), 5);
351 w.push_flag(self.p3);
352 w.push_ism(i64::from(self.gamma_n), 11);
353 w.push_u(u64::from(self.m_p), 2);
354 w.push_flag(self.m_l_n_third);
355 w.push_ism(i64::from(self.tau_n), 22);
356 w.push_ism(i64::from(self.delta_tau_n), 5);
357 w.push_u(u64::from(self.e_n), 5);
358 w.push_flag(self.m_p4);
359 w.push_u(u64::from(self.m_f_t), 4);
360 w.push_u(u64::from(self.m_n_t), 11);
361 w.push_u(u64::from(self.m_m), 2);
362 w.push_flag(self.additional_data_available);
363 w.push_u(u64::from(self.n_a), 11);
364 w.push_ism(self.tau_c, 32);
365 w.push_u(u64::from(self.m_n4), 5);
366 w.push_ism(i64::from(self.m_tau_gps), 22);
367 w.push_flag(self.m_l_n_fifth);
368 w.push_u(u64::from(self.reserved), 7);
369 w.into_bytes()
370 }
371}