navcore_math/position.rs
1//! A geographic position.
2
3/// A position on the sphere, in degrees.
4///
5/// Latitude is positive north, longitude positive east, matching both the
6/// Signal K vocabulary the core adopts (`navigation.position`) and GeoJSON.
7/// Note the field order is lat-then-lon while GeoJSON coordinates are
8/// lon-then-lat; the constructor names the arguments so the swap has to be
9/// deliberate.
10#[derive(Debug, Clone, Copy, PartialEq)]
11pub struct Position {
12 /// Degrees north of the equator, `-90.0..=90.0`.
13 pub lat_deg: f64,
14 /// Degrees east of Greenwich, normally `-180.0..=180.0`.
15 pub lon_deg: f64,
16}
17
18impl Position {
19 /// A position from latitude and longitude in degrees.
20 #[must_use]
21 pub const fn new(lat_deg: f64, lon_deg: f64) -> Self {
22 Self { lat_deg, lon_deg }
23 }
24
25 /// True when both components are finite and in range.
26 ///
27 /// Worth calling on anything that came off a wire: a GPS that has lost
28 /// its fix is a far more common source of an out-of-range latitude than
29 /// a bug is.
30 #[must_use]
31 pub fn is_valid(&self) -> bool {
32 self.lat_deg.is_finite()
33 && self.lon_deg.is_finite()
34 && self.lat_deg >= -90.0
35 && self.lat_deg <= 90.0
36 && self.lon_deg >= -180.0
37 && self.lon_deg <= 180.0
38 }
39
40 pub(crate) fn lat_rad(&self) -> f64 {
41 self.lat_deg.to_radians()
42 }
43
44 pub(crate) fn lon_rad(&self) -> f64 {
45 self.lon_deg.to_radians()
46 }
47}
48
49#[cfg(test)]
50mod tests {
51 use super::*;
52
53 #[test]
54 fn validity_catches_what_a_lost_fix_produces() {
55 assert!(Position::new(45.0, 13.0).is_valid());
56 assert!(Position::new(-90.0, 180.0).is_valid());
57 assert!(!Position::new(91.0, 0.0).is_valid());
58 assert!(!Position::new(0.0, 181.0).is_valid());
59 assert!(!Position::new(f64::NAN, 0.0).is_valid());
60 }
61}