1use crate::{
4 Angle, Error,
5 fmt::{FormatOptions, Formatter, formatter_impl},
6 inner,
7 parse::{self, Parsed, Value},
8};
9use core::{
10 fmt::{Debug, Display, Write},
11 str::FromStr,
12};
13use ordered_float::OrderedFloat;
14
15#[cfg(feature = "serde")]
16use serde::{Deserialize, Serialize};
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
43#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
44pub struct Latitude(OrderedFloat<f64>);
45
46pub const NORTH_POLE: Latitude = Latitude(OrderedFloat(LATITUDE_LIMIT));
52
53pub const ARCTIC_CIRCLE: Latitude = Latitude(OrderedFloat(66.5));
58
59pub const TROPIC_OF_CANCER: Latitude = Latitude(OrderedFloat(23.5));
64
65pub const EQUATOR: Latitude = Latitude(inner::ZERO);
70
71pub const TROPIC_OF_CAPRICORN: Latitude = Latitude(OrderedFloat(-23.5));
76
77pub const ANTARCTIC_CIRCLE: Latitude = Latitude(OrderedFloat(-66.5));
82
83pub const SOUTH_POLE: Latitude = Latitude(OrderedFloat(-LATITUDE_LIMIT));
85
86#[macro_export]
91macro_rules! lat {
92 (N $degrees:expr, $minutes:expr, $seconds:expr) => {
93 lat!($degrees.abs(), $minutes, $seconds).unwrap()
94 };
95 (S $degrees:expr, $minutes:expr, $seconds:expr) => {
96 lat!(-$degrees.abs(), $minutes, $seconds).unwrap()
97 };
98 ($degrees:expr, $minutes:expr, $seconds:expr) => {
99 Latitude::new($degrees, $minutes, $seconds).unwrap()
100 };
101 (N $degrees:expr, $minutes:expr) => {
102 lat!($degrees.abs(), $minutes).unwrap()
103 };
104 (S $degrees:expr, $minutes:expr) => {
105 lat!(-$degrees.abs(), $minutes).unwrap()
106 };
107 ($degrees:expr, $minutes:expr) => {
108 lat!($degrees, $minutes, 0.0).unwrap()
109 };
110 (N $degrees:expr) => {
111 lat!($degrees.abs()).unwrap()
112 };
113 (S $degrees:expr) => {
114 lat!(-$degrees.abs()).unwrap()
115 };
116 ($degrees:expr) => {
117 lat!($degrees, 0, 0.0).unwrap()
118 };
119}
120
121const LATITUDE_LIMIT: f64 = 90.0;
126
127impl Default for Latitude {
128 fn default() -> Self {
129 EQUATOR
130 }
131}
132
133impl TryFrom<f64> for Latitude {
134 type Error = Error;
135
136 fn try_from(value: f64) -> Result<Self, Self::Error> {
137 Self::try_from(OrderedFloat(value))
138 }
139}
140
141impl TryFrom<OrderedFloat<f64>> for Latitude {
142 type Error = Error;
143
144 fn try_from(value: OrderedFloat<f64>) -> Result<Self, Self::Error> {
145 if value.0 < -LATITUDE_LIMIT || value.0 > LATITUDE_LIMIT {
146 return Err(Error::InvalidAngle(value.into_inner(), LATITUDE_LIMIT));
147 }
148 Ok(Self(value))
149 }
150}
151
152impl From<Latitude> for OrderedFloat<f64> {
153 fn from(value: Latitude) -> Self {
154 value.0.into()
155 }
156}
157
158impl From<Latitude> for f64 {
159 fn from(value: Latitude) -> Self {
160 value.0.into()
161 }
162}
163
164impl FromStr for Latitude {
165 type Err = Error;
166
167 fn from_str(s: &str) -> Result<Self, Self::Err> {
168 match parse::parse_str(s)? {
169 Parsed::Angle(Value::Unknown(decimal)) => Self::try_from(decimal),
170 Parsed::Angle(Value::Latitude(lat)) => Ok(lat),
171 _ => Err(Error::InvalidAngle(0.0, 0.0)),
172 }
173 }
174}
175
176impl Display for Latitude {
177 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
180 if f.alternate() {
181 let mut buf = String::new();
182 self.format(&mut buf, &FormatOptions::dms_signed())?;
183 f.write_str(&buf)
184 } else {
185 Display::fmt(&(self.0), f)
186 }
187 }
188}
189
190impl Formatter for Latitude {
191 fn format<W: Write>(&self, f: &mut W, fmt: &FormatOptions) -> std::fmt::Result {
192 let fmt = (*fmt).with_labels(('N', 'S'));
193 formatter_impl(self.0, f, &fmt)
194 }
195}
196
197impl Angle for Latitude {
198 const MIN: Self = Self(OrderedFloat(-LATITUDE_LIMIT));
199 const MAX: Self = Self(OrderedFloat(LATITUDE_LIMIT));
200
201 fn new(degrees: i32, minutes: u32, seconds: f32) -> Result<Self, Error> {
202 if degrees < Self::MIN.as_float().0 as i32 || degrees > Self::MAX.as_float().0 as i32 {
203 return Err(Error::InvalidLatitudeDegrees(degrees));
204 }
205 let float = inner::from_degrees_minutes_seconds(degrees, minutes, seconds)?;
210 Self::try_from(float).map_err(|_| Error::InvalidLatitudeDegrees(degrees))
211 }
212
213 fn as_float(&self) -> OrderedFloat<f64> {
214 self.0
215 }
216}
217
218impl Latitude {
219 #[must_use]
221 pub fn is_on_equator(&self) -> bool {
222 self.is_zero()
223 }
224
225 #[must_use]
227 pub fn is_northern(&self) -> bool {
228 self.is_nonzero_positive()
229 }
230
231 #[must_use]
233 pub fn is_southern(&self) -> bool {
234 self.is_nonzero_negative()
235 }
236
237 #[must_use]
239 pub fn is_arctic(&self) -> bool {
240 *self >= ARCTIC_CIRCLE
241 }
242
243 #[must_use]
245 pub fn is_antarctic(&self) -> bool {
246 *self <= ANTARCTIC_CIRCLE
247 }
248
249 #[must_use]
254 pub fn is_tropic_of_cancer(&self) -> bool {
255 *self >= TROPIC_OF_CANCER
256 }
257
258 #[must_use]
260 pub fn is_tropic_of_capricorn(&self) -> bool {
261 *self <= TROPIC_OF_CAPRICORN
262 }
263
264 #[must_use]
271 pub fn is_tropical(&self) -> bool {
272 self.is_tropic_of_cancer() || self.is_tropic_of_capricorn()
273 }
274
275 #[must_use]
278 pub fn is_polar(&self) -> bool {
279 self.is_arctic() || self.is_antarctic()
280 }
281}