irox_units/units/
angle.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
// SPDX-License-Identifier: MIT
// Copyright 2023 IROX Contributors

//!
//! This module contains the basic types and conversions for the SI coherent derived "Planar Angle"
//! quantity
use core::fmt::{Display, Formatter};

use crate::units::{FromUnits, Unit};

///
/// Represents a specific Planar Angle unit - SI or otherwise
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
#[non_exhaustive]
pub enum AngleUnits {
    /// SI Base Unit for Planar Angle - Radians, the unit radius of a circle.
    /// There are `tau` Radians across a full circle
    #[default]
    Radians,

    /// Derived unit for Planar Angle - Degrees.  
    /// There are 360 degrees across a full circle
    Degrees,

    /// Derived unit for Planar Angle - Minutes - the first division of a degree
    /// There are 60 minutes in a degree.
    Minutes,

    /// Derived unit for Planar Angle - Seconds - the second division of a degree
    /// There are 60 seconds in a minute.
    Seconds,

    /// Derived unit for Planar Angle - Revolution (turn) - a full circuit of a circle
    /// There are 360 degrees in a revolution
    Revolutions,

    /// Derived unit for Planar Angle - NATO Mil
    /// There are 6400 mils in a turn/revolution
    Mils,
}

macro_rules! from_units_angle {
    ($type:ident) => {
        impl crate::units::FromUnits<$type> for AngleUnits {
            fn from(&self, value: $type, units: Self) -> $type {
                match self {
                    AngleUnits::Degrees => match units {
                        AngleUnits::Radians => value * RAD_2_DEG as $type,
                        AngleUnits::Degrees => value as $type,

                        AngleUnits::Minutes => value * RAD_2_DEG as $type * DEG_2_MIN as $type,
                        AngleUnits::Seconds => value * RAD_2_DEG as $type * DEG_2_SEC as $type,
                        AngleUnits::Revolutions => value / REV_2_RAD as $type,
                        AngleUnits::Mils => value * RAD_2_MIL as $type,
                    },
                    AngleUnits::Radians => match units {
                        AngleUnits::Degrees => value * DEG_2_RAD as $type,
                        AngleUnits::Radians => value as $type,

                        AngleUnits::Minutes => value * DEG_2_MIN as $type,
                        AngleUnits::Seconds => value * DEG_2_SEC as $type,
                        AngleUnits::Revolutions => value / REV_2_DEG as $type,
                        AngleUnits::Mils => value * DEG_2_MIL as $type,
                    },
                    _ => todo!(),
                }
            }
        }
    };
}

basic_unit!(Angle, AngleUnits, Degrees);
from_units_angle!(f32);
from_units_angle!(f64);

impl Angle {
    #[must_use]
    pub const fn new_radians(value: f64) -> Angle {
        Self::new(value, AngleUnits::Radians)
    }

    #[must_use]
    pub const fn new_degrees(value: f64) -> Angle {
        Self::new(value, AngleUnits::Degrees)
    }

    #[must_use]
    pub fn new_dms(degrees: i16, minutes: u8, seconds: f64) -> Angle {
        let mult: f64 = match degrees {
            ..=0 => -1.0,
            _ => 1.0,
        };
        let minutes: f64 = f64::from(minutes) * mult;
        let seconds: f64 = seconds * mult;
        let value = f64::from(degrees) + minutes / 60. + seconds / 3600.;
        Self::new_degrees(value)
    }

    #[must_use]
    pub fn as_degrees(&self) -> Angle {
        self.as_unit(AngleUnits::Degrees)
    }

    #[must_use]
    pub fn as_radians(&self) -> Angle {
        self.as_unit(AngleUnits::Radians)
    }

    #[must_use]
    pub fn as_dms(&self) -> (i16, u8, f64) {
        let (deg, val) = self.as_deg_min();

        let min = val as u8;
        let sec = (val - min as f64) * 60.;
        (deg, min, sec)
    }

    #[must_use]
    #[allow(unused_imports)]
    pub fn as_deg_min(&self) -> (i16, f64) {
        use irox_tools::f64::FloatExt;
        let val = self.as_degrees().value;
        let sign = val.signum() as i16;
        let val = val.abs();

        let deg = val as i16;
        let min = (val - deg as f64) * 60.;
        (deg * sign, min)
    }

    #[cfg(feature = "std")]
    pub fn sin(&self) -> f64 {
        self.as_radians().value.sin()
    }
    #[cfg(feature = "std")]
    pub fn asin(&self) -> f64 {
        self.as_radians().value.asin()
    }
    #[cfg(feature = "std")]
    pub fn cos(&self) -> f64 {
        self.as_radians().value.cos()
    }
    #[cfg(feature = "std")]
    pub fn acos(&self) -> f64 {
        self.as_radians().value.acos()
    }
}

impl Display for Angle {
    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
        write!(f, "{:03.3}\u{00B0}", self.as_degrees().value)
    }
}

/// Degree to Radians factor
pub const DEG_2_RAD: f64 = 0.017_453_292_519_943_295;
/// Radians to Degrees factor
pub const RAD_2_DEG: f64 = 57.295_779_513_082_32;
/// Revolutions to Degrees factor
pub const REV_2_DEG: f64 = 360.;
/// Revolutions to Radians factor
pub const REV_2_RAD: f64 = core::f64::consts::TAU;
/// Minutes to Seconds factor
pub const MIN_2_SEC: f64 = 60.;
/// Mils to Revolutions factor
pub const MIL_2_REV: f64 = 6400.;
/// Degrees to Minutes factor
pub const DEG_2_MIN: f64 = 60.;
/// Degrees to Seconds factor
pub const DEG_2_SEC: f64 = DEG_2_MIN * MIN_2_SEC;
/// Degrees to Mils factor
pub const DEG_2_MIL: f64 = MIL_2_REV / REV_2_DEG;
pub const RAD_2_MIL: f64 = MIL_2_REV / REV_2_RAD;