ring360/
lib.rs

1use std::ops::{Add,Sub};
2use std::fmt;
3
4/// Ring360 is a tuple struct encapsulating an f64
5#[derive(Debug, Clone, Copy)]
6pub struct Ring360(pub f64);
7
8/// Methods
9impl Ring360 {
10
11  /// The base is 360.0. All degree values will be modulated by this number
12  pub const BASE:f64 = 360.0;
13
14	/// Alternative constructor if the source degree uses the ±180º GIS system
15  /// This will not affect the degree conversion, but only the initial rotations
16  /// value, e.g. (-20).
17  pub fn from_gis(lng180: f64) -> Ring360 {
18    if lng180 < 0.0 {
19      Ring360(Self::BASE + lng180)
20    } else {
21      Ring360(lng180)
22    }
23  }
24
25  /// Degrees as 64-bit floats on the 0º to 360º scale around a circle
26  /// Use value() for the intrinsic value that may extend beyond this range
27  pub fn degrees(&self) -> f64 {
28    let deg_val = self.0 % Self::BASE;
29    if deg_val < 0.0 { 
30      Self::BASE - (0.0 - deg_val)
31    } else {
32      deg_val
33    }
34  }
35
36	/// Alias for for .degrees(), but is the default f64 conversion
37  pub fn to_f64(&self) -> f64 {
38    self.degrees()
39  }
40
41	/// Convert the internal 0-360º scale back to the -180º to +180º GIS scale
42	pub fn to_gis(&self) -> f64 {
43    if self.degrees() <= Self::half_turn() {
44      self.degrees()
45    } else {
46      self.degrees() - Self::BASE
47    }
48  }
49
50  /// Get the number of rotations. If the total is less than base of 360
51  pub fn rotations(&self) -> i64 {
52    (self.0 / Self::BASE).floor() as i64
53  }
54
55  /// Get the intrinsic raw value as a decimal fraction of rotations
56  /// e.g. 180.0 translates to 0.5 and 450.0 to 1.25
57  pub fn progress(&self) -> f64 {
58    self.0 / Self::BASE
59  }
60
61	/// Returns the raw internal f64 value on a 0-360º scale. 
62	/// Values under 0 or over 360 represent negative or positive rotations
63  pub fn value(&self) -> f64 {
64    self.0
65  }
66
67	pub fn half_turn() -> f64 {
68    Self::BASE / 2.0
69  }
70
71  pub fn minus_half_turn() -> f64 {
72    0.0 - Self::BASE / 2.0
73  }
74
75  /// Return a simple tuple pair with the 
76  /// 360º degree value and the number of rotations (turns)
77  pub fn as_tuple(&self) -> (f64, i64) {
78    (self.degrees(), self.rotations())
79  }
80
81  /// Multiply a Ring360 value by a normal f64 value
82  pub fn multiply(mut self, multiple: f64) -> Self {
83    self.0 *= multiple;
84    self
85  }
86
87  /// Divide a Ring360 by a normal f64 value
88  pub fn divide(mut self, divisor: f64) -> Self {
89    self.0 /= divisor;
90    self
91  }
92
93  /// Calculate the shortest distance in degrees between a Ring360 value
94  /// and a 64-bit float representing a degree
95	/// A positive value represents clockwise movement between the first and second longitude
96  pub fn angle_f64(&self, other_value: f64) -> f64 {
97    let mut diff = (other_value % Self::BASE) - self.degrees();
98    if diff < Self::minus_half_turn() {
99        diff += Self::BASE;
100    } else if diff > Self::half_turn() {
101        diff -= Self::BASE;
102    }
103    diff
104  }
105
106  /// Calculate the absolute angle with another 64-bit float in the 0 to 360º system
107  /// only in a clockwise direction with the 180º to 359.999º representing half to a full turn
108  pub fn angle_f64_abs(&self, other_value: f64) -> f64 {
109   let relative_value = self.angle_f64(other_value);
110   if relative_value < 0.0 {
111    Self::BASE + relative_value
112   } else {
113    relative_value
114   }
115  }
116
117  /// Calculate the shortest distance in degrees between 
118  /// two a Ring360 values
119  pub fn angle(&self, other_value: Ring360) -> f64 {
120    self.angle_f64(other_value.degrees())
121  }
122  /// Calculate the absolute angle with another Ring360 degree
123  /// only in a clockwise direction with the 180º to 359.999º representing half to a full turn
124  pub fn angle_abs(&self, other_value: Ring360) -> f64 {
125    self.angle_f64_abs(other_value.degrees())
126   }
127
128	/// Convert to radians for use with cos(), sin(), tan(), atan() etc.
129  pub fn to_radians(&self) -> f64 {
130    self.degrees().to_radians()
131  }
132
133	/// Calculate sine in the 360º system without explicity converting to radians
134  pub fn sin(&self) -> f64 {
135    self.to_radians().sin()
136  }
137
138	/// Calculate cosine in the 360º system without explicity converting to radians
139  pub fn cos(&self) -> f64 {
140    self.to_radians().cos()
141  }
142
143	/// Calculate tangent in the 360º system without explicity converting to radians
144  pub fn tan(&self) -> f64 {
145    self.to_radians().tan()
146  }
147
148	/// Calculate inverse cosine in the 360º system without explicity converting to and from radians
149  pub fn asin(&self) -> f64 {
150    self.to_radians().asin()
151  }
152
153	/// Calculate inverse cosine in the 360º system without explicity converting to and from radians
154  pub fn acos(&self) -> f64 {
155    self.to_radians().acos()
156  }
157
158	/// Calculate inverse tangent (arctan, aatan) in the 360º system without explicity converting to radians
159  pub fn atan(&self) -> f64 {
160    self.to_radians().atan()
161  }
162
163}
164
165/// Implement + (addition) operator with two Ring30 values
166impl Add for Ring360 {
167
168  type Output = Ring360;
169
170  /// Implement + for Ring360
171  fn add(mut self, other: Ring360) -> Self {
172    self.0 += other.value();
173    self
174  }
175}
176
177/// Implement - (subtraction) operator with two Ring30 values
178impl Sub for Ring360 {
179
180  type Output = Ring360;
181
182  /// Implement - for Ring360
183  fn sub(mut self, other: Ring360) -> Self {
184    self.0 -= other.value();
185    self
186  }
187}
188
189/// Implement default display for Ring360 as the degree value
190impl fmt::Display for Ring360 {
191  /// By default display the circular degree value
192  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
193    write!(f, "{}", self.degrees())
194  }
195}
196
197/// trait to convert normal float values to a Ring360 value 
198/// and to apply a simple mod_360() returning a 64-bit float
199pub trait ToRing360 {
200	fn to_360(&self) -> Ring360;
201  fn to_360_gis(&self) -> Ring360;
202	fn mod_360(&self) -> Self;
203  fn angle_360(&self, other_value: f64) -> Self;
204  fn angle_360_abs(&self, other_value: f64) -> Self;
205}
206
207/// Implement casting methods for f64
208impl ToRing360 for f64 {
209
210  /// Convert to a Ring360 struct
211	fn to_360(&self) -> Ring360 {
212    Ring360(*self)
213  }
214
215  /// Convert to GIS ±180 representation
216  fn to_360_gis(&self) -> Ring360 {
217    Ring360::from_gis(*self)
218  }
219
220  /// Convert a 64-bit float directly to the 0 to 360º system
221	fn mod_360(&self) -> f64 {
222    Ring360(*self).degrees()
223  }
224
225  /// Calculate the shortest relative angle with another 64-bit float in the 0 to 360º system
226  /// with negative values indicating an anticlockwise direction
227  fn angle_360(&self, other_value: f64) -> f64 {
228    Ring360(*self).angle_f64(other_value)
229  }
230
231  /// Calculate the absolute angle with another 64-bit float in the 0 to 360º system
232  /// only in a clockwise direction with the 180º to 359.999º representing half to a full turn
233  fn angle_360_abs(&self, other_value: f64) -> f64 {
234    Ring360(*self).angle_f64_abs(other_value)
235  }
236
237}