sct_reader/loaders/euroscope/
position.rs

1use std::{fmt::Display, marker::PhantomData};
2
3use super::{error::Error, SectorResult};
4
5#[derive(Debug, Clone, Copy, PartialEq)]
6pub struct Position<Status = MaybeValid> {
7    pub lat: f64,
8    pub lon: f64,
9    status: std::marker::PhantomData<Status>,
10}
11impl Position {
12    pub fn new(lat: f64, lon: f64) -> Position {
13        Position {
14            lat,
15            lon,
16            status: PhantomData,
17        }
18    }
19    pub fn try_new_from_es(lat: &str, lon: &str) -> SectorResult<Position> {
20        let lat = coord_from_es(lat).ok_or(Error::InvalidPosition)?;
21        let lon = coord_from_es(lon).ok_or(Error::InvalidPosition)?;
22        Ok(Position {
23            lat,
24            lon,
25            status: PhantomData,
26        })
27    }
28    pub fn validate(self) -> SectorResult<Position<Valid>> {
29        let valid = (-90.0..=90.0).contains(&self.lat) && (-180.0..=180.0).contains(&self.lon);
30        return if valid {
31            Ok(Position {
32                lat: self.lat,
33                lon: self.lon,
34                status: PhantomData,
35            })
36        } else {
37            Err(Error::InvalidPosition)
38        };
39    }
40}
41
42impl From<Position<Valid>> for Position<MaybeValid> {
43    fn from(value: Position<Valid>) -> Self {
44        Position {
45            lat: value.lat,
46            lon: value.lon,
47            status: PhantomData,
48        }
49    }
50}
51
52//N051.07.25.010
53//E002.39.13.334
54pub fn coord_from_es(value: &str) -> Option<f64> {
55    let multiply_by = if value.starts_with(&['N', 'n', 'E', 'e']) {
56        1.0
57    } else {
58        -1.0
59    };
60    let mut sections = value.get(1..)?.splitn(3, '.');
61    let degs = sections.next()?.parse::<f64>().ok()?;
62    let mins = sections.next()?.parse::<f64>().ok()?;
63    let secs = sections.next()?.parse::<f64>().ok()?;
64
65    let coord = degs + (mins / 60.0) + (secs / 3600.0);
66    return Some(coord * multiply_by);
67}
68
69#[derive(Debug, Clone, Copy, PartialEq)]
70pub struct Heading(f32);
71impl Heading {
72    pub fn new(heading: f32) -> SectorResult<Heading> {
73        heading.try_into()
74    }
75    pub fn new_from_u16(heading: u16) -> SectorResult<Heading> {
76        let value: f32 = heading.try_into().map_err(|_| Error::InvalidHeading)?;
77        Self::new(value)
78    }
79    pub fn value(&self) -> f32 {
80        self.0
81    }
82    pub fn value_u16(&self) -> u16 {
83        self.0.round() as u16
84    }
85    pub fn reciprocal(&self) -> Heading {
86        let new = if self.0 < 180.0 {
87            self.0 + 180.0
88        } else {
89            self.0 - 180.0
90        };
91        Heading(new)
92    }
93}
94impl Display for Heading {
95    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96        write!(f, "{:03}", self.0)
97    }
98}
99
100impl TryFrom<f32> for Heading {
101    type Error = Error;
102    fn try_from(mut value: f32) -> Result<Self, Self::Error> {
103        if value > 360.0 {
104            return Err(Error::InvalidHeading);
105        }
106        if value == 0.0 {
107            value = 360.0;
108        }
109        Ok(Heading(value))
110    }
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114pub struct MaybeValid;
115#[derive(Debug, Clone, Copy, PartialEq, Eq)]
116pub struct Valid;