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