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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
//! The traits for an angle

use std::{convert::TryFrom, error::Error, str::FromStr};

use num_traits::{CheckedAdd, CheckedSub};

pub mod dd;
mod degree;
pub mod dms_dd;
mod errors;

pub use errors::AngleNotInRange;

#[allow(clippy::module_name_repetitions)]
/// Common terminology for angles:
/// <https://en.wikipedia.org/wiki/Angle#Individual_angles>
pub trait AngleNames: Copy + PartialOrd {
    /// No angle
    fn zero() -> Self;

    /// The angle made of perpendicular rays
    fn right() -> Self;

    /// The angle made of two exactly opposite direction
    fn straight() -> Self;

    /// The angle made of full circle (perigon)
    fn complete() -> Self;

    /// No angle
    fn is_zero(self) -> bool {
        self == Self::zero()
    }

    /// Is the angle sharp?
    fn is_acute(self) -> bool {
        self > Self::zero() && self < Self::right()
    }

    /// Are the lines perpendicular?
    fn is_right(self) -> bool {
        self == Self::right()
    }

    /// Is the angle blunt?
    fn is_obtuse(self) -> bool {
        self > Self::right() && self < Self::straight()
    }

    /// Is the angle forms a straight line?
    fn is_straight(self) -> bool {
        self == Self::straight()
    }

    /// Is the angle more than a straight line?
    fn is_reflex(self) -> bool {
        self > Self::straight() && self < Self::complete()
    }

    /// Is the angle full round?
    fn is_complete(self) -> bool {
        self == Self::complete()
    }
}

/// Angle with ordering, addition/subtraction operations and
/// the abilities to construct itself from a string or a float number
pub trait Angle:
    AngleNames
    + Ord
    + CheckedAdd
    + CheckedSub
    + FromStr<Err = <Self as Angle>::ParseErr>
    + TryFrom<f64, Error = <Self as Angle>::NumErr>
{
    /// The error that can appear when representing some part of the angle with a number
    type NumErr: Error;
    /// The error that can appear while parsing the angle from a string
    type ParseErr: Error;

    /// Adjacent angle which sum to a [right](trait.AngleNames.html#tymethod.right) angle
    fn complement(self) -> Option<Self> {
        Self::right().checked_sub(&self)
    }

    /// Adjacent angle which sum to a [straight](trait.AngleNames.html#tymethod.straight) angle
    fn supplement(self) -> Option<Self> {
        Self::straight().checked_sub(&self)
    }

    /// Adjacent angle which sum to a [complete](trait.AngleNames.html#tymethod.complete) angle
    fn explement(self) -> Self {
        Self::complete()
            .checked_sub(&self)
            .expect("Current implementation stores angles <=360 degrees")
    }

    /// Difference between the angles by modulo independent of the order
    fn abs_diff(self, rhs: Self) -> Self {
        let diff = self.checked_sub(&rhs).or_else(|| rhs.checked_sub(&self));

        // difference should always be less than the maximum (considered valid) angle
        diff.expect("Difference is small enough to be valid angle")
    }

    /// Is the `other` angle differs by an exact multiple of a full turn
    fn turn_eq(self, mut other: Self) -> bool {
        while other >= Self::complete() {
            other = other - Self::complete();
        }

        self == other
    }

    /// Produce an error variant indicating an angle is more than the [right](trait.AngleNames.html#tymethod.right) one
    fn obtuse_err() -> Self::NumErr;

    /// Produce an error variant indicating an angle is [reflex](trait.AngleNames.html#tymethod.is_reflex)
    fn reflex_err() -> Self::NumErr;

    /// Produce an error variant indicating an angle does not fall into [full circle](trait.AngleNames.html#tymethod.complete)
    fn turn_err() -> Self::NumErr;

    /// Check the angle is less than or equal to the [right](trait.AngleNames.html#tymethod.right) one
    ///
    /// # Errors
    /// When the angle is greater than 90 degrees
    fn and_not_obtuse(self) -> Result<Self, Self::NumErr> {
        if self > Self::right() {
            Err(Self::obtuse_err())
        } else {
            Ok(self)
        }
    }

    /// Check the angle is less than or equal to the [straight](trait.AngleNames.html#tymethod.straight) one
    ///
    /// # Errors
    /// When the angle is greater than 180 degrees
    fn and_not_reflex(self) -> Result<Self, Self::NumErr> {
        if self.is_reflex() {
            Err(Self::reflex_err())
        } else {
            Ok(self)
        }
    }
}

pub(super) trait UnitsAngle: Angle {
    type Units: CheckedAdd + CheckedSub;

    fn with_units(u: Self::Units) -> Result<Self, Self::NumErr>;
    fn units(self) -> Self::Units;

    fn max_units() -> Self::Units {
        Self::complete().units()
    }
}

#[doc(hidden)]
#[macro_export]
/// Implement addition and subtraction operations
/// on the `UnitsAngle` types.
/// `$sum_t` should be able to hold a sum of any units
/// without the risk of overflow.
macro_rules! impl_angle_ops {
    ($t:ty: <$sum_t: ty) => {
        impl Add for $t {
            type Output = Self;

            fn add(self, rhs: Self) -> Self::Output {
                if let Some(sum) = self.checked_add(&rhs) {
                    return sum;
                }

                // the sum can overflow `Units`, so convert everything to $sum_t
                let self_units = <$sum_t>::from(self.units());
                let rhs_units = <$sum_t>::from(rhs.units());
                let max = <$sum_t>::from(Self::max_units());
                assert!(self_units <= max);
                assert!(rhs_units <= max);
                assert!(self_units + rhs_units > max);

                let sum_units = (self_units + rhs_units - max)
                    .try_into()
                    .expect("Less than max should be valid");
                Self::with_units(sum_units)
                    .expect("Wrapping sum around the max degree is always a valid degree")
            }
        }

        impl CheckedAdd for $t {
            fn checked_add(&self, rhs: &Self) -> Option<Self> {
                self.units()
                    .checked_add(rhs.units())
                    .filter(|&sum_units| sum_units <= Self::max_units())
                    .and_then(|units| Self::with_units(units).ok())
            }
        }

        impl Sub for $t {
            type Output = Self;

            fn sub(self, rhs: Self) -> Self::Output {
                if let Some(diff) = self.checked_sub(&rhs) {
                    return diff;
                }

                let self_ = self.units();
                let rhs = rhs.units();
                assert!(self_ < rhs);

                let max = Self::max_units();

                let diff = max - (rhs - self_);
                Self::with_units(diff).expect("The diff is less than the max angle")
            }
        }

        impl CheckedSub for $t {
            fn checked_sub(&self, rhs: &Self) -> Option<Self> {
                self.units()
                    .checked_sub(rhs.units())
                    .and_then(|units| Self::with_units(units).ok())
            }
        }
    };
}