1use crate::{
4 Error, Latitude, Longitude,
5 fmt::{FormatKind, FormatOptions, Formatter},
6 lat::EQUATOR,
7 long::INTERNATIONAL_REFERENCE_MERIDIAN,
8 parse::{self, Parsed},
9};
10use core::{
11 fmt::{Debug, Display, Write},
12 str::FromStr,
13};
14
15#[cfg(feature = "serde")]
16use serde::{Deserialize, Serialize};
17
18#[cfg(feature = "geojson")]
19use crate::Angle;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
40#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
41pub struct Coordinate {
42 lat: Latitude, long: Longitude, }
45
46pub const GEO_URL_SCHEME: &str = "geo";
54
55#[cfg(feature = "geojson")]
56pub const GEOJSON_TYPE_FIELD: &str = "type";
57#[cfg(feature = "geojson")]
58pub const GEOJSON_COORDINATES_FIELD: &str = "coordinates";
59#[cfg(feature = "geojson")]
60pub const GEOJSON_POINT_TYPE: &str = "Point";
61
62#[cfg(not(feature = "3d"))]
67#[macro_export]
68macro_rules! coord {
69 ($lat:expr ; $lon:expr) => {
70 $crate::coord::Coordinate::new($lat, $lon)
71 };
72}
73
74#[cfg(feature = "3d")]
75#[macro_export]
76macro_rules! coord {
77 ($lat:expr ; $lon:expr) => {
78 $crate::coord::Coordinate::new($lat, $lon)
79 };
80 ($lat:expr ; $lon:expr ; $alt:expr) => {
81 $crate::alt::Coordinate3d::new_from($lat, $lon, $alt)
82 };
83}
84
85impl Default for Coordinate {
90 fn default() -> Self {
91 Self {
92 lat: EQUATOR,
93 long: INTERNATIONAL_REFERENCE_MERIDIAN,
94 }
95 }
96}
97
98impl From<(Latitude, Longitude)> for Coordinate {
99 fn from(value: (Latitude, Longitude)) -> Self {
100 Self::new(value.0, value.1)
101 }
102}
103
104impl From<Coordinate> for (Latitude, Longitude) {
105 fn from(value: Coordinate) -> Self {
106 (value.lat, value.long)
107 }
108}
109
110impl From<Latitude> for Coordinate {
111 fn from(value: Latitude) -> Self {
112 Self::new(value, Longitude::default())
113 }
114}
115
116impl From<Longitude> for Coordinate {
117 fn from(value: Longitude) -> Self {
118 Self::new(Latitude::default(), value)
119 }
120}
121
122impl FromStr for Coordinate {
123 type Err = Error;
124
125 fn from_str(s: &str) -> Result<Self, Self::Err> {
126 match parse::parse_str(s)? {
127 Parsed::Coordinate(coord) => Ok(coord),
128 _ => Err(Error::InvalidAngle(0.0, 0.0)),
129 }
130 }
131}
132
133impl Display for Coordinate {
134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139 let format = if f.alternate() {
140 FormatOptions::dms()
141 } else {
142 FormatOptions::decimal()
143 };
144 self.format(f, &format)
145 }
146}
147
148impl Formatter for Coordinate {
149 fn format<W: Write>(&self, f: &mut W, fmt: &FormatOptions) -> std::fmt::Result {
150 let kind = fmt.kind();
151 self.lat.format(f, fmt)?;
152 write!(f, ",{}", if kind == FormatKind::DmsBare { "" } else { " " })?;
153 self.long.format(f, fmt)
154 }
155}
156
157impl Coordinate {
158 pub const fn new(lat: Latitude, long: Longitude) -> Self {
172 Self { lat, long }
173 }
174
175 #[must_use]
177 pub const fn with_latitude(mut self, lat: Latitude) -> Self {
178 self.lat = lat;
179 self
180 }
181
182 #[must_use]
184 pub const fn with_longitude(mut self, long: Longitude) -> Self {
185 self.long = long;
186 self
187 }
188
189 #[must_use]
191 pub const fn latitude(&self) -> Latitude {
192 self.lat
193 }
194
195 #[must_use]
197 pub const fn φ(&self) -> Latitude {
198 self.lat
199 }
200
201 #[must_use]
203 pub const fn longitude(&self) -> Longitude {
204 self.long
205 }
206
207 #[must_use]
209 pub const fn λ(&self) -> Longitude {
210 self.long
211 }
212
213 #[must_use]
215 pub fn is_on_equator(&self) -> bool {
216 self.lat.is_on_equator()
217 }
218
219 #[must_use]
221 pub fn is_northern(&self) -> bool {
222 self.lat.is_northern()
223 }
224
225 #[must_use]
227 pub fn is_southern(&self) -> bool {
228 self.lat.is_southern()
229 }
230
231 #[must_use]
233 pub fn is_on_international_reference_meridian(&self) -> bool {
234 self.long.is_on_international_reference_meridian()
235 }
236
237 #[must_use]
239 pub fn is_western(&self) -> bool {
240 self.long.is_western()
241 }
242
243 #[must_use]
245 pub fn is_eastern(&self) -> bool {
246 self.long.is_eastern()
247 }
248
249 #[must_use]
265 pub fn to_url_string(&self) -> String {
266 format!(
267 "{}:{},{}",
268 GEO_URL_SCHEME,
269 self.lat.to_formatted_string(&FormatOptions::decimal()),
270 self.long.to_formatted_string(&FormatOptions::decimal())
271 )
272 }
273
274 #[must_use]
293 pub fn to_microformat_string(&self) -> String {
294 format!(
295 "<span class=\"latitude\">{}</span>; <span class=\"longitude\">{}</span>",
296 self.lat.to_formatted_string(&FormatOptions::decimal()),
297 self.long.to_formatted_string(&FormatOptions::decimal())
298 )
299 }
300}
301
302#[cfg(feature = "urn")]
303impl From<Coordinate> for url::Url {
304 fn from(coord: Coordinate) -> Self {
305 Self::parse(&coord.to_url_string()).unwrap()
306 }
307}
308
309#[cfg(feature = "urn")]
310impl TryFrom<url::Url> for Coordinate {
311 type Error = crate::Error;
312
313 fn try_from(url: url::Url) -> Result<Self, Self::Error> {
314 if url.scheme() != GEO_URL_SCHEME {
315 return Err(crate::Error::InvalidUrnScheme);
316 }
317 let path = url.path();
318 let parts: Vec<&str> = path.split(',').collect();
319 if parts.len() != 2 {
320 return Err(crate::Error::InvalidCoordinate);
321 }
322 let lat_val: f64 = parts[0]
323 .parse()
324 .map_err(|_| crate::Error::InvalidCoordinate)?;
325 let lon_val: f64 = parts[1]
326 .parse()
327 .map_err(|_| crate::Error::InvalidCoordinate)?;
328 let lat = Latitude::try_from(lat_val).map_err(|_| crate::Error::InvalidCoordinate)?;
329 let lon = Longitude::try_from(lon_val).map_err(|_| crate::Error::InvalidCoordinate)?;
330 Ok(Coordinate::new(lat, lon))
331 }
332}
333
334#[cfg(feature = "geojson")]
335impl From<Coordinate> for serde_json::Value {
336 fn from(coord: Coordinate) -> Self {
338 serde_json::json!({
339 GEOJSON_TYPE_FIELD: GEOJSON_POINT_TYPE,
340 GEOJSON_COORDINATES_FIELD: [
341 coord.lat.as_float().0,
342 coord.long.as_float().0
343 ]
344 })
345 }
346}
347
348#[cfg(feature = "geojson")]
349impl TryFrom<serde_json::Value> for Coordinate {
350 type Error = crate::Error;
351
352 fn try_from(value: serde_json::Value) -> Result<Self, Self::Error> {
353 if value[GEOJSON_TYPE_FIELD] != GEOJSON_POINT_TYPE {
354 return Err(crate::Error::InvalidCoordinate);
355 }
356 let coords = value[GEOJSON_COORDINATES_FIELD]
357 .as_array()
358 .ok_or(crate::Error::InvalidCoordinate)?;
359 if coords.len() != 2 {
360 return Err(crate::Error::InvalidCoordinate);
361 }
362 let lat_val: f64 = coords[0]
363 .as_f64()
364 .ok_or(crate::Error::InvalidNumericFormat(coords[0].to_string()))?;
365 let lon_val: f64 = coords[1]
366 .as_f64()
367 .ok_or(crate::Error::InvalidNumericFormat(coords[1].to_string()))?;
368 let lat = Latitude::try_from(lat_val)?;
369 let lon = Longitude::try_from(lon_val)?;
370 Ok(Coordinate::new(lat, lon))
371 }
372}