1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
use thiserror::Error;

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

/// DOMES parsing error
#[derive(Debug, Error)]
pub enum Error {
    #[error("invalid domes format")]
    InvalidFormat,
    #[error("invalid domes length")]
    InvalidLength,
}

/// DOMES site reference point.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum TrackingPoint {
    /// Monument (pole, pillar, geodetic marker..)
    Monument,
    /// Instrument reference point.
    /// This is usually the antenna reference point, but it can be any
    /// location referred to an instrument, like a specific location
    /// on one axis of a telescope.
    Instrument,
}

/// DOMES site identification number,
/// see <https://itrf.ign.fr/en/network/domes/description>.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct DOMES {
    /// Area / Country code (3 digits)
    pub area: u16,
    /// Area site number (2 digits)
    pub site: u8,
    /// Tracking point
    pub point: TrackingPoint,
    /// Sequential number (3 digits)
    pub sequential: u16,
}

impl std::str::FromStr for DOMES {
    type Err = Error;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if s.len() == 9 {
            let point = if s[5..6].eq("M") {
                TrackingPoint::Monument
            } else if s[5..6].eq("S") {
                TrackingPoint::Instrument
            } else {
                return Err(Error::InvalidFormat);
            };
            let area = s[..3].parse::<u16>().map_err(|_| Error::InvalidFormat)?;
            let site = s[3..5].parse::<u8>().map_err(|_| Error::InvalidFormat)?;
            let sequential = s[6..].parse::<u16>().map_err(|_| Error::InvalidFormat)?;
            Ok(Self {
                point,
                area,
                site,
                sequential,
            })
        } else {
            Err(Error::InvalidLength)
        }
    }
}

impl std::fmt::Display for DOMES {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        let point = match self.point {
            TrackingPoint::Monument => 'M',
            TrackingPoint::Instrument => 'S',
        };
        write!(
            f,
            "{:03}{:02}{}{:03}",
            self.area, self.site, point, self.sequential
        )
    }
}

#[cfg(test)]
mod test {
    use super::{TrackingPoint, DOMES};
    use std::str::FromStr;
    #[test]
    fn parser() {
        for (descriptor, expected) in [
            (
                "10002M006",
                DOMES {
                    area: 100,
                    site: 2,
                    sequential: 6,
                    point: TrackingPoint::Monument,
                },
            ),
            (
                "40405S031",
                DOMES {
                    area: 404,
                    site: 5,
                    sequential: 31,
                    point: TrackingPoint::Instrument,
                },
            ),
        ] {
            let domes = DOMES::from_str(descriptor).unwrap();
            assert_eq!(domes, expected, "failed to parse DOMES");
            // reciprocal
            assert_eq!(domes.to_string(), descriptor, "DOMES reciprocal failed");
        }
    }
}