geoconv/
wgs84.rs

1// {{{ Copyright (c) Paul R. Tagliamonte <paultag@gmail.com>, 2023-2024
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19// THE SOFTWARE. }}}
20
21use crate::wgs::Wgs;
22
23/// [Wgs84] ("World Geodetic System 1984") is a standard published and maintained
24/// by the United States National Geospatial-Intelligence Agency. This is the
25/// CoordinateSystem used by most systems "by default", including GPS.
26///
27/// If you have a "Latitude and Longitude" without further information about
28/// the coordinate system, it's not a bad guess to try it out with [Wgs84] first.
29#[derive(Debug, Copy, Clone, PartialEq)]
30pub struct Wgs84 {}
31
32impl Wgs for Wgs84 {
33    const A: f64 = 6378137.0;
34    const B: f64 = 6356752.314245;
35}
36
37#[cfg(test)]
38mod tests {
39    use super::*;
40    use crate::{Aer, CoordinateSystem, Degrees, Enu, Lle, Meters, Wgs72, Xyz};
41
42    type Wgs84Lle = Lle<Wgs84, Degrees>;
43    type Wgs72Lle = Lle<Wgs72, Degrees>;
44
45    macro_rules! assert_in_eps {
46        ($x:expr, $y:expr, $d:expr) => {
47            if !($x - $y < $d && $y - $x < $d) {
48                panic!();
49            }
50        };
51    }
52
53    #[test]
54    fn lle_to_xyz() {
55        let g = Wgs84Lle::new(
56            Degrees::new(34.00000048),
57            Degrees::new(-117.3335693),
58            Meters::new(251.702),
59        );
60        let refx = Wgs84::lle_to_xyz(&g);
61        assert_in_eps!(-2430601.8, refx.x.as_float(), 0.1);
62        assert_in_eps!(-4702442.7, refx.y.as_float(), 0.1);
63        assert_in_eps!(3546587.4, refx.z.as_float(), 0.1);
64    }
65
66    #[test]
67    fn around_the_world() {
68        let refr = Wgs84Lle::new(
69            Degrees::new(38.897957),
70            Degrees::new(-77.036560),
71            Meters::new(30.0),
72        );
73
74        let position = Wgs84Lle::new(
75            Degrees::new(38.8709455),
76            Degrees::new(-77.0552551),
77            Meters::new(100.0),
78        );
79
80        let positionx = Wgs84::lle_to_xyz(&position);
81        let positionenu = Wgs84::xyz_to_enu(&refr, &positionx);
82        let positionxx1 = Wgs84::enu_to_xyz(&refr, &positionenu);
83        let position1: Wgs84Lle = Wgs84::xyz_to_lle(&positionxx1);
84
85        assert_in_eps!(
86            position.latitude.as_float(),
87            position1.latitude.as_float(),
88            1e-7
89        );
90        assert_in_eps!(
91            position.longitude.as_float(),
92            position1.longitude.as_float(),
93            1e-7
94        );
95        assert_in_eps!(
96            position.elevation.as_float(),
97            position1.elevation.as_float(),
98            1e-7
99        );
100    }
101
102    #[test]
103    fn lle_to_enu() {
104        let refr = Wgs84Lle::new(
105            Degrees::new(34.00000048),
106            Degrees::new(-117.3335693),
107            Meters::new(251.702),
108        );
109        let refx = Wgs84::lle_to_xyz(&refr);
110
111        let point = Xyz {
112            x: Meters::new(refx.x.as_float() + 1.0),
113            y: refx.y,
114            z: refx.z,
115        };
116        let pointenu = Wgs84::xyz_to_enu(&refr, &point);
117
118        assert_in_eps!(0.88834836, pointenu.east.as_float(), 0.1);
119        assert_in_eps!(0.25676467, pointenu.north.as_float(), 0.1);
120        assert_in_eps!(-0.38066927, pointenu.up.as_float(), 0.1);
121
122        let point = Xyz {
123            x: refx.x,
124            y: Meters::new(refx.y.as_float() + 1.0),
125            z: refx.z,
126        };
127        let pointenu = Wgs84::xyz_to_enu(&refr, &point);
128        assert_in_eps!(-0.45917011, pointenu.east.as_float(), 0.1);
129        assert_in_eps!(0.49675810, pointenu.north.as_float(), 0.1);
130        assert_in_eps!(-0.73647416, pointenu.up.as_float(), 0.1);
131
132        let point = Xyz {
133            x: refx.x,
134            y: refx.y,
135            z: Meters::new(refx.z.as_float() + 1.0),
136        };
137        let pointenu = Wgs84::xyz_to_enu(&refr, &point);
138        assert_eq!(0.0, pointenu.east.as_float());
139        assert_in_eps!(0.82903757, pointenu.north.as_float(), 0.1);
140        assert_in_eps!(0.55919291, pointenu.up.as_float(), 0.1);
141    }
142
143    #[test]
144    fn enu_aer_round_trip() {
145        let enu = Enu {
146            east: Meters::new(10.0),
147            north: Meters::new(20.0),
148            up: Meters::new(30.0),
149        };
150
151        let aer: Aer<Degrees> = enu.into();
152        let enu1: Enu = aer.into();
153
154        assert_in_eps!(10.0, enu1.east.as_float(), 1e-6);
155        assert_in_eps!(20.0, enu1.north.as_float(), 1e-6);
156        assert_in_eps!(30.0, enu1.up.as_float(), 1e-6);
157    }
158
159    #[test]
160    fn aer_enu_round_trip() {
161        let refr = Wgs84Lle::new(
162            Degrees::new(38.897957),
163            Degrees::new(-77.036560),
164            Meters::new(30.0),
165        );
166
167        let position = Wgs84Lle::new(
168            Degrees::new(38.8709455),
169            Degrees::new(-77.0552551),
170            Meters::new(30.0),
171        );
172
173        let positionx = Wgs84::lle_to_xyz(&position);
174        let positionenu = Wgs84::xyz_to_enu(&refr, &positionx);
175
176        let aed: Aer<Degrees> = positionenu.into();
177        let positionenu1: Enu = aed.into();
178
179        let positionx1 = Wgs84::enu_to_xyz(&refr, &positionenu1);
180        let position1: Wgs84Lle = Wgs84::xyz_to_lle(&positionx1);
181
182        assert_in_eps!(
183            position.latitude.as_float(),
184            position1.latitude.as_float(),
185            1e-7
186        );
187        assert_in_eps!(
188            position.longitude.as_float(),
189            position1.longitude.as_float(),
190            1e-7
191        );
192        assert_in_eps!(
193            position.elevation.as_float(),
194            position1.elevation.as_float(),
195            1e-7
196        );
197    }
198
199    #[test]
200    fn round_trip_wgs84_to_wgs72() {
201        let origin = Wgs84Lle::new(Degrees::new(10.0), Degrees::new(-20.0), Meters::new(30.0));
202        let translated: Wgs72Lle = origin.translate();
203        let round_trip: Wgs84Lle = translated.translate();
204
205        assert_in_eps!(
206            origin.latitude.as_float(),
207            round_trip.latitude.as_float(),
208            1e-7
209        );
210
211        assert_in_eps!(
212            origin.longitude.as_float(),
213            round_trip.longitude.as_float(),
214            1e-7
215        );
216
217        assert_in_eps!(
218            origin.elevation.as_float(),
219            round_trip.elevation.as_float(),
220            1e-7
221        );
222    }
223}
224
225// vim: foldmethod=marker