1use crate::{cardinal::Cardinal, Error};
5
6#[cfg(feature = "serde")]
7use serde_derive::{Deserialize, Serialize};
8
9#[derive(PartialEq, Copy, Clone, Debug)]
16#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
17pub struct DMS {
18 pub degrees: u16,
20 pub minutes: u8,
22 pub seconds: f64,
24 pub cardinal: Option<Cardinal>,
26}
27
28#[derive(Debug, Copy, Clone)]
29#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
30pub enum Scale {
31 Country,
33 LargeCity,
35 City,
37 Neighborhood,
39 Street,
41 Tree,
43 Human,
45 RoughSurveying,
47 PreciseSurveying,
49}
50
51impl core::fmt::Display for DMS {
52 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
53 if let Some(cardinal) = self.cardinal {
54 write!(
55 f,
56 "{}°{}'{:.4}\"{}",
57 self.degrees, self.minutes, self.seconds, cardinal,
58 )
59 } else {
60 write!(f, "{}°{}'{:.4}\"", self.degrees, self.minutes, self.seconds)
61 }
62 }
63}
64
65impl Default for DMS {
66 fn default() -> Self {
68 Self {
69 degrees: 0,
70 minutes: 0,
71 seconds: 0.0_f64,
72 cardinal: None,
73 }
74 }
75}
76
77impl From<DMS> for f64 {
78 fn from(val: DMS) -> Self {
80 val.to_ddeg_angle()
81 }
82}
83
84impl From<DMS> for f32 {
85 fn from(val: DMS) -> Self {
87 val.to_ddeg_angle() as f32
88 }
89}
90
91impl From<DMS> for u64 {
92 fn from(val: DMS) -> Self {
95 val.total_seconds().floor() as u64
96 }
97}
98
99impl From<DMS> for u32 {
100 fn from(val: DMS) -> Self {
103 val.total_seconds().floor() as u32
104 }
105}
106
107impl From<DMS> for u16 {
108 fn from(val: DMS) -> Self {
111 val.total_seconds().floor() as u16
112 }
113}
114
115impl From<DMS> for u8 {
116 fn from(val: DMS) -> Self {
119 val.total_seconds().floor() as u8
120 }
121}
122
123impl core::ops::Add<DMS> for DMS {
124 type Output = Result<Self, Error>;
125 fn add(self, rhs: Self) -> Result<Self, Error> {
126 if let Some(c0) = self.cardinal {
127 if let Some(c1) = rhs.cardinal {
128 let a = self.to_ddeg_angle() + rhs.to_ddeg_angle();
129 if c0.is_latitude() && c1.is_latitude() {
130 Ok(Self::from_ddeg_latitude(a))
131 } else if c0.is_longitude() && c1.is_longitude() {
132 Ok(Self::from_ddeg_longitude(a))
133 } else {
134 Err(Error::IncompatibleCardinals)
135 }
136 } else {
137 Ok(Self::from_seconds(
138 self.total_seconds() + rhs.total_seconds(),
139 ))
140 }
141 } else {
142 Ok(Self::from_seconds(
143 self.total_seconds() + rhs.total_seconds(),
144 ))
145 }
146 }
147}
148
149impl core::ops::AddAssign<DMS> for DMS {
150 fn add_assign(&mut self, rhs: Self) {
151 if let Some(c0) = self.cardinal {
152 if let Some(c1) = rhs.cardinal {
153 let a = self.to_ddeg_angle() + rhs.to_ddeg_angle();
154 if c0.is_latitude() && c1.is_latitude() {
155 *self = Self::from_ddeg_latitude(a)
156 } else if c0.is_longitude() && c1.is_longitude() {
157 *self = Self::from_ddeg_longitude(a)
158 }
159 } else {
160 *self = Self::from_seconds(self.total_seconds() + rhs.total_seconds())
161 }
162 } else {
163 *self = Self::from_seconds(self.total_seconds() + rhs.total_seconds())
164 }
165 }
166}
167
168impl core::ops::AddAssign<f64> for DMS {
169 fn add_assign(&mut self, rhs: f64) {
170 if let Some(cardinal) = self.cardinal {
171 let a = self.to_ddeg_angle() + rhs;
172 if cardinal.is_latitude() {
173 *self = Self::from_ddeg_latitude(a)
174 } else {
175 *self = Self::from_ddeg_longitude(a)
176 }
177 } else {
178 *self = Self::from_seconds(self.total_seconds() + rhs)
179 }
180 }
181}
182
183impl core::ops::Add<f64> for DMS {
184 type Output = Self;
185 fn add(self, rhs: f64) -> Self {
186 if let Some(cardinal) = self.cardinal {
187 let a = self.to_ddeg_angle() + rhs;
188 if cardinal.is_latitude() {
189 Self::from_ddeg_latitude(a)
190 } else {
191 Self::from_ddeg_longitude(a)
192 }
193 } else {
194 Self::from_seconds(self.total_seconds() + rhs)
195 }
196 }
197}
198
199impl core::ops::Sub<f64> for DMS {
200 type Output = Self;
201 fn sub(self, rhs: f64) -> Self {
202 if let Some(cardinal) = self.cardinal {
203 let a = self.to_ddeg_angle() - rhs;
204 if cardinal.is_latitude() {
205 Self::from_ddeg_latitude(a)
206 } else {
207 Self::from_ddeg_longitude(a)
208 }
209 } else {
210 Self::from_seconds(self.total_seconds() - rhs)
211 }
212 }
213}
214
215impl core::ops::SubAssign<f64> for DMS {
216 fn sub_assign(&mut self, rhs: f64) {
217 if let Some(cardinal) = self.cardinal {
218 let a = self.to_ddeg_angle() - rhs;
219 if cardinal.is_latitude() {
220 *self = Self::from_ddeg_latitude(a)
221 } else {
222 *self = Self::from_ddeg_longitude(a)
223 }
224 } else {
225 *self = Self::from_seconds(self.total_seconds() - rhs)
226 }
227 }
228}
229
230impl core::ops::Mul<f64> for DMS {
231 type Output = DMS;
232 fn mul(self, rhs: f64) -> DMS {
233 if let Some(cardinal) = self.cardinal {
234 let a = self.to_ddeg_angle() * rhs;
235 if cardinal.is_latitude() {
236 Self::from_ddeg_latitude(a)
237 } else {
238 Self::from_ddeg_longitude(a)
239 }
240 } else {
241 Self::from_seconds(self.total_seconds() * rhs)
242 }
243 }
244}
245
246impl core::ops::Div<f64> for DMS {
247 type Output = DMS;
248 fn div(self, rhs: f64) -> DMS {
249 if let Some(cardinal) = self.cardinal {
250 let a = self.to_ddeg_angle() / rhs;
251 if cardinal.is_latitude() {
252 Self::from_ddeg_latitude(a)
253 } else {
254 Self::from_ddeg_longitude(a)
255 }
256 } else {
257 Self::from_seconds(self.total_seconds() / rhs)
258 }
259 }
260}
261
262impl core::ops::MulAssign<f64> for DMS {
263 fn mul_assign(&mut self, rhs: f64) {
264 if let Some(cardinal) = self.cardinal {
265 let a = self.to_ddeg_angle() * rhs;
266 if cardinal.is_latitude() {
267 *self = Self::from_ddeg_latitude(a)
268 } else {
269 *self = Self::from_ddeg_longitude(a)
270 }
271 } else {
272 *self = Self::from_seconds(self.total_seconds() * rhs)
273 }
274 }
275}
276
277impl core::ops::DivAssign<f64> for DMS {
278 fn div_assign(&mut self, rhs: f64) {
279 if let Some(cardinal) = self.cardinal {
280 let a = self.to_ddeg_angle() / rhs;
281 if cardinal.is_latitude() {
282 *self = Self::from_ddeg_latitude(a)
283 } else {
284 *self = Self::from_ddeg_longitude(a)
285 }
286 } else {
287 *self = Self::from_seconds(self.total_seconds() / rhs)
288 }
289 }
290}
291
292impl DMS {
293 pub fn new(degrees: u16, minutes: u8, seconds: f64, cardinal: Option<Cardinal>) -> DMS {
297 let d = Self::from_seconds(degrees as f64 * 3600.0 + minutes as f64 * 60.0 + seconds);
298 if let Some(cardinal) = cardinal {
299 d.with_cardinal(cardinal)
300 } else {
301 d
302 }
303 }
304
305 pub fn from_seconds(seconds: f64) -> Self {
307 let degrees = (seconds / 3600.0).floor();
308 let minutes = ((seconds - degrees * 3600.0) / 60.0).floor();
309 let integer = ((seconds - degrees * 3600.0 - minutes * 60.0).floor() as u8) % 60;
310 Self {
311 degrees: (degrees as u16) % 360,
312 minutes: minutes as u8,
313 seconds: integer as f64 + seconds.fract(),
314 cardinal: None,
315 }
316 }
317
318 pub fn with_cardinal(&self, cardinal: Cardinal) -> Self {
322 Self {
323 degrees: self.degrees,
324 minutes: self.minutes,
325 seconds: self.seconds,
326 cardinal: Some(cardinal),
327 }
328 }
329
330 pub fn from_ddeg_angle(angle: f64) -> Self {
333 let degrees = angle.abs().floor();
334 let minutes = ((angle.abs() - degrees) * 60.0).floor();
335 let seconds = (angle.abs() - degrees - minutes / 60.0_f64) * 3600.0_f64;
336 Self {
337 degrees: degrees as u16,
338 minutes: minutes as u8,
339 seconds,
340 cardinal: None,
341 }
342 }
343
344 pub fn from_ddeg_latitude(angle: f64) -> Self {
347 let degrees = angle.abs().floor();
348 let minutes = ((angle.abs() - degrees) * 60.0).floor();
349 let seconds = (angle.abs() - degrees - minutes / 60.0_f64) * 3600.0_f64;
350 let cardinal = if angle < 0.0 {
351 Cardinal::South
352 } else {
353 Cardinal::North
354 };
355 Self {
356 degrees: (degrees as u16) % 90,
357 minutes: minutes as u8,
358 seconds,
359 cardinal: Some(cardinal),
360 }
361 }
362
363 pub fn from_ddeg_longitude(angle: f64) -> Self {
366 let degrees = angle.abs().floor();
367 let minutes = ((angle.abs() - degrees) * 60.0).floor();
368 let seconds = (angle.abs() - degrees - minutes / 60.0_f64) * 3600.0_f64;
369 let cardinal = if angle < 0.0 {
370 Cardinal::West
371 } else {
372 Cardinal::East
373 };
374 Self {
375 degrees: (degrees as u16) % 180,
376 minutes: minutes as u8,
377 seconds,
378 cardinal: Some(cardinal),
379 }
380 }
381
382 pub fn to_ddeg_angle(&self) -> f64 {
385 let d = self.degrees as f64 + self.minutes as f64 / 60.0_f64 + self.seconds / 3600.0_f64;
386 match self.cardinal {
387 Some(cardinal) => {
388 if cardinal.is_southern() || cardinal.is_western() {
389 -d
390 } else {
391 d
392 }
393 }
394 None => d,
395 }
396 }
397
398 pub fn add_ddeg(&mut self, angle: f64) {
400 *self = Self::from_ddeg_angle(self.to_ddeg_angle() + angle);
401 }
402
403 pub fn with_ddeg_angle(&self, angle: f64) -> Self {
405 Self::from_ddeg_angle(self.to_ddeg_angle() + angle)
406 }
407
408 pub fn total_seconds(&self) -> f64 {
410 self.degrees as f64 * 3600.0 + self.minutes as f64 * 60.0 + self.seconds
411 }
412
413 pub fn to_radians(&self) -> f64 {
415 self.to_ddeg_angle() / 180.0 * core::f64::consts::PI
416 }
417 pub fn to_europe50(&self) -> Result<DMS, Error> {
503 if let Some(cardinal) = self.cardinal {
504 if cardinal.is_latitude() {
505 *self + DMS::new(0, 0, 3.6, Some(Cardinal::North))
506 } else {
507 *self + DMS::new(0, 0, 2.4, Some(Cardinal::East))
508 }
509 } else {
510 Ok(*self)
511 }
512 }
513}