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