1use crate::{
4 Error, Latitude, Longitude,
5 fmt::{FormatKind, FormatOptions, Formatter},
6 parse::{self, Parsed},
7};
8use core::{
9 fmt::{Debug, Display, Write},
10 str::FromStr,
11};
12
13#[cfg(feature = "serde")]
14use serde::{Deserialize, Serialize};
15
16#[cfg(feature = "geojson")]
17use crate::Angle;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
38#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
39pub struct Coordinate {
40 lat: Latitude, long: Longitude, }
43
44pub const GEO_URL_SCHEME: &str = "geo";
52
53#[cfg(feature = "geojson")]
54pub const GEOJSON_TYPE_FIELD: &str = "type";
55#[cfg(feature = "geojson")]
56pub const GEOJSON_COORDINATES_FIELD: &str = "coordinates";
57#[cfg(feature = "geojson")]
58pub const GEOJSON_POINT_TYPE: &str = "Point";
59
60#[macro_export]
65macro_rules! coord {
66 ($lat:expr ; $lon:expr) => {
67 Coordinate::new($lat, $lon)
68 };
69}
70
71impl From<(Latitude, Longitude)> for Coordinate {
76 fn from(value: (Latitude, Longitude)) -> Self {
77 Self::new(value.0, value.1)
78 }
79}
80
81impl From<Coordinate> for (Latitude, Longitude) {
82 fn from(value: Coordinate) -> Self {
83 (value.lat, value.long)
84 }
85}
86
87impl From<Latitude> for Coordinate {
88 fn from(value: Latitude) -> Self {
89 Self::new(value, Longitude::default())
90 }
91}
92
93impl From<Longitude> for Coordinate {
94 fn from(value: Longitude) -> Self {
95 Self::new(Latitude::default(), value)
96 }
97}
98
99impl FromStr for Coordinate {
100 type Err = Error;
101
102 fn from_str(s: &str) -> Result<Self, Self::Err> {
103 match parse::parse_str(s)? {
104 Parsed::Coordinate(coord) => Ok(coord),
105 _ => Err(Error::InvalidAngle(0.0, 0.0)),
106 }
107 }
108}
109
110impl Display for Coordinate {
111 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116 let format = if f.alternate() {
117 FormatOptions::dms()
118 } else {
119 FormatOptions::decimal()
120 };
121 self.format(f, &format)
122 }
123}
124
125impl Formatter for Coordinate {
126 fn format<W: Write>(&self, f: &mut W, fmt: &FormatOptions) -> std::fmt::Result {
127 let kind = fmt.kind();
128 self.lat.format(f, fmt)?;
129 write!(f, ",{}", if kind == FormatKind::DmsBare { "" } else { " " })?;
130 self.long.format(f, fmt)
131 }
132}
133
134impl Coordinate {
135 pub const fn new(lat: Latitude, long: Longitude) -> Self {
149 Self { lat, long }
150 }
151
152 #[must_use]
154 pub const fn with_latitude(mut self, lat: Latitude) -> Self {
155 self.lat = lat;
156 self
157 }
158
159 #[must_use]
161 pub const fn with_longitude(mut self, long: Longitude) -> Self {
162 self.long = long;
163 self
164 }
165
166 #[must_use]
168 pub const fn latitude(&self) -> Latitude {
169 self.lat
170 }
171
172 #[must_use]
174 pub const fn φ(&self) -> Latitude {
175 self.lat
176 }
177
178 #[must_use]
180 pub const fn longitude(&self) -> Longitude {
181 self.long
182 }
183
184 #[must_use]
186 pub const fn λ(&self) -> Longitude {
187 self.long
188 }
189
190 #[must_use]
192 pub fn is_on_equator(&self) -> bool {
193 self.lat.is_on_equator()
194 }
195
196 #[must_use]
198 pub fn is_northern(&self) -> bool {
199 self.lat.is_northern()
200 }
201
202 #[must_use]
204 pub fn is_southern(&self) -> bool {
205 self.lat.is_southern()
206 }
207
208 #[must_use]
210 pub fn is_on_international_reference_meridian(&self) -> bool {
211 self.long.is_on_international_reference_meridian()
212 }
213
214 #[must_use]
216 pub fn is_western(&self) -> bool {
217 self.long.is_western()
218 }
219
220 #[must_use]
222 pub fn is_eastern(&self) -> bool {
223 self.long.is_eastern()
224 }
225
226 #[must_use]
242 pub fn to_url_string(&self) -> String {
243 format!(
244 "{}:{},{}",
245 GEO_URL_SCHEME,
246 self.lat.to_formatted_string(&FormatOptions::decimal()),
247 self.long.to_formatted_string(&FormatOptions::decimal())
248 )
249 }
250
251 #[must_use]
270 pub fn to_microformat_string(&self) -> String {
271 format!(
272 "<span class=\"latitude\">{}</span>; <span class=\"longitude\">{}</span>",
273 self.lat.to_formatted_string(&FormatOptions::decimal()),
274 self.long.to_formatted_string(&FormatOptions::decimal())
275 )
276 }
277}
278
279#[cfg(feature = "urn")]
280impl From<Coordinate> for url::Url {
281 fn from(coord: Coordinate) -> Self {
282 Self::parse(&coord.to_url_string()).unwrap()
283 }
284}
285
286#[cfg(feature = "urn")]
287impl TryFrom<url::Url> for Coordinate {
288 type Error = crate::Error;
289
290 fn try_from(url: url::Url) -> Result<Self, Self::Error> {
291 if url.scheme() != GEO_URL_SCHEME {
292 return Err(crate::Error::InvalidUrnScheme);
293 }
294 let path = url.path();
295 let parts: Vec<&str> = path.split(',').collect();
296 if parts.len() != 2 {
297 return Err(crate::Error::InvalidCoordinate);
298 }
299 let lat_val: f64 = parts[0]
300 .parse()
301 .map_err(|_| crate::Error::InvalidCoordinate)?;
302 let lon_val: f64 = parts[1]
303 .parse()
304 .map_err(|_| crate::Error::InvalidCoordinate)?;
305 let lat = Latitude::try_from(lat_val).map_err(|_| crate::Error::InvalidCoordinate)?;
306 let lon = Longitude::try_from(lon_val).map_err(|_| crate::Error::InvalidCoordinate)?;
307 Ok(Coordinate::new(lat, lon))
308 }
309}
310
311#[cfg(feature = "geojson")]
312impl From<Coordinate> for serde_json::Value {
313 fn from(coord: Coordinate) -> Self {
315 serde_json::json!({
316 GEOJSON_TYPE_FIELD: GEOJSON_POINT_TYPE,
317 GEOJSON_COORDINATES_FIELD: [
318 coord.lat.as_float().0,
319 coord.long.as_float().0
320 ]
321 })
322 }
323}
324
325#[cfg(feature = "geojson")]
326impl TryFrom<serde_json::Value> for Coordinate {
327 type Error = crate::Error;
328
329 fn try_from(value: serde_json::Value) -> Result<Self, Self::Error> {
330 if value[GEOJSON_TYPE_FIELD] != GEOJSON_POINT_TYPE {
331 return Err(crate::Error::InvalidCoordinate);
332 }
333 let coords = value[GEOJSON_COORDINATES_FIELD]
334 .as_array()
335 .ok_or(crate::Error::InvalidCoordinate)?;
336 if coords.len() != 2 {
337 return Err(crate::Error::InvalidCoordinate);
338 }
339 let lat_val: f64 = coords[0]
340 .as_f64()
341 .ok_or(crate::Error::InvalidNumericFormat(coords[0].to_string()))?;
342 let lon_val: f64 = coords[1]
343 .as_f64()
344 .ok_or(crate::Error::InvalidNumericFormat(coords[1].to_string()))?;
345 let lat = Latitude::try_from(lat_val)?;
346 let lon = Longitude::try_from(lon_val)?;
347 Ok(Coordinate::new(lat, lon))
348 }
349}