sct_reader/loaders/euroscope/
position.rs1use 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
52pub 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;