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}