use std::f64::consts::{PI, TAU};
use std::fmt;
use std::ops::{Add, Div, Mul, Sub};
#[derive(Clone, Copy, Default, PartialOrd)]
pub struct Angle(f64);
impl Angle {
pub const fn radians(self) -> f64 {
self.0
}
pub const fn from_radians(x: f64) -> Self {
const fn lpr(x: f64, y: f64) -> f64 {
let z = x % y;
z + if z < 0.0 { y } else { 0.0 }
}
Angle(lpr(x, TAU))
}
pub const fn to_latitude(self) -> Self {
match self.radians() > PI {
true => Angle(self.radians() - TAU),
false => self,
}
}
pub const fn degrees(self) -> f64 {
self.radians().to_degrees()
}
pub const fn from_degrees(x: f64) -> Self {
Angle::from_radians(x.to_radians())
}
pub const fn turns(self) -> f64 {
self.radians() / TAU
}
pub const fn from_turns(x: f64) -> Self {
Self::from_radians(x * TAU)
}
pub const fn decimal(self) -> f64 {
self.degrees() / 15.0 }
pub const fn from_decimal(x: f64) -> Self {
Angle::from_degrees(x * 15.0)
}
pub fn clock(self) -> (u8, u8, f64) {
let y = self.decimal();
(
y.trunc() as u8,
(y.fract() * 60.0).trunc() as u8,
(y.fract() * 60.0).fract() * 60.0,
)
}
pub const fn from_clock(h: u8, m: u8, s: f64) -> Self {
Angle::from_decimal((h as f64) + (((m as f64) + (s / 60.0)) / 60.0))
}
pub fn degminsec(self) -> (i16, u8, f64) {
let y = self.degrees();
(
y.trunc() as i16,
(y.fract() * 60.0).trunc() as u8,
(y.fract() * 60.0).fract() * 60.0,
)
}
pub const fn from_degminsec(d: i16, m: u8, s: f64) -> Self {
Angle::from_degrees((d as f64) + (((m as f64) + (s / 60.0)) / 60.0))
}
pub fn gst(self, date: Date) -> Self {
let t = date.centuries();
Angle::from_decimal(
6.697374558
+ (2400.051336 * t)
+ (0.000025862 * (t * t))
+ (self.decimal() * 1.002737909),
)
}
pub fn ungst(self, date: Date) -> Self {
let t = date.centuries();
let t0 = Angle::from_decimal(6.697374558 + (2400.051336 * t) + (0.000025862 * (t * t)));
(self - t0) * 0.9972695663
}
pub fn sin(self) -> f64 {
self.radians().sin()
}
pub fn cos(self) -> f64 {
self.radians().cos()
}
pub fn tan(self) -> f64 {
self.radians().tan()
}
pub fn asin(x: f64) -> Self {
Angle::from_radians(x.asin())
}
pub fn acos(x: f64) -> Self {
Angle::from_radians(x.acos())
}
pub fn atan2(x: f64, y: f64) -> Self {
Angle::from_radians(x.atan2(y))
}
pub fn inverse(self) -> Self {
Angle::from_radians(TAU - self.radians())
}
pub fn refractdelta(self) -> Self {
Angle::from_degminsec(
0,
0,
(1.02
/ (self.degrees() + (10.3 / (self.degrees() + 5.11)))
.to_radians()
.tan())
* 60.0,
)
}
pub fn refract(self) -> Self {
if self.to_latitude().degrees() > 0.0 {
self + self.refractdelta()
} else {
self
}
}
}
impl fmt::Debug for Angle {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let (d, m, s) = self.degminsec();
write!(f, "{}°{}'{:.2}\"", d, m, s)
}
}
impl PartialEq for Angle {
fn eq(&self, other: &Self) -> bool {
let (d, m, _) = self.degminsec();
let (d2, m2, _) = other.degminsec();
d == d2 && m == m2
}
}
impl Add<Angle> for Angle {
type Output = Angle;
fn add(self, x: Self) -> Self {
Angle::from_radians(self.radians() + x.radians())
}
}
impl Sub<Angle> for Angle {
type Output = Angle;
fn sub(self, x: Self) -> Self {
Angle::from_radians(self.radians() - x.radians())
}
}
impl Mul<f64> for Angle {
type Output = Angle;
fn mul(self, x: f64) -> Self {
Angle::from_radians(self.radians() * x)
}
}
impl Div<f64> for Angle {
type Output = Angle;
fn div(self, x: f64) -> Self {
Angle::from_radians(self.radians() / x)
}
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub struct Date(f64);
impl Date {
pub const fn julian(self) -> f64 {
self.0
}
pub const fn from_julian(x: f64) -> Self {
Date(x)
}
pub const fn centuries(self) -> f64 {
(self.julian() - 2451545.0) / 36525.0
}
pub fn calendar(self) -> (i64, u8, u8, Angle) {
let j = self.julian() + 0.5;
let (i, f) = (j.trunc(), j.fract());
let b = if i > 2_299_160.0 {
let a = ((i - 1867216.25) / 36524.25).trunc();
i + 1.0 + a - (a / 4.0).trunc()
} else {
i
} + 1524.0;
let d = ((b - 122.2) / 365.25).trunc();
let e = (365.25 * d).trunc();
let g = ((b - e) / 30.6001).trunc();
let m = if g < 13.5 { g - 1.0 } else { g - 13.0 };
let y = if m > 2.5 { d - 4716.0 } else { d - 4715.0 };
let d = b - e + f - (30.6001 * g).trunc();
(y as i64, m as u8, d as u8, Angle::from_turns(d.fract()))
}
pub fn from_calendar(y: i64, m: u8, day: u8, t: Angle) -> Self {
let (year, month) = if m < 3 { (y - 1, m + 12) } else { (y, m) };
Date::from_julian(
if year >= 1582 {
2 - (year / 100) + (year / 400)
} else {
0
} as f64
+ (365.25 * year as f64 - if year < 0 { 0.75 } else { 0.0 }).trunc()
+ (30.6001 * (month + 1) as f64).trunc()
+ day as f64
+ t.turns()
+ 1_720_994.5,
)
}
pub fn time(self) -> Angle {
self.calendar().3
}
pub fn from_time(d: Self, t: Angle) -> Self {
let (y, m, d, _) = d.calendar();
Self::from_calendar(y, m, d, t)
}
pub const fn unix(self) -> f64 {
(self.julian() - 2440587.5) * 86400.0
}
pub const fn from_unix(t: f64) -> Self {
Date::from_julian((t / 86400.0) + 2440587.5)
}
pub fn now() -> Self {
use std::time::{SystemTime, UNIX_EPOCH};
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Expected pre-1970-01-01 date")
.as_secs() as f64;
Date::from_unix(now)
}
}
pub fn easter(year: i32) -> (i32, i32) {
let a = year % 19;
let (b, c) = (year / 100, year % 100);
let d = b / 4;
let g = (b - (b + 8) / 25 + 1) / 3;
let h = ((19 * a) + b - d - g + 15) % 30;
let l = (32 + (2 * b % 4) + (2 * c / 4) - h - c % 4) % 7;
let m = (a + (11 * h) + (22 * l)) / 451;
let n = (h + l - (7 * m) + 114) / 31;
let p = (h + l - (7 * m) + 114) % 31;
(n, p + 1)
}
pub const J2000: Date = Date::from_julian(2451545.0);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_julian() {
assert_eq!(
Date::from_julian(2_446_113.75),
Date::from_calendar(1985, 2, 17, Angle::from_decimal(6.0))
);
assert_eq!(
Date::from_julian(2_446_113.75).calendar(),
(1985, 2, 17, Angle::from_decimal(6.0))
);
assert_eq!(
Date::from_calendar(1967, 04, 12, Angle::from_turns(0.6))
.time()
.decimal(),
14.400000002235174
);
}
#[test]
fn test_calendar() {
assert_eq!(
Date::from_calendar(1000, 10, 20, Angle::default()).calendar(),
(1000, 10, 20, Angle::default())
);
assert_eq!(
Date::from_calendar(1000, 14, 20, Angle::default()).calendar(),
(1001, 2, 20, Angle::default())
);
}
#[test]
fn test_decimalhrs() {
assert_eq!(
Angle::from_clock(18, 31, 27.0),
Angle::from_decimal(18.52417)
);
assert_eq!(Angle::from_decimal(11.75), Angle::from_clock(11, 45, 0.0));
}
#[test]
fn test_gst() {
assert_eq!(
Angle::from_clock(14, 36, 51.6).gst(Date::from_julian(2_444_351.5)),
Angle::from_clock(4, 40, 5.23)
);
assert_eq!(
Angle::from_clock(4, 40, 5.23).ungst(Date::from_julian(2_444_351.5)),
Angle::from_clock(14, 36, 51.6)
);
}
#[test]
fn test_lati() {
assert_eq!(
Angle::from_degrees(-25.0).to_latitude().degrees(),
-25.00000000000002 );
}
#[test]
fn test_turn() {
assert_eq!(Angle::from_turns(0.5), Angle::from_degrees(180.0));
assert_eq!(Angle::from_degrees(180.0), Angle::from_turns(0.5));
}
#[test]
fn test_easter() {
assert_eq!(easter(2000), (4, 23));
assert_eq!(easter(2024), (3, 31));
}
#[test]
fn test_refract() {
assert_eq!(
Angle::from_degrees(25.0).refractdelta(),
Angle::from_degminsec(0, 2, 9.2)
);
assert_eq!(
Angle::from_degrees(-25.0).refract(),
Angle::from_degminsec(-25, 0, 0.0)
);
}
}