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}