Skip to main content

irox_units/units/
length.rs

1// SPDX-License-Identifier: MIT
2// Copyright 2025 IROX Contributors
3//
4
5//!
6//! This module contains the basic types and conversions for the SI "Length" quantity
7use core::fmt::{Display, Formatter};
8
9use crate::units::{FromUnits, Unit};
10
11///
12/// Represents a specific length unit - SI or otherwise
13#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
14#[non_exhaustive]
15pub enum LengthUnits {
16    /// SI Base Unit for Length - Meters
17    #[default]
18    Meters,
19
20    /// SI Derived unit kilometers
21    Kilometers,
22
23    /// US Imperial "Foot"
24    Feet,
25
26    /// US Imperial "Mile"
27    Mile,
28
29    /// Nautical Mile, classically 1 arcminute on the Earth
30    NauticalMile,
31
32    /// The U.S. Survey Foot
33    USSurveyFoot,
34}
35macro_rules! from_units_length {
36    ($type:ident) => {
37        impl crate::units::FromUnits<$type> for LengthUnits {
38            fn from(&self, value: $type, source_unit: Self) -> $type {
39                match self {
40                    // target
41                    LengthUnits::Meters => match source_unit {
42                        // source
43                        LengthUnits::Meters => value as $type,
44                        LengthUnits::Feet => value * FEET_TO_METERS as $type,
45                        LengthUnits::Kilometers => value * KILOMETERS_TO_METERS as $type,
46                        LengthUnits::Mile => value * MILES_TO_METERS as $type,
47                        LengthUnits::NauticalMile => value * NAUTICAL_MILES_TO_METERS as $type,
48                        LengthUnits::USSurveyFoot => value * SURVEYFOOT_TO_METER as $type,
49                    },
50                    LengthUnits::Feet => match source_unit {
51                        LengthUnits::Meters => value * METERS_TO_FEET as $type,
52                        LengthUnits::Feet => value as $type,
53                        LengthUnits::Kilometers => {
54                            FromUnits::<$type>::from(&LengthUnits::Meters, value, source_unit)
55                                * METERS_TO_KILOMETERS as $type
56                        }
57                        LengthUnits::Mile => {
58                            FromUnits::<$type>::from(&LengthUnits::Meters, value, source_unit)
59                                * METERS_TO_MILES as $type
60                        }
61                        LengthUnits::NauticalMile => {
62                            FromUnits::<$type>::from(&LengthUnits::Meters, value, source_unit)
63                                * METERS_TO_NAUTICAL_MILE as $type
64                        }
65                        LengthUnits::USSurveyFoot => {
66                            value * (SURVEYFOOT_TO_METER * METERS_TO_FEET) as $type
67                        }
68                    },
69                    LengthUnits::Kilometers => match source_unit {
70                        LengthUnits::Meters => value * METERS_TO_KILOMETERS as $type,
71                        LengthUnits::Kilometers => value,
72                        LengthUnits::Feet => {
73                            value * (FEET_TO_METERS * METERS_TO_KILOMETERS) as $type
74                        }
75                        LengthUnits::Mile => {
76                            value * (MILES_TO_METERS * METERS_TO_KILOMETERS) as $type
77                        }
78                        LengthUnits::NauticalMile => {
79                            value * (NAUTICAL_MILES_TO_METERS * METERS_TO_KILOMETERS) as $type
80                        }
81                        LengthUnits::USSurveyFoot => {
82                            value * (SURVEYFOOT_TO_METER * METERS_TO_KILOMETERS) as $type
83                        }
84                    },
85                    LengthUnits::Mile => match source_unit {
86                        LengthUnits::Meters => value * METERS_TO_MILES as $type,
87                        LengthUnits::Kilometers => {
88                            value * (KILOMETERS_TO_METERS * METERS_TO_MILES) as $type
89                        }
90                        LengthUnits::Feet => value * (FEET_TO_METERS * METERS_TO_MILES) as $type,
91                        LengthUnits::Mile => value,
92                        LengthUnits::NauticalMile => {
93                            value * (NAUTICAL_MILES_TO_METERS * METERS_TO_MILES) as $type
94                        }
95                        LengthUnits::USSurveyFoot => {
96                            value * (SURVEYFOOT_TO_METER * METERS_TO_MILES) as $type
97                        }
98                    },
99                    LengthUnits::NauticalMile => match source_unit {
100                        LengthUnits::Meters => value * METERS_TO_NAUTICAL_MILE as $type,
101                        LengthUnits::Kilometers => {
102                            value * (KILOMETERS_TO_METERS * METERS_TO_NAUTICAL_MILE) as $type
103                        }
104                        LengthUnits::Feet => {
105                            value * (FEET_TO_METERS * METERS_TO_NAUTICAL_MILE) as $type
106                        }
107                        LengthUnits::Mile => {
108                            value * (MILES_TO_METERS * METERS_TO_NAUTICAL_MILE) as $type
109                        }
110                        LengthUnits::NauticalMile => value,
111                        LengthUnits::USSurveyFoot => {
112                            value * (SURVEYFOOT_TO_METER * METERS_TO_NAUTICAL_MILE) as $type
113                        }
114                    },
115                    LengthUnits::USSurveyFoot => match source_unit {
116                        LengthUnits::Meters => value * METER_TO_SURVEYFOOT as $type,
117                        LengthUnits::Kilometers => {
118                            value * (KILOMETERS_TO_METERS * METER_TO_SURVEYFOOT) as $type
119                        }
120                        LengthUnits::Feet => {
121                            value * (FEET_TO_METERS * METER_TO_SURVEYFOOT) as $type
122                        }
123                        LengthUnits::Mile => {
124                            value * (MILES_TO_METERS * METER_TO_SURVEYFOOT) as $type
125                        }
126                        LengthUnits::NauticalMile => {
127                            value * (NAUTICAL_MILES_TO_METERS * METER_TO_SURVEYFOOT) as $type
128                        }
129                        LengthUnits::USSurveyFoot => value,
130                    },
131                }
132            }
133        }
134    };
135}
136basic_unit!(Length, LengthUnits, Meters);
137from_units_length!(f32);
138from_units_length!(f64);
139
140impl LengthUnits {
141    pub const fn short_name(&self) -> &'static str {
142        match self {
143            LengthUnits::Meters => "m",
144            LengthUnits::Kilometers => "km",
145            LengthUnits::Feet => "ft",
146            LengthUnits::Mile => "mi",
147            LengthUnits::NauticalMile => "nmi",
148            LengthUnits::USSurveyFoot => "ussft",
149        }
150    }
151}
152
153///
154/// Represents a discrete quantity of 'Length' as defined in NIST 811.2008
155impl Length {
156    pub const ZERO: Length = Self::new_meters(0.0);
157
158    #[must_use]
159    pub const fn new_meters(value: f64) -> Length {
160        Self {
161            value,
162            units: LengthUnits::Meters,
163        }
164    }
165
166    #[must_use]
167    pub const fn new_feet(value: f64) -> Length {
168        Self {
169            value,
170            units: LengthUnits::Feet,
171        }
172    }
173
174    #[must_use]
175    pub fn as_meters(&self) -> Length {
176        self.as_unit(LengthUnits::Meters)
177    }
178
179    #[must_use]
180    pub fn as_feet(&self) -> Length {
181        self.as_unit(LengthUnits::Feet)
182    }
183}
184
185impl Display for Length {
186    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
187        f.write_fmt(format_args!(
188            "{:02.3}{}",
189            self.value,
190            self.units.short_name()
191        ))
192    }
193}
194
195pub const FEET_TO_METERS: f64 = 3.048E-01; // Exact, as per NIST 811.2008
196pub const METERS_TO_FEET: f64 = 1. / FEET_TO_METERS;
197pub const MILES_TO_METERS: f64 = 1.609_344E3; // Exact, as per NIST 811.2008
198pub const METERS_TO_MILES: f64 = 1. / MILES_TO_METERS;
199pub const KILOMETERS_TO_METERS: f64 = 1000.;
200pub const METERS_TO_KILOMETERS: f64 = 1. / KILOMETERS_TO_METERS;
201pub const NAUTICAL_MILES_TO_METERS: f64 = 1.852E3;
202pub const METERS_TO_NAUTICAL_MILE: f64 = 1. / NAUTICAL_MILES_TO_METERS;
203
204#[allow(clippy::excessive_precision)]
205pub const SURVEYFOOT_TO_METER: f64 = 3.04800609601219241E-1;
206pub const METER_TO_SURVEYFOOT: f64 = 1. / SURVEYFOOT_TO_METER;
207
208#[macro_export]
209macro_rules! assert_length_eq_eps {
210    ($left:expr, $right:expr, $eps:expr) => {
211        match (&$left, &$right) {
212            (left_val, right_val) => {
213                let delta = (*left_val - *right_val).value().abs();
214                if !(delta <= $eps) {
215                    panic!(
216                        "Assertion failed, {} - {} = {} > {} (error: {})",
217                        &*left_val,
218                        &*right_val,
219                        delta,
220                        $eps,
221                        delta - $eps
222                    )
223                }
224            }
225        }
226    };
227}
228
229#[cfg(test)]
230mod tests {
231    use crate::units::length::LengthUnits;
232    use crate::units::FromUnits;
233
234    #[test]
235    pub fn test_feet_meters() {
236        assert_eq!(
237            LengthUnits::Feet.from(1.0, LengthUnits::Meters),
238            1.0 / 0.3048
239        );
240        assert_eq!(LengthUnits::Meters.from(1.0, LengthUnits::Feet), 0.3048);
241    }
242
243    #[test]
244    pub fn test_meters_kilometers() {
245        assert_eq!(
246            LengthUnits::Meters.from(1.0, LengthUnits::Kilometers),
247            1000.
248        );
249        assert_eq!(
250            LengthUnits::Kilometers.from(1000.0, LengthUnits::Meters),
251            1.
252        );
253    }
254}