1use log::warn;
2
3pub fn dms_to_dd(d: i32, m: u16, s: f64) -> f64 {
8 (d.abs() as f64 + (m as f64 + s / 60.) / 60.).copysign(d as f64)
9}
10
11pub fn dm_to_dd(d: i32, m: f64) -> f64 {
16 (d.abs() as f64 + (m / 60.)).copysign(d as f64)
17}
18
19pub fn iso_dm_to_dd(iso_dm: f64) -> f64 {
24 let magn = iso_dm.abs();
25 let dm = magn.trunc() as u64;
26 let d = (dm / 100) as f64;
27 let m = (dm % 100) as f64 + magn.fract();
28 (d + (m / 60.)).copysign(iso_dm)
29}
30
31pub fn dd_to_iso_dm(dd: f64) -> f64 {
33 let dm = dd.abs();
34 let d = dm.trunc();
35 let m = dm.fract() * 60.;
36 (d * 100. + m).copysign(dd)
37}
38
39pub fn iso_dms_to_dd(iso_dms: f64) -> f64 {
44 let magn = iso_dms.abs();
45 let dms = magn as u32;
46 let d = dms / 10000;
47 let ms = dms % 10000;
48 let m = ms / 100;
49 let s = (ms % 100) as f64 + magn.fract();
50 (d as f64 + ((s / 60.) + m as f64) / 60.).copysign(iso_dms)
51}
52
53pub fn dd_to_iso_dms(dd: f64) -> f64 {
56 let magn = dd.abs();
57 let d = magn.trunc();
58 let mm = magn.fract() * 60.;
59 let m = mm.trunc();
60 let s = mm.fract() * 60.;
61 (d * 10000. + m * 100. + s).copysign(dd)
62}
63
64pub fn normalize_symmetric(angle: f64) -> f64 {
66 use std::f64::consts::PI;
67 let angle = (angle + PI) % (2.0 * PI);
68 angle - PI * angle.signum()
69}
70
71pub fn normalize_positive(angle: f64) -> f64 {
73 use std::f64::consts::PI;
74 let angle = angle % (2.0 * PI);
75 if angle < 0. {
76 return angle + 2.0 * PI;
77 }
78 angle
79}
80
81pub fn parse_sexagesimal(angle: &str) -> f64 {
84 let mut dms = [0.0, 0.0, 0.0];
86 let mut angle = angle.trim();
87
88 let n = angle.len();
90 if n == 0 || angle == "NaN" {
91 return f64::NAN;
92 }
93
94 let mut postfix_sign = 1.0;
96 if "wWsSeEnN".contains(&angle[n - 1..]) {
97 if "wWsS".contains(&angle[n - 1..]) {
98 postfix_sign = -1.0;
99 }
100 angle = &angle[..n - 1];
101 }
102
103 for (i, element) in angle.split(':').enumerate() {
105 if i < 3 {
106 if let Ok(v) = element.parse::<f64>() {
107 dms[i] = v;
108 continue;
109 }
110 }
111 warn!("Cannot parse {angle} as a real number or sexagesimal angle");
113 return f64::NAN;
114 }
115
116 let sign = dms[0].signum() * postfix_sign;
119 sign * (dms[0].abs() + (dms[1] + dms[2] / 60.0) / 60.0)
120}
121
122#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn test_angular() {
130 assert_eq!(dms_to_dd(55, 30, 36.), 55.51);
132 assert_eq!(dm_to_dd(55, 30.60), 55.51);
133
134 assert!((iso_dm_to_dd(5530.60) - 55.51).abs() < 1e-10);
136 assert!((iso_dm_to_dd(15530.60) - 155.51).abs() < 1e-10);
137 assert!((iso_dm_to_dd(-15530.60) + 155.51).abs() < 1e-10);
138 assert!((iso_dms_to_dd(553036.0) - 55.51).abs() < 1e-10);
139 assert_eq!(dd_to_iso_dm(55.5025), 5530.15);
140 assert_eq!(dd_to_iso_dm(-55.5025), -5530.15);
141 assert_eq!(dd_to_iso_dms(55.5025), 553009.);
142 assert_eq!(dd_to_iso_dms(-55.51), -553036.);
143
144 assert_eq!(iso_dm_to_dd(5500.), 55.);
145 assert_eq!(iso_dm_to_dd(-5500.), -55.);
146 assert_eq!(iso_dm_to_dd(5530.60), -iso_dm_to_dd(-5530.60));
147 assert_eq!(iso_dms_to_dd(553036.), -iso_dms_to_dd(-553036.00));
148 }
149
150 #[test]
151 fn test_parse_sexagesimal() {
152 assert_eq!(1.51, parse_sexagesimal("1:30:36"));
153 assert_eq!(-1.51, parse_sexagesimal("-1:30:36"));
154 assert_eq!(1.51, parse_sexagesimal("1:30:36N"));
155 assert_eq!(-1.51, parse_sexagesimal("1:30:36S"));
156 assert_eq!(1.51, parse_sexagesimal("1:30:36e"));
157 assert_eq!(-1.51, parse_sexagesimal("1:30:36w"));
158 assert!(parse_sexagesimal("q1:30:36w").is_nan());
159 }
160}