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))]
21pub struct Point<A: Angle> {
23 lat: Latitude<A>,
24 lon: Longitude<A>,
25}
26
27impl<A: Angle> Point<A> {
28 pub fn new(lat: Latitude<A>, lon: Longitude<A>) -> Self {
30 Self { lat, lon }
31 }
32
33 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 pub fn north_pole() -> Self {
50 Self::new(North.into(), Longitude::prime())
52 }
53
54 pub fn south_pole() -> Self {
56 Self::new(South.into(), Longitude::prime())
58 }
59
60 pub fn is_pole(&self) -> bool {
63 self.lat.is_pole()
64 }
65
66 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 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}