1#[derive(Debug, Clone, Copy)]
2pub struct Location {
3 pub lat: Coord,
4 pub lon: Coord,
5}
6
7impl Location {
8 pub fn new_from_coord(lat: Coord, lon: Coord) -> Self {
9 Self { lat, lon }
10 }
11
12 pub fn from_ref_coord(
13 lat_ref: LatRef,
14 lat: (f64, f64, f64),
15 lon_ref: LonRef,
16 lon: (f64, f64, f64),
17 ) -> Self {
18 let lat = Coord::from_sign_deg_min_sec(lat_ref.as_sign(), lat);
19 let lon = Coord::from_sign_deg_min_sec(lon_ref.as_sign(), lon);
20
21 Self { lat, lon }
22 }
23
24 pub fn lat_ref_deg_min_sec(&self) -> (LatRef, (f64, f64, f64)) {
25 let (deg, min, sec) = self.lat.as_deg_min_sec();
26 (LatRef::from_sign(deg), (deg.abs(), min, sec))
27 }
28
29 pub fn lon_ref_deg_min_sec(&self) -> (LonRef, (f64, f64, f64)) {
30 let (deg, min, sec) = self.lon.as_deg_min_sec();
31 (LonRef::from_sign(deg), (deg.abs(), min, sec))
32 }
33
34 pub fn iso_6709(&self) -> String {
45 let (lat_ref, (lat_deg, lat_min, lat_sec)) = self.lat_ref_deg_min_sec();
46 let (lon_ref, (lon_deg, lon_min, lon_sec)) = self.lon_ref_deg_min_sec();
47
48 fn pad_one_0(v: f64) -> String {
49 let s = format!("{v}");
50
51 let pre_decimal = s.split_once('.').map_or(s.as_str(), |x| x.0);
52
53 if pre_decimal.len() == 1 {
54 format!("0{s}")
55 } else {
56 s
57 }
58 }
59
60 let lat_sec = pad_one_0(lat_sec);
61 let lon_sec = pad_one_0(lon_sec);
62
63 format!("{lat_deg}°{lat_min:02}'{lat_sec}\"{lat_ref} {lon_deg}°{lon_min:02}'{lon_sec}\"{lon_ref}")
64 }
65
66 pub fn geo_uri(&self) -> String {
70 let lat = self.lat.0;
71 let lon = self.lon.0;
72 format!("geo:{lat:.6},{lon:.6}")
74 }
75}
76
77#[derive(Debug, Clone, Copy)]
78pub struct Coord(pub f64);
79
80impl Coord {
81 pub fn as_deg_min_sec(&self) -> (f64, f64, f64) {
89 let deg = self.0;
90 let h = deg.fract().abs() * 60.;
91 let s = h.fract() * 60.;
92
93 (deg.trunc(), h.trunc(), (s * 100.).round() / 100.)
94 }
95
96 pub fn from_deg_min_sec((deg, min, sec): (f64, f64, f64)) -> Self {
104 let sign = deg.signum();
105 Coord(deg + sign * min / 60. + sign * sec / 60. / 60.)
106 }
107
108 pub fn from_sign_deg_min_sec(sign: f64, deg_min_sec: (f64, f64, f64)) -> Self {
116 Self(sign * Self::from_deg_min_sec(deg_min_sec).0)
117 }
118}
119
120#[derive(Debug, Clone, Copy)]
121pub enum LatRef {
122 North,
123 South,
124}
125
126impl LatRef {
127 pub fn from_sign(sign: f64) -> Self {
128 if sign >= 0. {
129 Self::North
130 } else {
131 Self::South
132 }
133 }
134
135 pub fn as_sign(&self) -> f64 {
136 match self {
137 Self::North => 1.,
138 Self::South => -1.,
139 }
140 }
141}
142
143impl TryFrom<&str> for LatRef {
144 type Error = InvalidLatRef;
145 fn try_from(value: &str) -> Result<Self, Self::Error> {
146 match value {
147 "N" => Ok(Self::North),
148 "S" => Ok(Self::South),
149 v => Err(Self::Error::InvalidLatitudeRef(v.to_string())),
150 }
151 }
152}
153
154#[derive(Debug, Clone, thiserror::Error)]
155pub enum InvalidLatRef {
156 #[error("Invalid latitude reference: '{0}'. Must be 'N' or 'S'.")]
157 InvalidLatitudeRef(String),
158}
159
160impl std::fmt::Display for LatRef {
161 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162 match self {
163 Self::North => f.write_str("N"),
164 Self::South => f.write_str("S"),
165 }
166 }
167}
168
169#[derive(Debug, Clone, Copy)]
170pub enum LonRef {
171 East,
172 West,
173}
174
175impl LonRef {
176 pub fn from_sign(sign: f64) -> Self {
177 if sign >= 0. {
178 Self::East
179 } else {
180 Self::West
181 }
182 }
183
184 pub fn as_sign(&self) -> f64 {
185 match self {
186 Self::East => 1.,
187 Self::West => -1.,
188 }
189 }
190}
191
192impl TryFrom<&str> for LonRef {
193 type Error = InvalidLonRef;
194 fn try_from(value: &str) -> Result<Self, Self::Error> {
195 match value {
196 "E" => Ok(Self::East),
197 "W" => Ok(Self::West),
198 v => Err(Self::Error::InvalidLonRef(v.to_string())),
199 }
200 }
201}
202
203#[derive(Debug, Clone, thiserror::Error)]
204pub enum InvalidLonRef {
205 #[error("Invalid latitude reference: '{0}'. Must be 'E' or 'W'.")]
206 InvalidLonRef(String),
207}
208
209impl std::fmt::Display for LonRef {
210 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211 match self {
212 Self::East => f.write_str("E"),
213 Self::West => f.write_str("W"),
214 }
215 }
216}