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