#![allow(non_snake_case)]
use chrono::{DateTime, TimeZone};
use radians::{Angle, Deg64, Rad64, Unit, Wrap64};
use std::f64::consts::PI;
use std::ops::{Index, IndexMut, Mul, Neg};
use std::slice::SliceIndex;
#[derive(PartialEq, Default, Copy, Clone, Debug)]
struct Vector([f64; 3]);
impl<I: SliceIndex<[f64]>> Index<I> for Vector {
type Output = I::Output;
fn index(&self, index: I) -> &Self::Output {
&self.0[index]
}
}
impl<I: SliceIndex<[f64]>> IndexMut<I> for Vector {
fn index_mut(&mut self, index: I) -> &mut Self::Output {
&mut self.0[index]
}
}
impl Mul<f64> for Vector {
type Output = Self;
fn mul(self, rhs: f64) -> Self::Output {
Vector(self.0.map(|x| x * rhs))
}
}
impl Neg for Vector {
type Output = Self;
fn neg(self) -> Self::Output {
Vector(self.0.map(|x| -x))
}
}
impl Vector {
fn iter(&self) -> std::slice::Iter<'_, f64> {
self.0.iter()
}
fn normalized(&self) -> Self {
*self * (1.0 / dot(self, self)).sqrt()
}
}
fn dot(a: &Vector, b: &Vector) -> f64 {
a.iter()
.zip(b.iter())
.map(|(x, y)| x * y)
.fold(0.0, |x, y| x + y)
}
fn cross(a: &Vector, b: &Vector) -> Vector {
Vector([
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0],
])
}
#[derive(PartialEq, Default, Copy, Clone, Debug)]
struct Matrix([[f64; 3]; 3]);
impl Index<(usize, usize)> for Matrix {
type Output = f64;
fn index(&self, (r, c): (usize, usize)) -> &Self::Output {
&self.0[r][c]
}
}
impl IndexMut<(usize, usize)> for Matrix {
fn index_mut(&mut self, (r, c): (usize, usize)) -> &mut Self::Output {
&mut self.0[r][c]
}
}
impl Mul<Vector> for Matrix {
type Output = Vector;
fn mul(self, rhs: Vector) -> Self::Output {
Vector(self.0.map(|x| dot(&Vector(x), &rhs)))
}
}
fn kepler(M: Wrap64, e: f64) -> Wrap64 {
let mut E = M.val() - e * M.sin();
loop {
let dM = M.val() - (E - e * E.sin());
let dE = dM / (1.0 - e * E.cos());
E += dE;
if dE.abs() < 1e-8 {
return Rad64::new(E).wrap();
}
}
}
fn Rx<U: Unit<f64>>(r: Angle<f64, U>) -> Matrix {
let (s, c) = r.sin_cos();
Matrix([[1.0, 0.0, 0.0], [0.0, c, s], [0.0, -s, c]])
}
fn Rz<U: Unit<f64>>(r: Angle<f64, U>) -> Matrix {
let (s, c) = r.sin_cos();
Matrix([[c, s, 0.0], [-s, c, 0.0], [0.0, 0.0, 1.0]])
}
fn timestamp_f64<Tz: TimeZone>(time: &DateTime<Tz>, epoch: f64) -> f64 {
((time.timestamp() as f64 - epoch) + 1e-9 * (time.timestamp_subsec_nanos() as f64)) / 86400.0
}
fn earth_rotation_angle<Tz: TimeZone>(time: &DateTime<Tz>) -> Wrap64 {
let t = timestamp_f64(time, 946728000.0);
Rad64::new((t.fract() + 0.779057273264 + 0.00273781191135448 * t).fract() * 2.0 * PI).wrap()
}
fn sun_direction<Tz, U1, U2>(
lat: Angle<f64, U1>,
lon: Angle<f64, U2>,
time: &DateTime<Tz>,
) -> Vector
where
Tz: TimeZone,
U1: Unit<f64>,
U2: Unit<f64>,
{
const J2000_EPOCH: f64 = 946727935.816;
let OBLIQUITY = Deg64::new(23.43928);
let T = timestamp_f64(time, J2000_EPOCH) / 36525.0; let e = 0.01673163 - 0.00003661 * T;
let I = Deg64::new(-0.00054346 - 0.01337178 * T);
let L = Deg64::new(100.46691572 + 35999.37306329 * T);
let ω_bar = Deg64::new(102.93005885 + 0.31795260 * T);
let Ω = Deg64::new(-5.11260389 - 0.24123856 * T);
let ω = ω_bar - Ω;
let M = (L - ω_bar).rad().wrap();
let E = kepler(M, e);
let r_orbital = Vector([E.cos() - e, (1.0 - e * e).sqrt() * E.sin(), 0.0]);
let r_eq = Rx(-OBLIQUITY) * (Rz(-Ω) * (Rx(-I) * (Rz(-ω) * r_orbital)));
let r_cirs = -r_eq;
let era = earth_rotation_angle(time);
let r_tirs = Rz(era.inner()) * r_cirs;
let (slat, clat) = lat.sin_cos();
let (slon, clon) = lon.sin_cos();
let l_z = Vector([clat * clon, clat * slon, slat]);
let l_x = cross(&Vector([0.0, 0.0, 1.0]), &l_z).normalized();
let l_y = cross(&l_z, &l_x);
Matrix([l_x.0, l_y.0, l_z.0]) * r_tirs.normalized() }
pub fn solar_fraction<Tz, U1, U2, U3, U4>(
lat: Angle<f64, U1>,
lon: Angle<f64, U2>,
elevation: Angle<f64, U3>,
azimuth: Angle<f64, U4>,
time: &DateTime<Tz>,
) -> f64
where
Tz: TimeZone,
U1: Unit<f64>,
U2: Unit<f64>,
U3: Unit<f64>,
U4: Unit<f64>,
{
let sun_dir = sun_direction(lat, lon, time);
if sun_dir[2] <= 0.0 {
return 0.0; }
let (s_el, c_el) = elevation.sin_cos();
let (s_az, c_az) = azimuth.sin_cos();
let panel_dir = Vector([c_el * s_az, c_el * c_az, s_el]);
dot(&sun_dir, &panel_dir).max(0.0)
}