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#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
33pub struct Longitude(OrderedFloat<f64>);
34
35pub const INTERNATIONAL_REFERENCE_MERIDIAN: Longitude = Longitude(inner::ZERO);
41
42pub const ANTI_MERIDIAN: Longitude = Longitude(OrderedFloat(LONGITUDE_LIMIT));
44
45#[macro_export]
50macro_rules! long {
51 (E $degrees:expr, $minutes:expr, $seconds:expr) => {
52 long!($degrees.abs(), $minutes, $seconds).unwrap()
53 };
54 (W $degrees:expr, $minutes:expr, $seconds:expr) => {
55 long!(-$degrees.abs(), $minutes, $seconds).unwrap()
56 };
57 ($degrees:expr, $minutes:expr, $seconds:expr) => {
58 Longitude::new($degrees, $minutes, $seconds).unwrap()
59 };
60 (E $degrees:expr, $minutes:expr) => {
61 long!($degrees.abs(), $minutes).unwrap()
62 };
63 (W $degrees:expr, $minutes:expr) => {
64 long!(-$degrees.abs(), $minutes).unwrap()
65 };
66 ($degrees:expr, $minutes:expr) => {
67 long!($degrees, $minutes, 0.0).unwrap()
68 };
69 (E $degrees:expr) => {
70 long!($degrees.abs()).unwrap()
71 };
72 (W $degrees:expr) => {
73 long!(-$degrees.abs()).unwrap()
74 };
75 ($degrees:expr) => {
76 long!($degrees, 0, 0.0).unwrap()
77 };
78}
79
80const LONGITUDE_LIMIT: f64 = 180.0;
85
86impl Default for Longitude {
87 fn default() -> Self {
88 INTERNATIONAL_REFERENCE_MERIDIAN
89 }
90}
91
92impl TryFrom<f64> for Longitude {
93 type Error = Error;
94
95 fn try_from(value: f64) -> Result<Self, Self::Error> {
96 Self::try_from(OrderedFloat(value))
97 }
98}
99
100impl TryFrom<OrderedFloat<f64>> for Longitude {
101 type Error = Error;
102
103 fn try_from(value: OrderedFloat<f64>) -> Result<Self, Self::Error> {
104 if value.0 < -LONGITUDE_LIMIT || value.0 > LONGITUDE_LIMIT {
105 return Err(Error::InvalidAngle(value.into_inner(), LONGITUDE_LIMIT));
106 }
107 Ok(Self(value))
108 }
109}
110
111impl From<Longitude> for OrderedFloat<f64> {
112 fn from(value: Longitude) -> Self {
113 value.0.into()
114 }
115}
116
117impl From<Longitude> for f64 {
118 fn from(value: Longitude) -> Self {
119 value.0.into()
120 }
121}
122
123impl FromStr for Longitude {
124 type Err = Error;
125
126 fn from_str(s: &str) -> Result<Self, Self::Err> {
127 match parse::parse_str(s)? {
128 Parsed::Angle(Value::Unknown(decimal)) => Self::try_from(decimal),
129 Parsed::Angle(Value::Longitude(lon)) => Ok(lon),
130 _ => Err(Error::InvalidAngle(0.0, 0.0)),
131 }
132 }
133}
134
135impl Display for Longitude {
136 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139 if f.alternate() {
140 let mut buf = String::new();
141 self.format(&mut buf, &FormatOptions::dms_signed())?;
142 f.write_str(&buf)
143 } else {
144 Display::fmt(&(self.0), f)
145 }
146 }
147}
148
149impl Formatter for Longitude {
150 fn format<W: Write>(&self, f: &mut W, fmt: &FormatOptions) -> std::fmt::Result {
151 let fmt = (*fmt).with_labels(('E', 'W'));
152 formatter_impl(self.0, f, &fmt)
153 }
154}
155
156impl Angle for Longitude {
157 const MIN: Self = Self(OrderedFloat(-LONGITUDE_LIMIT));
158 const MAX: Self = Self(OrderedFloat(LONGITUDE_LIMIT));
159
160 fn new(degrees: i32, minutes: u32, seconds: f32) -> Result<Self, Error> {
161 if degrees < Self::MIN.as_float().0 as i32 || degrees > Self::MAX.as_float().0 as i32 {
162 return Err(Error::InvalidLongitudeDegrees(degrees));
163 }
164 let float = inner::from_degrees_minutes_seconds(degrees, minutes, seconds)?;
165 Self::try_from(float).map_err(|_| Error::InvalidLongitudeDegrees(degrees))
166 }
167
168 fn as_float(&self) -> OrderedFloat<f64> {
169 self.0
170 }
171}
172
173impl Longitude {
174 #[must_use]
176 pub fn is_on_international_reference_meridian(&self) -> bool {
177 self.is_zero()
178 }
179
180 #[must_use]
182 pub fn is_western(&self) -> bool {
183 self.is_nonzero_negative()
184 }
185
186 #[must_use]
188 pub fn is_eastern(&self) -> bool {
189 self.is_nonzero_positive()
190 }
191}