Skip to main content

irox_units/units/
compass.rs

1// SPDX-License-Identifier: MIT
2// Copyright 2023 IROX Contributors
3
4//!
5//! Contains [`Compass`] and [`CompassReference`], ways of measuring physical angles on a Sphere or
6//! Ellipse.
7//!
8
9use core::fmt::{Display, Formatter};
10use core::marker::PhantomData;
11
12use crate::units::angle::Angle;
13use crate::units::FromUnits;
14
15///
16/// The direction that a compass needle moves for "positive" increases
17#[derive(Debug, Copy, Clone, Default, Eq, PartialEq)]
18pub enum RotationDirection {
19    /// Positive-Clockwise is the standard direction a compass needle moves, positive in a
20    /// clockwise direction, towards the right, usually with the zero point at 'North'
21    #[default]
22    PositiveClockwise,
23
24    /// Positive-Counter-Clockwise is the standard direction of rotation on a cartesian
25    /// coordinate plane - mostly used for trigonometric convenience (sin/cos/tan/etc) where the
26    /// needle moves towards the left when positive, usually with the zero point at 'East'
27    PositiveCounterClockwise,
28}
29
30impl FromUnits<Angle> for RotationDirection {
31    fn from(&self, value: Angle, units: Self) -> Angle {
32        value
33            * match self {
34                RotationDirection::PositiveClockwise => match units {
35                    RotationDirection::PositiveClockwise => 1.0,
36                    RotationDirection::PositiveCounterClockwise => -1.0,
37                },
38                RotationDirection::PositiveCounterClockwise => match units {
39                    RotationDirection::PositiveClockwise => -1.0,
40                    RotationDirection::PositiveCounterClockwise => 1.0,
41                },
42            }
43    }
44}
45
46///
47/// The "zero" reference point for a compass needle
48#[derive(Debug, Copy, Clone, Default, Eq, PartialEq)]
49pub enum CompassReference {
50    /// 0 is True North - the geometric north pole (axis of rotation)
51    #[default]
52    TrueNorth,
53
54    /// 0 is Magnetic North - the direction that a compass needle points
55    MagneticNorth,
56
57    /// 0 is East - Used mostly for X/Y cartesian planes where angles are 0 to the right
58    East,
59}
60
61impl FromUnits<Angle> for CompassReference {
62    fn from(&self, value: Angle, units: Self) -> Angle {
63        match self {
64            CompassReference::TrueNorth => match units {
65                CompassReference::TrueNorth => value,
66                _ => todo!(),
67            },
68            CompassReference::MagneticNorth => match units {
69                CompassReference::MagneticNorth => value,
70                _ => todo!(),
71            },
72            CompassReference::East => match units {
73                CompassReference::East => value,
74                _ => todo!(),
75            },
76        }
77    }
78}
79
80/// Represents a heading - the compass direction that the entity is pointing
81pub type Heading = Compass<HeadingType>;
82
83/// `HeadingType` is used as a compile-time check for [`Heading`] = [`Compass<HeadingType>`]
84#[derive(Debug, Copy, Clone, Default, Eq, PartialEq)]
85pub struct HeadingType;
86
87/// Represents a track - the compass direction that the entity is travelling
88pub type Track = Compass<TrackType>;
89
90/// `TrackType` is used as a compile-time check for [`Track`] = [`Compass<TrackType>`]
91#[derive(Debug, Copy, Clone, Default, Eq, PartialEq)]
92pub struct TrackType;
93
94/// Represents a bearing - the compass direction of your desired destination
95pub type Bearing = Compass<BearingType>;
96
97/// `BearingType` is used as a compile-time check for [`Bearing`] = [`Compass<BearingType>`]
98#[derive(Debug, Copy, Clone, Default, Eq, PartialEq)]
99pub struct BearingType;
100
101/// Represents a course - the compass direction of your desired track
102pub type Course = Compass<CourseType>;
103
104/// `CourseType` is used as a compile-time check for [`Course`] = [`Compass<CourseType>`]
105#[derive(Debug, Copy, Clone, Default, Eq, PartialEq)]
106pub struct CourseType;
107
108/// Represents a azimuth - the compass direction of a generic pointing angle
109pub type Azimuth = Compass<AzimuthType>;
110
111/// `AzimuthType` is used as a compile-time check for [`Azimuth`] = [`Compass<AzimuthType>`]
112#[derive(Debug, Copy, Clone, Default, Eq, PartialEq)]
113pub struct AzimuthType;
114
115/// Represents a compass needle and the direction that it's pointing
116#[derive(Debug, Copy, Clone, Default, PartialEq)]
117pub struct Compass<T> {
118    angle: Angle,
119    direction: RotationDirection,
120    reference: CompassReference,
121    _ign: PhantomData<T>,
122}
123
124impl<T> Display for Compass<T> {
125    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
126        write!(
127            f,
128            "{} {:?} {:?}",
129            self.angle, self.direction, self.reference
130        )
131    }
132}
133
134impl<T> Compass<T> {
135    ///
136    /// Creates a new heading type - see [`HeadingType`] for details.
137    #[must_use]
138    pub const fn new_heading(
139        angle: Angle,
140        direction: RotationDirection,
141        reference: CompassReference,
142    ) -> Compass<HeadingType> {
143        Compass {
144            angle,
145            direction,
146            reference,
147            _ign: PhantomData,
148        }
149    }
150
151    ///
152    /// Creates a new track type - see [`Track`] for details.
153    #[must_use]
154    pub const fn new_track(
155        angle: Angle,
156        direction: RotationDirection,
157        reference: CompassReference,
158    ) -> Compass<TrackType> {
159        Compass {
160            angle,
161            direction,
162            reference,
163            _ign: PhantomData,
164        }
165    }
166
167    ///
168    /// Creates a new bearing type - see [`Bearing`] for details.
169    #[must_use]
170    pub const fn new_bearing(
171        angle: Angle,
172        direction: RotationDirection,
173        reference: CompassReference,
174    ) -> Compass<BearingType> {
175        Compass {
176            angle,
177            direction,
178            reference,
179            _ign: PhantomData,
180        }
181    }
182
183    ///
184    /// Creates a new course type - see [`Course`] for details.
185    #[must_use]
186    pub const fn new_course(
187        angle: Angle,
188        direction: RotationDirection,
189        reference: CompassReference,
190    ) -> Compass<CourseType> {
191        Compass {
192            angle,
193            direction,
194            reference,
195            _ign: PhantomData,
196        }
197    }
198
199    ///
200    /// Creates a new azimuth type - see [`Azimuth`] for details.
201    #[must_use]
202    pub const fn new_azimuth(
203        angle: Angle,
204        direction: RotationDirection,
205        reference: CompassReference,
206    ) -> Compass<AzimuthType> {
207        Compass {
208            angle,
209            direction,
210            reference,
211            _ign: PhantomData,
212        }
213    }
214
215    #[must_use]
216    pub const fn angle(&self) -> &Angle {
217        &self.angle
218    }
219
220    #[must_use]
221    pub const fn direction(&self) -> &RotationDirection {
222        &self.direction
223    }
224
225    #[must_use]
226    pub const fn reference(&self) -> &CompassReference {
227        &self.reference
228    }
229
230    #[must_use]
231    pub fn as_direction_reference(
232        &self,
233        direction: RotationDirection,
234        reference: CompassReference,
235    ) -> Compass<T> {
236        let angle = direction.from(self.angle, self.direction);
237        let angle = reference.from(angle, self.reference);
238        Compass {
239            angle,
240            direction,
241            reference,
242            _ign: PhantomData,
243        }
244    }
245}
246
247///
248/// Represents a relative angle from a particular zero point that's not a standard reference like
249/// North or East.  Used for "relative bearings" and the like where the angle is referenced to the
250/// heading of an entity (like, 10 degrees to the right)
251#[derive(Debug, Clone, PartialEq)]
252pub struct CompassOffset<T, B> {
253    compass: Compass<T>,
254    offset: Angle,
255    direction: RotationDirection,
256    _ign: PhantomData<B>,
257}
258
259impl<T, B> CompassOffset<T, B> {
260    #[must_use]
261    pub fn compass(&self) -> &Compass<T> {
262        &self.compass
263    }
264
265    #[must_use]
266    pub fn offset(&self) -> &Angle {
267        &self.offset
268    }
269
270    #[must_use]
271    pub fn direction(&self) -> &RotationDirection {
272        &self.direction
273    }
274}
275
276/// Represents the relative angle from a particular entities heading
277pub type RelativeBearing = CompassOffset<HeadingType, BearingType>;
278
279impl Compass<HeadingType> {
280    ///
281    /// Converts this heading into a relative bearing using the specified offset and direction
282    #[must_use]
283    pub fn relative_bearing(self, direction: RotationDirection, offset: Angle) -> RelativeBearing {
284        CompassOffset {
285            compass: self,
286            offset,
287            direction,
288            _ign: PhantomData,
289        }
290    }
291}
292
293///
294/// Represents a generic compass direction, any one of [`Heading`], [`Track`], [`Bearing`],
295/// [`Course`] or [`Azimuth`]
296#[derive(Debug, Copy, Clone, PartialEq)]
297pub enum CompassDirection {
298    Heading(Heading),
299    Track(Track),
300    Bearing(Bearing),
301    Course(Course),
302    Azimuth(Azimuth),
303}
304
305impl Display for CompassDirection {
306    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
307        match self {
308            CompassDirection::Heading(h) => {
309                write!(f, "Heading({h})")
310            }
311            CompassDirection::Track(t) => {
312                write!(f, "Track({t})")
313            }
314            CompassDirection::Bearing(b) => {
315                write!(f, "Bearing({b})")
316            }
317            CompassDirection::Course(c) => {
318                write!(f, "Course({c})")
319            }
320            CompassDirection::Azimuth(a) => {
321                write!(f, "Azimuth({a})")
322            }
323        }
324    }
325}