Skip to main content

geometry_cs/
unit.rs

1//! Plane-angle units: [`Degree`] and [`Radian`].
2//!
3//! Mirrors `boost::geometry::degree` and `boost::geometry::radian` —
4//! the empty tag types declared at `boost/geometry/core/cs.hpp:41-51`,
5//! together with their use as the `units` typedef on the
6//! `cs::{spherical,geographic,polar}<DegreeOrRadian>` templates
7//! (`cs.hpp:54-79`).
8//!
9//! Boost.Geometry never expresses degree↔radian conversion as a method
10//! on the tag; it scatters `* 0.017453292…` constants and ad-hoc helpers
11//! across `util/math.hpp` and the spherical/geographic strategies. We
12//! collect both directions into the [`AngleUnit`] trait so a strategy
13//! can write `U::to_radians(angle)` once and have it monomorphise to
14//! either `angle` (for [`Radian`]) or `angle * π/180` (for [`Degree`]).
15
16use geometry_coords::CoordinateScalar;
17
18/// Unit of plane angle.
19///
20/// Mirrors `boost::geometry::degree` / `boost::geometry::radian` from
21/// `boost/geometry/core/cs.hpp:41-51` and their role as the
22/// `cs_angular_units<Cs>::type` in `cs.hpp:251-281`. The conversion
23/// methods are the Rust analogue of the unnamed scaling factors Boost
24/// inlines inside each strategy.
25///
26/// # Examples
27///
28/// ```
29/// use geometry_cs::{AngleUnit, Degree, Radian};
30///
31/// // 180° == π rad
32/// let rad = Degree::to_radians(180.0_f64);
33/// assert!((rad - core::f64::consts::PI).abs() < 1e-12);
34///
35/// // Round-trip through Radian is the identity.
36/// assert_eq!(Radian::to_radians(1.5_f64), 1.5);
37/// ```
38pub trait AngleUnit {
39    /// Convert a value in this unit to radians.
40    ///
41    /// For [`Radian`] this is the identity; for [`Degree`] it multiplies
42    /// by `π / 180`.
43    fn to_radians<T: CoordinateScalar + FromF64>(value: T) -> T;
44
45    /// Convert a value in radians to this unit.
46    ///
47    /// For [`Radian`] this is the identity; for [`Degree`] it multiplies
48    /// by `180 / π`.
49    fn from_radians<T: CoordinateScalar + FromF64>(value: T) -> T;
50}
51
52/// Degrees (−180 … 180). Mirrors `boost::geometry::degree`
53/// (`boost/geometry/core/cs.hpp:41`).
54///
55/// # Examples
56///
57/// ```
58/// use geometry_cs::{AngleUnit, Degree};
59/// assert!((Degree::to_radians(90.0_f64) - core::f64::consts::FRAC_PI_2).abs() < 1e-12);
60/// ```
61#[derive(Debug, Default, Clone, Copy)]
62pub struct Degree;
63
64/// Radians (−π … π). Mirrors `boost::geometry::radian`
65/// (`boost/geometry/core/cs.hpp:51`).
66///
67/// # Examples
68///
69/// ```
70/// use geometry_cs::{AngleUnit, Radian};
71/// assert_eq!(Radian::to_radians(1.5_f64), 1.5);
72/// ```
73#[derive(Debug, Default, Clone, Copy)]
74pub struct Radian;
75
76impl AngleUnit for Degree {
77    #[inline]
78    fn to_radians<T: CoordinateScalar + FromF64>(value: T) -> T {
79        value * T::from_f64(core::f64::consts::PI / 180.0)
80    }
81
82    #[inline]
83    fn from_radians<T: CoordinateScalar + FromF64>(value: T) -> T {
84        value * T::from_f64(180.0 / core::f64::consts::PI)
85    }
86}
87
88impl AngleUnit for Radian {
89    #[inline]
90    fn to_radians<T: CoordinateScalar + FromF64>(value: T) -> T {
91        value
92    }
93
94    #[inline]
95    fn from_radians<T: CoordinateScalar + FromF64>(value: T) -> T {
96        value
97    }
98}
99
100/// Construct a scalar from an `f64` literal.
101///
102/// Lives in `geometry-cs` rather than `geometry-coords` because it is
103/// only ever needed for the degree↔radian conversion factor — the rest
104/// of the kernel either uses `T::ZERO` / `T::ONE` from
105/// [`CoordinateScalar`] or stays in the scalar type the caller picked.
106/// Keeping the bound local means strategies that never touch angle
107/// units do not pay for it.
108///
109/// # Examples
110///
111/// ```
112/// use geometry_cs::FromF64;
113/// assert_eq!(f64::from_f64(1.5), 1.5);
114/// assert_eq!(f32::from_f64(1.5), 1.5_f32);
115/// ```
116pub trait FromF64 {
117    /// Cast an `f64` constant down to `Self`. Truncating for `f32` is
118    /// fine — these are conversion-factor constants, not user data.
119    fn from_f64(value: f64) -> Self;
120}
121
122impl FromF64 for f32 {
123    #[inline]
124    #[allow(clippy::cast_possible_truncation)]
125    fn from_f64(value: f64) -> Self {
126        value as f32
127    }
128}
129
130impl FromF64 for f64 {
131    #[inline]
132    fn from_f64(value: f64) -> Self {
133        value
134    }
135}