utiles_core/
lnglat.rs

1//! Longitude and Latitude coordinates
2use std::fmt::Display;
3use std::str::FromStr;
4
5use crate::point::Point2d;
6use crate::traits::{Coord2dLike, IsOk, LngLatLike};
7use crate::UtilesCoreResult;
8
9/// `LngLat` contains a longitude and latitude as f64.
10#[derive(Debug, Clone, Copy, PartialEq)]
11pub struct LngLat {
12    /// 2d point - x -> longitude, y -> latitude
13    pub xy: Point2d<f64>,
14}
15
16impl LngLat {
17    /// Create a new `LngLat` from longitude & latitude.
18    #[must_use]
19    pub fn new(lng: f64, lat: f64) -> Self {
20        LngLat {
21            xy: Point2d::new(lng, lat),
22        }
23    }
24
25    /// Return the x/lng/lon/long/longitude value.
26    #[must_use]
27    pub fn lng(&self) -> f64 {
28        self.xy.x
29    }
30
31    /// Return the y/lat/latitude value.
32    #[must_use]
33    pub fn lat(&self) -> f64 {
34        self.xy.y
35    }
36
37    /// Return the x/lng/lon/long/longitude value.
38    #[must_use]
39    pub fn lon(&self) -> f64 {
40        self.xy.x
41    }
42
43    /// Return the y/lat/latitude value.
44    #[must_use]
45    pub fn x(&self) -> f64 {
46        self.xy.x
47    }
48
49    /// Return the y/lat/latitude value.
50    #[must_use]
51    pub fn y(&self) -> f64 {
52        self.xy.y
53    }
54
55    /// Return wrapped lnglat
56    #[must_use]
57    pub fn geo_wrap(&self) -> LngLat {
58        LngLat {
59            xy: Point2d::new(Self::geo_wrap_lng(self.xy.x), self.xy.y),
60        }
61    }
62
63    /// Return wrapped longitude
64    /// -180.0 <= lng <= 180.0
65    #[must_use]
66    pub fn geo_wrap_lng(lng: f64) -> f64 {
67        if lng < -180.0 {
68            lng + 360.0
69        } else if lng > 180.0 {
70            lng - 360.0
71        } else {
72            lng
73        }
74    }
75}
76
77impl Coord2dLike for LngLat {
78    fn x(&self) -> f64 {
79        self.xy.x
80    }
81
82    fn y(&self) -> f64 {
83        self.xy.y
84    }
85}
86
87impl LngLatLike for LngLat {
88    fn lng(&self) -> f64 {
89        self.xy.x
90    }
91
92    fn lat(&self) -> f64 {
93        self.xy.y
94    }
95}
96
97impl IsOk for LngLat {
98    fn ok(&self) -> UtilesCoreResult<Self> {
99        if self.xy.x >= -180.0
100            && self.xy.x <= 180.0
101            && self.xy.y >= -90.0
102            && self.xy.y <= 90.0
103        {
104            Ok(*self)
105        } else {
106            Err(crate::UtilesCoreError::InvalidLngLat(format!(
107                "Invalid LngLat coordinates: x: {}, y: {}",
108                self.xy.x, self.xy.y
109            )))
110        }
111    }
112}
113
114impl Display for LngLat {
115    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116        write!(f, "({}, {})", self.xy.x, self.xy.y)
117    }
118}
119
120impl IntoIterator for LngLat {
121    type Item = (f64, f64);
122    type IntoIter = std::iter::Once<Self::Item>;
123
124    fn into_iter(self) -> Self::IntoIter {
125        std::iter::once((self.xy.x, self.xy.y))
126    }
127}
128
129impl From<(f64, f64)> for LngLat {
130    fn from(xy: (f64, f64)) -> Self {
131        LngLat::new(xy.0, xy.1)
132    }
133}
134
135impl FromStr for LngLat {
136    type Err = std::num::ParseFloatError; // Change this to your correct Error type
137
138    fn from_str(s: &str) -> Result<Self, Self::Err> {
139        // Split the string, parse the parts into float and return LngLat.
140        let parts: Vec<&str> = s.split(',').collect();
141        // parse parts to float
142        let x = parts[0].parse::<f64>()?;
143        let y = parts[1].parse::<f64>()?;
144        Ok(LngLat::new(x, y))
145    }
146}
147
148/// Wraps a longitude value to the range [-180, 180].
149#[must_use]
150#[inline]
151pub fn wrap_lon(lon: f64) -> f64 {
152    (lon + 180.0).rem_euclid(360.0) - 180.0
153}