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
229
230
231
232
233
234
235
236
237
use std::ops::{Add,Sub};
use std::fmt;

/// Ring360 is a tuple struct encapsulating an f64
#[derive(Debug, Clone, Copy)]
pub struct Ring360(pub f64);

/// Methods
impl Ring360 {

  /// The base is 360.0. All degree values will be modulated by this number
  pub const BASE:f64 = 360.0;

	/// Alternative constructor if the source degree uses the ±180º GIS system
  /// This will not affect the degree conversion, but only the initial rotations
  /// value, e.g. (-20).
  pub fn from_gis(lng180: f64) -> Ring360 {
    if lng180 < 0.0 {
      Ring360(Self::BASE + lng180)
    } else {
      Ring360(lng180)
    }
  }

  /// Degrees as 64-bit floats on the 0º to 360º scale around a circle
  /// Use value() for the intrinsic value that may extend beyond this range
  pub fn degrees(&self) -> f64 {
    let deg_val = self.0 % Self::BASE;
    if deg_val < 0.0 { 
      Self::BASE - (0.0 - deg_val)
    } else {
      deg_val
    }
  }

	/// Alias for for .degrees(), but is the default f64 conversion
  pub fn to_f64(&self) -> f64 {
    self.degrees()
  }

	/// Convert the internal 0-360º scale back to the -180º to +180º GIS scale
	pub fn to_gis(&self) -> f64 {
    if self.degrees() <= Self::half_turn() {
      self.degrees()
    } else {
      self.degrees() - Self::BASE
    }
  }

  /// Get the number of rotations. If the total is less than base of 360
  pub fn rotations(&self) -> i64 {
    (self.0 / Self::BASE).floor() as i64
  }

  /// Get the intrinsic raw value as a decimal fraction of rotations
  /// e.g. 180.0 translates to 0.5 and 450.0 to 1.25
  pub fn progress(&self) -> f64 {
    self.0 / Self::BASE
  }

	/// Returns the raw internal f64 value on a 0-360º scale. 
	/// Values under 0 or over 360 represent negative or positive rotations
  pub fn value(&self) -> f64 {
    self.0
  }

	pub fn half_turn() -> f64 {
    Self::BASE / 2.0
  }

  pub fn minus_half_turn() -> f64 {
    0.0 - Self::BASE / 2.0
  }

  /// Return a simple tuple pair with the 
  /// 360º degree value and the number of rotations (turns)
  pub fn as_tuple(&self) -> (f64, i64) {
    (self.degrees(), self.rotations())
  }

  /// Multiply a Ring360 value by a normal f64 value
  pub fn multiply(mut self, multiple: f64) -> Self {
    self.0 *= multiple;
    self
  }

  /// Divide a Ring360 by a normal f64 value
  pub fn divide(mut self, divisor: f64) -> Self {
    self.0 /= divisor;
    self
  }

  /// Calculate the shortest distance in degrees between a Ring360 value
  /// and a float64 representing a degree
	/// A positive value represents clockwise movement between the first and second longitude
  pub fn angle_f64(&self, other_value: f64) -> f64 {
    let mut diff = (other_value % Self::BASE) - self.degrees();
    if diff < Self::minus_half_turn() {
        diff += Self::BASE;
    } else if diff > Self::half_turn() {
        diff -= Self::BASE
    }
    diff
  }

  /// Calculate the absolute angle with another 64-bit float in the 0 to 360º system
  /// only in a clockwise direction with the 180º to 359.999º representing half to a full turn
  pub fn angle_f64_abs(&self, other_value: f64) -> f64 {
   let relative_value = self.angle_f64(other_value);
   if relative_value < 0.0 {
    Self::BASE + relative_value
   } else {
    relative_value
   }
  }

  /// Calculate the shortest distance in degrees between 
  /// two a Ring360 values
  pub fn angle(&self, other_value: Ring360) -> f64 {
    self.angle_f64(other_value.degrees())
  }
  /// Calculate the absolute angle with another Ring360 degree
  /// only in a clockwise direction with the 180º to 359.999º representing half to a full turn
  pub fn angle_abs(&self, other_value: Ring360) -> f64 {
    self.angle_f64_abs(other_value.degrees())
   }

	/// Convert to radians for use with cos(), sin(), tan(), atan() etc.
  pub fn to_radians(&self) -> f64 {
    self.degrees().to_radians()
  }

	/// Calculate sine in the 360º system without explicity converting to radians
  pub fn sin(&self) -> f64 {
    self.to_radians().sin()
  }

	/// Calculate cosine in the 360º system without explicity converting to radians
  pub fn cos(&self) -> f64 {
    self.to_radians().cos()
  }

	/// Calculate tangent in the 360º system without explicity converting to radians
  pub fn tan(&self) -> f64 {
    self.to_radians().tan()
  }

	/// Calculate inverse cosine in the 360º system without explicity converting to and from radians
  pub fn asin(&self) -> f64 {
    self.to_radians().asin()
  }

	/// Calculate inverse cosine in the 360º system without explicity converting to and from radians
  pub fn acos(&self) -> f64 {
    self.to_radians().acos()
  }

	/// Calculate inverse tangent (arctan, aatan) in the 360º system without explicity converting to radians
  pub fn atan(&self) -> f64 {
    self.to_radians().atan()
  }

}

/// Implement + (addition) operator with two Ring30 values
impl Add for Ring360 {

  type Output = Ring360;

  /// Implement + for Ring360
  fn add(mut self, other: Ring360) -> Self {
    self.0 += other.value();
    self
  }
}

/// Implement - (subtraction) operator with two Ring30 values
impl Sub for Ring360 {

  type Output = Ring360;

  /// Implement - for Ring360
  fn sub(mut self, other: Ring360) -> Self {
    self.0 -= other.value();
    self
  }
}

/// Implement default display for Ring360 as the degree value
impl fmt::Display for Ring360 {
  /// By default display the circular degree value
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    write!(f, "{}", self.degrees())
  }
}

/// trait to convert normal float values to a Ring360 value 
/// and to apply a simple mod_360() returning a 64-bit float
pub trait ToRing360 {
	fn to_360(&self) -> Ring360;
  fn to_360_gis(&self) -> Ring360;
	fn mod_360(&self) -> Self;
  fn angle_360(&self, other_value: f64) -> Self;
  fn angle_360_abs(&self, other_value: f64) -> Self;
}

/// Implement casting methods for f64
impl ToRing360 for f64 {

  /// Convert to a Ring360 struct
	fn to_360(&self) -> Ring360 {
    Ring360(*self)
  }

  /// Convert to GIS ±180 representation
  fn to_360_gis(&self) -> Ring360 {
    Ring360::from_gis(*self)
  }

  /// Convert a 64-bit float directly to the 0 to 360º system
	fn mod_360(&self) -> f64 {
    Ring360(*self).degrees()
  }

  /// Calculate the shortest relative angle with another 64-bit float in the 0 to 360º system
  /// with negative values indicating an anticlockwise direction
  fn angle_360(&self, other_value: f64) -> f64 {
    Ring360(*self).angle_f64(other_value)
  }

  /// Calculate the absolute angle with another 64-bit float in the 0 to 360º system
  /// only in a clockwise direction with the 180º to 359.999º representing half to a full turn
  fn angle_360_abs(&self, other_value: f64) -> f64 {
    Ring360(*self).angle_f64_abs(other_value)
  }

}