Skip to main content

lat_long/
inner.rs

1use crate::Error;
2use ordered_float::OrderedFloat;
3
4// ---------------------------------------------------------------------------
5// Constants
6// ---------------------------------------------------------------------------
7
8pub const ZERO: OrderedFloat<f64> = OrderedFloat(0.0);
9
10// ---------------------------------------------------------------------------
11// Functions
12// ---------------------------------------------------------------------------
13
14const MINUTES_PER_DEGREE: f64 = 60.0;
15const SECONDS_PER_MINUTE: f64 = 60.0;
16const SECONDS_PER_DEGREE: f64 = MINUTES_PER_DEGREE * SECONDS_PER_MINUTE;
17
18///
19/// Construct a new `Angle` from degrees, minutes, and seconds.
20///
21pub(crate) const fn from_degrees_minutes_seconds(
22    degrees: i32,
23    minutes: u32,
24    seconds: f32,
25) -> Result<OrderedFloat<f64>, Error> {
26    if minutes >= MINUTES_PER_DEGREE as u32 {
27        return Err(Error::InvalidMinutes(minutes));
28    }
29    if seconds.is_sign_negative() || seconds >= SECONDS_PER_MINUTE as f32 {
30        return Err(Error::InvalidSeconds(seconds));
31    }
32    Ok(OrderedFloat(to_decimal_degrees(degrees, minutes, seconds)))
33}
34
35///
36/// Convert a (degrees, minutes, seconds) tuple into a decimal-degree float.
37///
38/// For negative locations (south latitude / west longitude), `degrees` is
39/// negative and minutes/seconds are always non-negative. The formula applied
40/// is `sign(degrees) × (|degrees| + minutes/60 + seconds/3600)`.
41///
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    if degrees.is_negative() { -float } else { float }
49}
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.
58///
59pub(crate) const fn to_degrees_minutes_seconds(angle: OrderedFloat<f64>) -> (i32, u32, f32) {
60    let float = angle.0;
61    let negative = float < 0.0;
62    let abs = if negative { -float } else { float };
63    let degrees_abs = abs.trunc() as i32;
64    let remainder = abs.fract() * MINUTES_PER_DEGREE;
65    let minutes = remainder.trunc() as u32;
66    let seconds = (remainder.fract() * SECONDS_PER_MINUTE) as f32;
67    let degrees = if negative { -degrees_abs } else { degrees_abs };
68    (degrees, minutes, seconds)
69}