Skip to main content

lat_long/
lib.rs

1//! Geographic latitude and longitude coordinate types.
2//!
3//! This crate provides strongly-typed [`Latitude`], [`Longitude`], and [`Coordinate`] values that are validated on
4//! construction and carry their own display logic (decimal degrees **or** degrees–minutes–seconds). The goal is not
5//! to provide a single, large, and potentially unwieldy "geo" crate, but rather a collection of small, focused crates
6//! that can be used together or independently.
7//!
8//! ## Quick start
9//!
10//! ```rust
11//! use lat_long::{Angle, Coordinate, Latitude, Longitude};
12//!
13//! let lat = Latitude::new(48, 51, 29.6).expect("valid latitude");
14//! let lon = Longitude::new(2, 21, 7.6).expect("valid longitude");
15//! let paris = Coordinate::new(lat, lon);
16//!
17//! // Decimal-degree display (default)
18//! println!("{paris}");   // => 48.858222, 2.218778
19//! // Degrees–minutes–seconds display (alternate flag)
20//! println!("{paris:#}"); // => 48° 51' 29.6" N, 2° 21' 7.6" E
21//! ```
22//!
23//! ```rust
24//! use lat_long::{parse::{self, Parsed}, Coordinate};
25//!
26//! if let Ok(Parsed::Coordinate(london)) = parse::parse_str("51.522, -0.127") {
27//!     println!("{london}"); // => 51.522, -0.127
28//! }
29//! ```
30//!
31//! ```rust,ignore
32//! // Convert to URL, requires `url` feature flag
33//! let url = url::Url::from(paris);
34//! println!("{url}"); // => geo:48.858222,2.218778
35//! ```
36//!
37//! ```rust,ignore
38//! // Convert to JSON, requires `geojson` feature flag
39//! let json = serde_json::Value::from(paris);
40//! println!("{json}"); // => { "type": "Point", "coordinates": [48.858222,2.218778] }
41//! ```
42//!
43//! ## Formatting
44//!
45//! The [`fmt`] module provides functionality for formatting and parsing coordinates.
46//!
47//! | `FormatKind`    | Format String | Positive             | Negative             |
48//! |-----------------|---------------|----------------------|----------------------|
49//! | `Decimal`       | `{}`          | 48.858222            | -48.858222           |
50//! | `DmsSigned`     | `{:#}`        | 48° 51′ 29.600000″   | -48° 51′ 29.600000″  |
51//! | `DmsLabeled`    | N/A           | 48° 51′ 29.600000″ N | 48° 51′ 29.600000″ S |
52//! | `DmsBare`       | N/A           | +048:51:29.600000    | -048:51:29.600000    |
53//!
54//! Note that the `DmsBare` format is intended as a regular, easy-to-parse format for use in
55//! data files, rather than as a human-readable format. In it`s coordinate pair form, it is
56//! also the only format that does not allow whitespace around the comma separator.
57//!
58//! ## Parsing
59//!
60//! The [`parse`] module provides functionality for parsing coordinates. The parser accepts all of the
61//! formats described above. The parser is also used by the implementation of `FromStr` for `Latitude`,
62//! `Longitude`, and `Coordinate`.
63//!
64//! ## Feature flags
65#![doc = document_features::document_features!()]
66//! ## References
67//!
68//! * [Latitude and longitude](https://en.wikipedia.org/wiki/Geographic_coordinate_system#Latitude_and_longitude)
69//! * [WGS 84](https://en.wikipedia.org/wiki/World_Geodetic_System)
70
71use ordered_float::OrderedFloat;
72use std::fmt::{Debug, Display};
73use std::hash::Hash;
74
75// ---------------------------------------------------------------------------
76// Public Types
77// ---------------------------------------------------------------------------
78
79pub trait Angle:
80    Clone
81    + Copy
82    + Debug
83    + Default
84    + Display
85    + PartialEq
86    + Eq
87    + PartialOrd
88    + Ord
89    + Hash
90    + TryFrom<f64, Error = Error>
91    + TryFrom<OrderedFloat<f64>, Error = Error>
92    + Into<OrderedFloat<f64>>
93    + Into<f64>
94{
95    const MIN: Self;
96    const MAX: Self;
97
98    /// Construct a new angle from degrees, minutes, and seconds.
99    fn new(degrees: i32, minutes: u32, seconds: f32) -> Result<Self, Error>
100    where
101        Self: Sized;
102
103    fn as_float(&self) -> OrderedFloat<f64> {
104        (*self).into()
105    }
106
107    fn to_radians(&self) -> f64 {
108        self.as_float().0.to_radians()
109    }
110
111    fn from_radians(radians: f64) -> Result<Self, Error>
112    where
113        Self: Sized,
114    {
115        Self::try_from(OrderedFloat(radians.to_degrees()))
116    }
117
118    /// Returns `true` if the angle is exactly zero.
119    fn is_zero(&self) -> bool {
120        self.as_float() == inner::ZERO
121    }
122
123    /// Returns `true` if the angle is positive and non-zero.
124    fn is_nonzero_positive(&self) -> bool {
125        !self.is_zero() && self.as_float() > inner::ZERO
126    }
127
128    /// Returns `true` if the angle is negative and non-zero.
129    fn is_nonzero_negative(&self) -> bool {
130        !self.is_zero() && self.as_float() < inner::ZERO
131    }
132
133    /// The signed integer degrees component (carries the sign for negative angles).
134    fn degrees(&self) -> i32 {
135        inner::to_degrees_minutes_seconds(self.as_float()).0
136    }
137
138    /// The unsigned minutes component (always in `0..60`).
139    fn minutes(&self) -> u32 {
140        inner::to_degrees_minutes_seconds(self.as_float()).1
141    }
142
143    /// The unsigned seconds component (always in `0.0..60.0`).
144    fn seconds(&self) -> f32 {
145        inner::to_degrees_minutes_seconds(self.as_float()).2
146    }
147
148    fn abs(self) -> Self
149    where
150        Self: Sized,
151    {
152        Self::try_from(OrderedFloat(self.as_float().0.abs())).unwrap()
153    }
154
155    fn modulo_max(self) -> Self
156    where
157        Self: Sized,
158    {
159        Self::try_from(self.as_float() % Self::MAX.as_float()).unwrap()
160    }
161
162    /// Checked absolute value. Computes self.abs(), returning None if self == MIN.
163    fn checked_abs(self) -> Option<Self>
164    where
165        Self: Sized,
166    {
167        if self == Self::MIN {
168            None
169        } else {
170            Some(Self::try_from(OrderedFloat(self.as_float().0.abs())).unwrap())
171        }
172    }
173
174    /// Computes the absolute value of self.
175    ///
176    /// Returns a tuple of the absolute version of self along with a boolean indicating whether an overflow happened.
177    /// If self is the minimum value Self::MIN, then the minimum value will be returned again and true will be returned
178    /// for an overflow happening.
179    fn overflowing_abs(self) -> (Self, bool)
180    where
181        Self: Sized,
182    {
183        if self == Self::MIN {
184            (self, true)
185        } else {
186            (
187                Self::try_from(OrderedFloat(self.as_float().0.abs())).unwrap(),
188                false,
189            )
190        }
191    }
192
193    /// Saturating absolute value. Computes self.abs(), returning MAX if self == MIN instead of overflowing.
194    fn saturating_abs(self) -> Self
195    where
196        Self: Sized,
197    {
198        if self == Self::MIN {
199            Self::MAX
200        } else {
201            Self::try_from(OrderedFloat(self.as_float().abs())).unwrap()
202        }
203    }
204
205    /// Strict absolute value. Computes self.abs(), panicking if self == MIN.
206    fn strict_abs(self) -> Self
207    where
208        Self: Sized,
209    {
210        if self == Self::MIN {
211            panic!("attempt to take absolute value of the minimum value")
212        } else {
213            Self::try_from(OrderedFloat(self.as_float().0.abs())).unwrap()
214        }
215    }
216
217    /// Unchecked absolute value. Computes self.abs(), assuming overflow cannot occur.
218    ///
219    /// Calling x.unchecked_abs() is semantically equivalent to calling x.checked_abs().unwrap_unchecked().
220    ///
221    /// If you’re just trying to avoid the panic in debug mode, then do not use this. Instead, you’re looking for wrapping_abs.
222    fn unchecked_abs(self) -> Self
223    where
224        Self: Sized,
225    {
226        Self::try_from(OrderedFloat(self.as_float().0.abs())).unwrap()
227    }
228
229    /// Wrapping (modular) absolute value. Computes self.abs(), wrapping around at the boundary of the type.
230    ///
231    /// The only case where such wrapping can occur is when one takes the absolute value of the negative minimal
232    /// value for the type; this is a positive value that is too large to represent in the type. In such a case,
233    /// this function returns MIN itself.
234    fn wrapping_abs(self) -> Self
235    where
236        Self: Sized,
237    {
238        if self == Self::MIN {
239            Self::MIN
240        } else {
241            Self::try_from(OrderedFloat(self.as_float().0.abs())).unwrap()
242        }
243    }
244}
245
246// ---------------------------------------------------------------------------
247// Internal Modules
248// ---------------------------------------------------------------------------
249
250mod inner;
251pub mod parse;
252
253// ---------------------------------------------------------------------------
254// Public Modules & Exports
255// ---------------------------------------------------------------------------
256
257#[cfg(feature = "elevation")]
258pub mod elevation;
259#[cfg(feature = "elevation")]
260pub use elevation::{CoordinateWithElevation, Elevation};
261
262pub mod coord;
263pub use coord::Coordinate;
264pub mod error;
265pub use error::Error;
266pub mod fmt;
267pub mod latitude;
268pub use latitude::Latitude;
269pub mod longitude;
270pub use longitude::Longitude;