gnss_rs/
domes.rs

1use thiserror::Error;
2
3#[cfg(feature = "serde")]
4use serde::{Deserialize, Serialize};
5
6/// DOMES parsing error
7#[derive(Debug, Error)]
8pub enum Error {
9    #[error("invalid domes format")]
10    InvalidFormat,
11    #[error("invalid domes length")]
12    InvalidLength,
13}
14
15/// DOMES site reference point.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
17#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
18pub enum TrackingPoint {
19    /// Monument (pole, pillar, geodetic marker..)
20    Monument,
21    /// Instrument reference point.
22    /// This is usually the antenna reference point, but it can be any
23    /// location referred to an instrument, like a specific location
24    /// on one axis of a telescope.
25    Instrument,
26}
27
28/// DOMES site identification number,
29/// see <https://itrf.ign.fr/en/network/domes/description>.
30#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
31#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
32pub struct DOMES {
33    /// Area / Country code (3 digits)
34    pub area: u16,
35    /// Area site number (2 digits)
36    pub site: u8,
37    /// Tracking point
38    pub point: TrackingPoint,
39    /// Sequential number (3 digits)
40    pub sequential: u16,
41}
42
43impl std::str::FromStr for DOMES {
44    type Err = Error;
45    fn from_str(s: &str) -> Result<Self, Self::Err> {
46        if s.len() == 9 {
47            let point = if s[5..6].eq("M") {
48                TrackingPoint::Monument
49            } else if s[5..6].eq("S") {
50                TrackingPoint::Instrument
51            } else {
52                return Err(Error::InvalidFormat);
53            };
54            let area = s[..3].parse::<u16>().map_err(|_| Error::InvalidFormat)?;
55            let site = s[3..5].parse::<u8>().map_err(|_| Error::InvalidFormat)?;
56            let sequential = s[6..].parse::<u16>().map_err(|_| Error::InvalidFormat)?;
57            Ok(Self {
58                point,
59                area,
60                site,
61                sequential,
62            })
63        } else {
64            Err(Error::InvalidLength)
65        }
66    }
67}
68
69impl std::fmt::Display for DOMES {
70    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
71        let point = match self.point {
72            TrackingPoint::Monument => 'M',
73            TrackingPoint::Instrument => 'S',
74        };
75        write!(
76            f,
77            "{:03}{:02}{}{:03}",
78            self.area, self.site, point, self.sequential
79        )
80    }
81}
82
83#[cfg(test)]
84mod test {
85    use super::{TrackingPoint, DOMES};
86    use std::str::FromStr;
87    #[test]
88    fn parser() {
89        for (descriptor, expected) in [
90            (
91                "10002M006",
92                DOMES {
93                    area: 100,
94                    site: 2,
95                    sequential: 6,
96                    point: TrackingPoint::Monument,
97                },
98            ),
99            (
100                "40405S031",
101                DOMES {
102                    area: 404,
103                    site: 5,
104                    sequential: 31,
105                    point: TrackingPoint::Instrument,
106                },
107            ),
108        ] {
109            let domes = DOMES::from_str(descriptor).unwrap();
110            assert_eq!(domes, expected, "failed to parse DOMES");
111            // reciprocal
112            assert_eq!(domes.to_string(), descriptor, "DOMES reciprocal failed");
113        }
114    }
115}