Skip to main content

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}