geod_types/coord/
point.rs

1use std::{
2    convert::{TryFrom, TryInto},
3    fmt,
4};
5
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8
9use crate::angle::Angle;
10
11use super::{
12    lat::{
13        Latitude,
14        Pole::{North, South},
15    },
16    lon::Longitude,
17};
18
19#[derive(Debug)]
20#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
21/// The point on the surface of an ellipsoid, represented as the pair (latitude, longitude)
22pub struct Point<A: Angle> {
23    lat: Latitude<A>,
24    lon: Longitude<A>,
25}
26
27impl<A: Angle> Point<A> {
28    /// Construct a point from the given latitude and longitude
29    pub fn new(lat: Latitude<A>, lon: Longitude<A>) -> Self {
30        Self { lat, lon }
31    }
32
33    /// Construct a point on the surface with some numeric information about
34    /// latitude and longitude.
35    ///
36    /// # Errors
37    /// An overflow of some kind can appear when constructing latitude or longitude from the given numbers.
38    pub fn with_coordinates<Lat, Lon>(lat: Lat, lon: Lon) -> Result<Self, A::NumErr>
39    where
40        Latitude<A>: TryFrom<Lat, Error = A::NumErr>,
41        Longitude<A>: TryFrom<Lon, Error = A::NumErr>,
42    {
43        let lat = lat.try_into()?;
44        let lon = lon.try_into()?;
45        Ok(Self { lat, lon })
46    }
47
48    /// Construct a north pole point (lat=90, lon=0 (by convention)).
49    pub fn north_pole() -> Self {
50        // All longitude values reach singularity on a pole, so put it zero
51        Self::new(North.into(), Longitude::prime())
52    }
53
54    /// Construct a south pole point (lat=-90, lon=0 (by convention)).
55    pub fn south_pole() -> Self {
56        // All longitude values reach singularity on a pole, so put it zero
57        Self::new(South.into(), Longitude::prime())
58    }
59
60    /// Is the point represents a pole?
61    /// All the longitudes at pole are singular, so the longitude of the pole can be any meridian.
62    pub fn is_pole(&self) -> bool {
63        self.lat.is_pole()
64    }
65
66    /// The diametrically opposite point
67    pub fn antipodal(&self) -> Self {
68        Self {
69            lat: -self.lat,
70            lon: self.lon.opposite(),
71        }
72    }
73}
74
75impl<A: Angle> PartialEq for Point<A> {
76    fn eq(&self, other: &Self) -> bool {
77        if self.lat == other.lat {
78            // meridians at the poles do not matter
79            if self.lat.is_pole() {
80                return true;
81            }
82
83            if self.lon == other.lon {
84                return true;
85            }
86        }
87
88        false
89    }
90}
91
92impl<A: Angle> fmt::Display for Point<A>
93where
94    A: fmt::Display,
95{
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        if f.alternate() {
98            write!(f, "Lat: {:#}, Long: {:#}", self.lat, self.lon)
99        } else {
100            write!(f, "({},{})", self.lat, self.lon)
101        }
102    }
103}
104
105#[cfg(test)]
106mod tests_accur {
107    use crate::angle::{dms_dd::AccurateDegree, AngleNames};
108
109    use super::super::{
110        lon::RotationalDirection::{East, West},
111        AngleAndDirection,
112    };
113    use super::*;
114
115    #[test]
116    fn south_pole() {
117        let sp = Point::<AccurateDegree>::south_pole();
118        assert_eq!(sp.lat.hemisphere(), Some(South));
119        assert!(sp.lat.angle_from_equator().is_right());
120
121        assert!(sp.lon.angle().is_zero());
122        assert!(sp.lon.direction().is_none());
123
124        assert_eq!(format!("{}", sp), "(-90°,0°)");
125        assert_eq!(format!("{:#}", sp), "Lat: 90°S, Long: 0°");
126    }
127
128    #[test]
129    fn origin_point() {
130        let origin = Point::<AccurateDegree>::new(Latitude::equator(), Longitude::prime());
131        assert!(origin.lat.hemisphere().is_none());
132        assert!(origin.lat.angle_from_equator().is_zero());
133
134        assert!(origin.lon.angle().is_zero());
135        assert!(origin.lon.direction().is_none());
136
137        assert_eq!(format!("{}", origin), "(0°,0°)");
138        assert_eq!(format!("{:#}", origin), "Lat: 0°, Long: 0°");
139    }
140
141    #[test]
142    fn north_east() {
143        let saint_petersburg = Point::new(
144            Latitude::with_angle_and_direction(
145                AccurateDegree::with_dms(59, 56, 15, 0).unwrap(),
146                North,
147            )
148            .unwrap(),
149            Longitude::east((30, 18, 31, 0)).unwrap(),
150        );
151        let saint_petersburg2 = Point::with_coordinates([59, 56, 15], (30, 18, 31)).unwrap();
152        assert_eq!(saint_petersburg, saint_petersburg2);
153
154        assert_eq!(saint_petersburg.lat.hemisphere(), Some(North));
155        assert_eq!(
156            saint_petersburg.lat.angle_from_equator(),
157            AccurateDegree::with_dms(59, 56, 15, 0).unwrap()
158        );
159
160        assert_eq!(
161            saint_petersburg.lon.angle(),
162            AccurateDegree::with_dms(30, 18, 31, 0).unwrap()
163        );
164        assert_eq!(saint_petersburg.lon.direction(), Some(East));
165
166        assert_eq!(format!("{}", saint_petersburg), "(59.937500°,30.308611°)");
167        assert_eq!(
168            format!("{:#}", saint_petersburg),
169            "Lat: 59°56′15″N, Long: 30°18′31″E"
170        );
171    }
172
173    #[test]
174    fn south_west() {
175        let santiago = Point::new(
176            Latitude::with_angle_and_direction(
177                AccurateDegree::with_dms(33, 27, 0, 0).unwrap(),
178                South,
179            )
180            .unwrap(),
181            Longitude::west([70, 40, 0, 0]).unwrap(),
182        );
183        let santiago2 = Point::with_coordinates((-33, 27), [-70, 40]).unwrap();
184        assert_eq!(santiago, santiago2);
185
186        assert_eq!(santiago.lat.hemisphere(), Some(South));
187        assert_eq!(
188            santiago.lat.angle_from_equator(),
189            AccurateDegree::with_dms(33, 27, 0, 0).unwrap()
190        );
191
192        assert_eq!(
193            santiago.lon.angle(),
194            AccurateDegree::with_dms(70, 40, 0, 0).unwrap()
195        );
196        assert_eq!(santiago.lon.direction(), Some(West));
197
198        assert_eq!(format!("{}", santiago), "(-33.450000°,-70.666667°)");
199        assert_eq!(format!("{:#}", santiago), "Lat: 33°27′S, Long: 70°40′W");
200    }
201
202    #[test]
203    fn lat3_long4() {
204        let point =
205            Point::<AccurateDegree>::with_coordinates((-33, 27, 44), [70, 40, 15, 76]).unwrap();
206        assert_eq!(point.lat.hemisphere(), Some(South));
207        assert_eq!(
208            point.lat.angle_from_equator(),
209            AccurateDegree::with_dms(33, 27, 44, 0).unwrap()
210        );
211
212        assert_eq!(
213            point.lon.angle(),
214            AccurateDegree::with_dms(70, 40, 15, 76).unwrap()
215        );
216        assert_eq!(point.lon.direction(), Some(East));
217
218        assert_eq!(format!("{}", point), "(-33.462222°,70.671044°)");
219        assert_eq!(
220            format!("{:#}", point),
221            "Lat: 33°27′44″S, Long: 70°40′15.76″E"
222        );
223    }
224
225    #[test]
226    fn lat4_long3() {
227        let point =
228            Point::<AccurateDegree>::with_coordinates((33, 27, 44, 33), [-167, 11, 2, 4]).unwrap();
229        assert_eq!(point.lat.hemisphere(), Some(North));
230        assert_eq!(
231            point.lat.angle_from_equator(),
232            AccurateDegree::with_dms(33, 27, 44, 33).unwrap()
233        );
234
235        assert_eq!(
236            point.lon.angle(),
237            AccurateDegree::with_dms(167, 11, 2, 4).unwrap()
238        );
239        assert_eq!(point.lon.direction(), Some(West));
240
241        assert_eq!(format!("{}", point), "(33.462314°,-167.183900°)");
242        assert_eq!(
243            format!("{:#}", point),
244            "Lat: 33°27′44.33″N, Long: 167°11′2.04″W"
245        );
246    }
247
248    #[test]
249    fn from_f64_east_long() {
250        let l = Longitude::<AccurateDegree>::try_from(66.914_142).unwrap();
251        assert_eq!(l.direction(), Some(East));
252        assert!(l
253            .angle()
254            .almost_equal(AccurateDegree::with_dms(66, 54, 50, 91).unwrap()));
255    }
256
257    #[test]
258    fn from_f64_west_long() {
259        let l: Longitude<_> = (-122.427_478_3).try_into().unwrap();
260        assert!(Longitude::with_angle_and_direction(
261            AccurateDegree::with_dms(122, 25, 38, 92).unwrap(),
262            West,
263        )
264        .unwrap()
265        .angle()
266        .almost_equal(l.angle()));
267    }
268}
269
270#[cfg(test)]
271mod tests_dec {
272    use crate::angle::{dd::DecimalDegree, AngleNames};
273
274    use super::super::{
275        lon::RotationalDirection::{East, West},
276        AngleAndDirection,
277    };
278    use super::*;
279
280    #[test]
281    fn south_pole() {
282        let sp = Point::<DecimalDegree>::south_pole();
283        assert_eq!(sp.lat.hemisphere(), Some(South));
284        assert!(sp.lat.angle_from_equator().is_right());
285
286        assert!(sp.lon.angle().is_zero());
287        assert!(sp.lon.direction().is_none());
288
289        assert_eq!(format!("{}", sp), "(-90°,0°)");
290        assert_eq!(format!("{:#}", sp), "Lat: 90°S, Long: 0°");
291    }
292
293    #[test]
294    fn origin_point() {
295        let origin = Point::<DecimalDegree>::new(Latitude::equator(), Longitude::prime());
296        assert!(origin.lat.hemisphere().is_none());
297        assert!(origin.lat.angle_from_equator().is_zero());
298
299        assert!(origin.lon.angle().is_zero());
300        assert!(origin.lon.direction().is_none());
301
302        assert_eq!(format!("{}", origin), "(0°,0°)");
303        assert_eq!(format!("{:#}", origin), "Lat: 0°, Long: 0°");
304    }
305
306    #[test]
307    fn north_east() {
308        let saint_petersburg = Point::new(
309            Latitude::with_angle_and_direction(
310                DecimalDegree::with_dms(59, 56, 15, 0).unwrap(),
311                North,
312            )
313            .unwrap(),
314            Longitude::east((30, 18, 31, 0)).unwrap(),
315        );
316        let saint_petersburg2 = Point::with_coordinates([59, 56, 15], (30, 18, 31)).unwrap();
317        assert_eq!(saint_petersburg, saint_petersburg2);
318
319        assert_eq!(saint_petersburg.lat.hemisphere(), Some(North));
320        assert_eq!(
321            saint_petersburg.lat.angle_from_equator(),
322            DecimalDegree::with_dms(59, 56, 15, 0).unwrap()
323        );
324
325        assert_eq!(
326            saint_petersburg.lon.angle(),
327            DecimalDegree::with_dms(30, 18, 31, 0).unwrap()
328        );
329        assert_eq!(saint_petersburg.lon.direction(), Some(East));
330
331        assert_eq!(format!("{}", saint_petersburg), "(59.9375000°,30.3086111°)");
332        assert_eq!(
333            format!("{:#}", saint_petersburg),
334            "Lat: 59°56′15″N, Long: 30°18′31″E"
335        );
336    }
337
338    #[test]
339    fn south_west() {
340        let santiago = Point::new(
341            Latitude::with_angle_and_direction(
342                DecimalDegree::with_dms(33, 27, 0, 0).unwrap(),
343                South,
344            )
345            .unwrap(),
346            Longitude::west([70, 40, 0, 0]).unwrap(),
347        );
348        let santiago2 = Point::with_coordinates((-33, 27), [-70, 40]).unwrap();
349        assert_eq!(santiago, santiago2);
350
351        assert_eq!(santiago.lat.hemisphere(), Some(South));
352        assert_eq!(
353            santiago.lat.angle_from_equator(),
354            DecimalDegree::with_dms(33, 27, 0, 0).unwrap()
355        );
356
357        assert_eq!(
358            santiago.lon.angle(),
359            DecimalDegree::with_dms(70, 40, 0, 0).unwrap()
360        );
361        assert_eq!(santiago.lon.direction(), Some(West));
362
363        assert_eq!(format!("{}", santiago), "(-33.4500000°,-70.6666667°)");
364        assert_eq!(format!("{:#}", santiago), "Lat: 33°27′S, Long: 70°40′W");
365    }
366
367    #[test]
368    fn lat3_long4() {
369        let point =
370            Point::<DecimalDegree>::with_coordinates((-33, 27, 44), [70, 40, 15, 758]).unwrap();
371        assert_eq!(point.lat.hemisphere(), Some(South));
372        assert_eq!(
373            point.lat.angle_from_equator(),
374            DecimalDegree::with_dms(33, 27, 44, 0).unwrap()
375        );
376
377        assert_eq!(
378            point.lon.angle(),
379            DecimalDegree::with_dms(70, 40, 15, 758).unwrap()
380        );
381        assert_eq!(point.lon.direction(), Some(East));
382
383        assert_eq!(format!("{}", point), "(-33.4622222°,70.6710439°)");
384        assert_eq!(
385            format!("{:#}", point),
386            "Lat: 33°27′44″S, Long: 70°40′15.758″E"
387        );
388    }
389
390    #[test]
391    fn lat4_long3() {
392        let point =
393            Point::<DecimalDegree>::with_coordinates((33, 27, 44, 333), [-167, 11, 2, 45]).unwrap();
394        assert_eq!(point.lat.hemisphere(), Some(North));
395        assert_eq!(
396            point.lat.angle_from_equator(),
397            DecimalDegree::with_dms(33, 27, 44, 333).unwrap()
398        );
399
400        assert_eq!(
401            point.lon.angle(),
402            DecimalDegree::with_dms(167, 11, 2, 45).unwrap()
403        );
404        assert_eq!(point.lon.direction(), Some(West));
405
406        assert_eq!(format!("{}", point), "(33.4623147°,-167.1839014°)");
407        assert_eq!(
408            format!("{:#}", point),
409            "Lat: 33°27′44.333″N, Long: 167°11′2.045″W"
410        );
411    }
412
413    #[test]
414    fn from_f64_east_long() {
415        let l = Longitude::<DecimalDegree>::try_from(66.914_142).unwrap();
416        assert_eq!(l.direction(), Some(East));
417        assert!(l
418            .angle()
419            .almost_equal(DecimalDegree::with_dms(66, 54, 50, 911).unwrap()));
420    }
421
422    #[test]
423    fn from_f64_west_long() {
424        let l = (-122.427_478_3).try_into().unwrap();
425        assert_eq!(
426            Longitude::with_angle_and_direction(
427                DecimalDegree::with_dms(122, 25, 38, 922).unwrap(),
428                West,
429            )
430            .unwrap(),
431            l
432        );
433    }
434}
435
436#[cfg(test)]
437mod tests_arith {
438    use crate::{AccurateDegree, DecimalDegree};
439
440    use super::*;
441
442    #[test]
443    fn simple_antipodal() {
444        let p = Point::<DecimalDegree>::with_coordinates([-32, 46, 10], (3, 1, 11)).unwrap();
445        assert_eq!(
446            p.antipodal(),
447            Point::with_coordinates((32, 46, 10), (-176, 58, 49)).unwrap()
448        );
449    }
450
451    #[test]
452    fn poles_are_antipods() {
453        let np = Point::<AccurateDegree>::north_pole();
454        let sp = Point::south_pole();
455
456        assert_eq!(np.antipodal(), sp);
457        assert_eq!(sp.antipodal(), np);
458    }
459
460    #[test]
461    fn equator_antipodal_is_on_equator() {
462        let p = Point::<DecimalDegree>::with_coordinates(0, (15, 34)).unwrap();
463        assert_eq!(
464            p.antipodal(),
465            Point::with_coordinates(0, (-164, 26)).unwrap()
466        );
467    }
468}