Skip to main content

compass_data/
common_types.rs

1const FEET_TO_METERS: f64 = 0.3048;
2
3/// East North Elevation coordinates
4/// Always stored in meters
5#[derive(Clone, Copy, Debug, PartialEq)]
6#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
7pub struct EastNorthElevation {
8    pub easting: f64,
9    pub northing: f64,
10    pub up: f64,
11}
12
13impl EastNorthElevation {
14    #[must_use]
15    pub fn from_meters(easting: f64, northing: f64, up: f64) -> Self {
16        Self {
17            easting,
18            northing,
19            up,
20        }
21    }
22
23    #[must_use]
24    pub fn from_feet(easting: f64, northing: f64, up: f64) -> Self {
25        Self {
26            easting: easting * FEET_TO_METERS,
27            northing: northing * FEET_TO_METERS,
28            up: up * FEET_TO_METERS,
29        }
30    }
31}
32
33#[derive(Clone, Copy, Debug, PartialEq)]
34#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
35pub struct UtmLocation {
36    pub east_north_elevation: EastNorthElevation,
37    pub zone: u8,
38    pub convergence_angle: f64,
39}
40
41#[derive(Clone, Copy, Debug, PartialEq)]
42#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
43pub struct Date {
44    pub month: u8,
45    pub day: u8,
46    pub year: u16,
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52    use float_eq::assert_float_eq;
53
54    // EastNorthElevation tests
55    #[test]
56    fn test_from_meters_basic() {
57        let ene = EastNorthElevation::from_meters(100.0, 200.0, 300.0);
58        assert_float_eq!(ene.easting, 100.0, abs <= 1e-10);
59        assert_float_eq!(ene.northing, 200.0, abs <= 1e-10);
60        assert_float_eq!(ene.up, 300.0, abs <= 1e-10);
61    }
62
63    #[test]
64    fn test_from_meters_zero() {
65        let ene = EastNorthElevation::from_meters(0.0, 0.0, 0.0);
66        assert_float_eq!(ene.easting, 0.0, abs <= 1e-10);
67        assert_float_eq!(ene.northing, 0.0, abs <= 1e-10);
68        assert_float_eq!(ene.up, 0.0, abs <= 1e-10);
69    }
70
71    #[test]
72    fn test_from_meters_negative() {
73        let ene = EastNorthElevation::from_meters(-100.0, -200.0, -50.0);
74        assert_float_eq!(ene.easting, -100.0, abs <= 1e-10);
75        assert_float_eq!(ene.northing, -200.0, abs <= 1e-10);
76        assert_float_eq!(ene.up, -50.0, abs <= 1e-10);
77    }
78
79    #[test]
80    fn test_from_feet_basic() {
81        // 1 foot = 0.3048 meters
82        let ene = EastNorthElevation::from_feet(100.0, 200.0, 300.0);
83        assert_float_eq!(ene.easting, 30.48, abs <= 1e-10);
84        assert_float_eq!(ene.northing, 60.96, abs <= 1e-10);
85        assert_float_eq!(ene.up, 91.44, abs <= 1e-10);
86    }
87
88    #[test]
89    fn test_from_feet_zero() {
90        let ene = EastNorthElevation::from_feet(0.0, 0.0, 0.0);
91        assert_float_eq!(ene.easting, 0.0, abs <= 1e-10);
92        assert_float_eq!(ene.northing, 0.0, abs <= 1e-10);
93        assert_float_eq!(ene.up, 0.0, abs <= 1e-10);
94    }
95
96    #[test]
97    fn test_from_feet_negative() {
98        let ene = EastNorthElevation::from_feet(-10.0, -20.0, -30.0);
99        assert_float_eq!(ene.easting, -3.048, abs <= 1e-10);
100        assert_float_eq!(ene.northing, -6.096, abs <= 1e-10);
101        assert_float_eq!(ene.up, -9.144, abs <= 1e-10);
102    }
103
104    #[test]
105    fn test_from_feet_known_conversion() {
106        // 1 foot exactly
107        let ene = EastNorthElevation::from_feet(1.0, 1.0, 1.0);
108        assert_float_eq!(ene.easting, FEET_TO_METERS, abs <= 1e-10);
109        assert_float_eq!(ene.northing, FEET_TO_METERS, abs <= 1e-10);
110        assert_float_eq!(ene.up, FEET_TO_METERS, abs <= 1e-10);
111    }
112
113    #[test]
114    fn test_from_feet_large_values() {
115        // Large coordinate values typical in UTM
116        let ene = EastNorthElevation::from_feet(1_000_000.0, 4_000_000.0, 10_000.0);
117        assert_float_eq!(ene.easting, 304_800.0, abs <= 1e-6);
118        assert_float_eq!(ene.northing, 1_219_200.0, abs <= 1e-6);
119        assert_float_eq!(ene.up, 3_048.0, abs <= 1e-6);
120    }
121
122    // UtmLocation tests
123    #[test]
124    fn test_utm_location_creation() {
125        let ene = EastNorthElevation::from_meters(500_000.0, 4_500_000.0, 1500.0);
126        let utm = UtmLocation {
127            east_north_elevation: ene,
128            zone: 13,
129            convergence_angle: -0.5,
130        };
131        assert_eq!(utm.zone, 13);
132        assert_float_eq!(utm.convergence_angle, -0.5, abs <= 1e-10);
133        assert_float_eq!(utm.east_north_elevation.easting, 500_000.0, abs <= 1e-10);
134    }
135
136    #[test]
137    fn test_utm_location_zone_boundaries() {
138        let ene = EastNorthElevation::from_meters(0.0, 0.0, 0.0);
139
140        // Zone 1 (minimum valid zone)
141        let utm1 = UtmLocation {
142            east_north_elevation: ene,
143            zone: 1,
144            convergence_angle: 0.0,
145        };
146        assert_eq!(utm1.zone, 1);
147
148        // Zone 60 (maximum valid zone)
149        let utm60 = UtmLocation {
150            east_north_elevation: ene,
151            zone: 60,
152            convergence_angle: 0.0,
153        };
154        assert_eq!(utm60.zone, 60);
155    }
156
157    #[test]
158    fn test_utm_location_convergence_angle_range() {
159        let ene = EastNorthElevation::from_meters(500_000.0, 4_500_000.0, 0.0);
160
161        // Positive convergence angle
162        let utm_pos = UtmLocation {
163            east_north_elevation: ene,
164            zone: 13,
165            convergence_angle: 2.5,
166        };
167        assert_float_eq!(utm_pos.convergence_angle, 2.5, abs <= 1e-10);
168
169        // Negative convergence angle
170        let utm_neg = UtmLocation {
171            east_north_elevation: ene,
172            zone: 13,
173            convergence_angle: -2.5,
174        };
175        assert_float_eq!(utm_neg.convergence_angle, -2.5, abs <= 1e-10);
176    }
177
178    // Date tests
179    #[test]
180    fn test_date_creation() {
181        let date = Date {
182            month: 6,
183            day: 15,
184            year: 2024,
185        };
186        assert_eq!(date.month, 6);
187        assert_eq!(date.day, 15);
188        assert_eq!(date.year, 2024);
189    }
190
191    #[test]
192    fn test_date_january_first() {
193        let date = Date {
194            month: 1,
195            day: 1,
196            year: 2000,
197        };
198        assert_eq!(date.month, 1);
199        assert_eq!(date.day, 1);
200        assert_eq!(date.year, 2000);
201    }
202
203    #[test]
204    fn test_date_december_last() {
205        let date = Date {
206            month: 12,
207            day: 31,
208            year: 1999,
209        };
210        assert_eq!(date.month, 12);
211        assert_eq!(date.day, 31);
212        assert_eq!(date.year, 1999);
213    }
214
215    #[test]
216    fn test_date_leap_year() {
217        let date = Date {
218            month: 2,
219            day: 29,
220            year: 2024,
221        };
222        assert_eq!(date.month, 2);
223        assert_eq!(date.day, 29);
224        assert_eq!(date.year, 2024);
225    }
226}