Skip to main content

lat_long/
inner.rs

1//! This module provides the [`Angle`] type, and associated constants.
2
3use crate::Error;
4use ordered_float::OrderedFloat;
5
6// ---------------------------------------------------------------------------
7// Public Constants
8// ---------------------------------------------------------------------------
9
10pub const ZERO: OrderedFloat<f64> = OrderedFloat(0.0);
11
12// ---------------------------------------------------------------------------
13// Public Functions
14// ---------------------------------------------------------------------------
15
16const MINUTES_PER_DEGREE: f64 = 60.0;
17const SECONDS_PER_MINUTE: f64 = 60.0;
18const SECONDS_PER_DEGREE: f64 = MINUTES_PER_DEGREE * SECONDS_PER_MINUTE;
19
20/// Construct a new `Angle` from degrees, minutes, and seconds.
21///
22///
23pub(crate) const fn from_degrees_minutes_seconds(
24    degrees: i32,
25    minutes: u32,
26    seconds: f32,
27) -> Result<OrderedFloat<f64>, Error> {
28    if minutes >= MINUTES_PER_DEGREE as u32 {
29        return Err(Error::InvalidMinutes(minutes));
30    }
31    if seconds.is_sign_negative() || seconds >= SECONDS_PER_MINUTE as f32 {
32        return Err(Error::InvalidSeconds(seconds));
33    }
34    Ok(OrderedFloat(to_decimal_degrees(degrees, minutes, seconds)))
35}
36
37/// Convert a (degrees, minutes, seconds) tuple into a decimal-degree float.
38///
39/// For negative locations (south latitude / west longitude), `degrees` is
40/// negative and minutes/seconds are always non-negative. The formula applied
41/// is `sign(degrees) × (|degrees| + minutes/60 + seconds/3600)`.
42pub(crate) const fn to_decimal_degrees(degrees: i32, minutes: u32, seconds: f32) -> f64 {
43    let abs_degs = degrees.abs() as f64;
44    let fmins = minutes as f64 / MINUTES_PER_DEGREE;
45    let fsecs = seconds as f64 / SECONDS_PER_DEGREE;
46    let float = abs_degs + fmins + fsecs;
47    // Restore the sign of the original degrees component.
48    let float = if degrees.is_negative() { -float } else { float };
49    float
50}
51
52/// Decompose a decimal-degree float into (degrees, minutes, seconds).
53///
54/// The sign is carried only in `degrees`; `minutes` and `seconds` are always
55/// non-negative.
56/// Decompose this angle into `(degrees, minutes, seconds)`. The sign is
57/// carried only in `degrees`; `minutes` and `seconds` are always non-negative.
58pub(crate) const fn to_degrees_minutes_seconds(angle: OrderedFloat<f64>) -> (i32, u32, f32) {
59    let float = angle.0;
60    let negative = float < 0.0;
61    let abs = if negative { -float } else { float };
62    let degrees_abs = abs.trunc() as i32;
63    let remainder = abs.fract() * MINUTES_PER_DEGREE;
64    let minutes = remainder.trunc() as u32;
65    let seconds = (remainder.fract() * SECONDS_PER_MINUTE) as f32;
66    let degrees = if negative { -degrees_abs } else { degrees_abs };
67    (degrees, minutes, seconds)
68}