Skip to main content

sidereon_core/rtcm/
station.rs

1//! RTCM 3 stationary antenna reference point messages 1005 and 1006.
2//!
3//! Message 1005 (RTCM 10403.3 Table 3.5-9) gives the Earth-centred,
4//! Earth-fixed (ECEF) coordinates of a reference station's antenna reference
5//! point. Message 1006 (Table 3.5-10) is identical but appends the antenna
6//! height above the marker. Both carry the ECEF components as 38-bit
7//! two's-complement integers in units of 0.0001 m, and the height (1006) as an
8//! unsigned 16-bit integer in the same unit.
9//!
10//! The coordinates are stored as their raw transmitted integers so the body
11//! round-trips byte-for-byte; the [`StationCoordinates::x_m`] family converts to
12//! meters.
13
14use crate::error::{Error, Result};
15
16use super::bits::{BitReader, BitWriter};
17
18/// ECEF reference-point scale: each integer step is 0.0001 m.
19const ECEF_SCALE_M: f64 = 0.0001;
20
21/// A decoded message 1005 or 1006 antenna reference point.
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
23pub struct StationCoordinates {
24    /// 1005 or 1006.
25    pub message_number: u16,
26    /// Reference station identifier (DF003).
27    pub reference_station_id: u16,
28    /// ITRF realization year (DF021, 6 bits).
29    pub itrf_realization_year: u8,
30    /// GPS service supported at this station (DF022).
31    pub gps_indicator: bool,
32    /// GLONASS service supported (DF023).
33    pub glonass_indicator: bool,
34    /// Galileo service supported (DF024).
35    pub galileo_indicator: bool,
36    /// Reference-station indicator (DF141): physical vs non-physical station.
37    pub reference_station_indicator: bool,
38    /// Antenna reference point ECEF X (DF025), raw integer of 0.0001 m steps.
39    pub ecef_x: i64,
40    /// Single receiver oscillator indicator (DF142).
41    pub single_receiver_oscillator: bool,
42    /// Reserved field DF001 (1 bit), preserved for exact round-trip.
43    pub reserved: bool,
44    /// Antenna reference point ECEF Y (DF026), raw integer of 0.0001 m steps.
45    pub ecef_y: i64,
46    /// Quarter-cycle indicator (DF364, 2 bits).
47    pub quarter_cycle_indicator: u8,
48    /// Antenna reference point ECEF Z (DF027), raw integer of 0.0001 m steps.
49    pub ecef_z: i64,
50    /// Antenna height above the marker (DF028), raw integer of 0.0001 m steps.
51    /// Present only for message 1006.
52    pub antenna_height: Option<u16>,
53}
54
55impl StationCoordinates {
56    /// ECEF X in meters.
57    pub fn x_m(&self) -> f64 {
58        self.ecef_x as f64 * ECEF_SCALE_M
59    }
60
61    /// ECEF Y in meters.
62    pub fn y_m(&self) -> f64 {
63        self.ecef_y as f64 * ECEF_SCALE_M
64    }
65
66    /// ECEF Z in meters.
67    pub fn z_m(&self) -> f64 {
68        self.ecef_z as f64 * ECEF_SCALE_M
69    }
70
71    /// Antenna height in meters, if this is a 1006 message.
72    pub fn antenna_height_m(&self) -> Option<f64> {
73        self.antenna_height.map(|h| f64::from(h) * ECEF_SCALE_M)
74    }
75
76    /// Decode a 1005 / 1006 body (without the transport frame).
77    pub fn decode(body: &[u8]) -> Result<Self> {
78        let mut r = BitReader::new(body);
79        let message_number = r.u(12)? as u16;
80        if message_number != 1005 && message_number != 1006 {
81            return Err(Error::Parse(format!(
82                "message {message_number} is not station coordinates 1005/1006"
83            )));
84        }
85        let reference_station_id = r.u(12)? as u16;
86        let itrf_realization_year = r.u(6)? as u8;
87        let gps_indicator = r.flag()?;
88        let glonass_indicator = r.flag()?;
89        let galileo_indicator = r.flag()?;
90        let reference_station_indicator = r.flag()?;
91        let ecef_x = r.i(38)?;
92        let single_receiver_oscillator = r.flag()?;
93        let reserved = r.flag()?;
94        let ecef_y = r.i(38)?;
95        let quarter_cycle_indicator = r.u(2)? as u8;
96        let ecef_z = r.i(38)?;
97        let antenna_height = if message_number == 1006 {
98            Some(r.u(16)? as u16)
99        } else {
100            None
101        };
102
103        Ok(Self {
104            message_number,
105            reference_station_id,
106            itrf_realization_year,
107            gps_indicator,
108            glonass_indicator,
109            galileo_indicator,
110            reference_station_indicator,
111            ecef_x,
112            single_receiver_oscillator,
113            reserved,
114            ecef_y,
115            quarter_cycle_indicator,
116            ecef_z,
117            antenna_height,
118        })
119    }
120
121    /// Encode this station coordinate message body (without the transport frame).
122    pub fn encode(&self) -> Vec<u8> {
123        let mut w = BitWriter::new();
124        w.push_u(u64::from(self.message_number), 12);
125        w.push_u(u64::from(self.reference_station_id), 12);
126        w.push_u(u64::from(self.itrf_realization_year), 6);
127        w.push_flag(self.gps_indicator);
128        w.push_flag(self.glonass_indicator);
129        w.push_flag(self.galileo_indicator);
130        w.push_flag(self.reference_station_indicator);
131        w.push_i(self.ecef_x, 38);
132        w.push_flag(self.single_receiver_oscillator);
133        w.push_flag(self.reserved);
134        w.push_i(self.ecef_y, 38);
135        w.push_u(u64::from(self.quarter_cycle_indicator), 2);
136        w.push_i(self.ecef_z, 38);
137        if let Some(height) = self.antenna_height {
138            w.push_u(u64::from(height), 16);
139        }
140        w.into_bytes()
141    }
142}