#[cfg(test)]
mod tests;
use crate::astro::constants::units::{DEG_TO_RAD, KM_TO_M};
use crate::astro::frames::transforms::itrs_to_geodetic_compute;
use crate::astro::math::vec3::norm3_ref as norm;
use crate::validate;
use super::{cal2jd, invalid_tide_input, TideError};
pub const NUM_OCEAN_CONSTITUENTS: usize = 11;
const TWO_PI: f64 = 2.0 * std::f64::consts::PI;
const SPEED_RAD_S: [f64; NUM_OCEAN_CONSTITUENTS] = [
1.405_19e-4,
1.454_44e-4,
1.378_80e-4,
1.458_42e-4,
0.729_21e-4,
0.675_98e-4,
0.725_23e-4,
0.649_59e-4,
0.053_234e-4,
0.026_392e-4,
0.003_982e-4,
];
#[rustfmt::skip]
const ANGFAC: [[f64; 4]; NUM_OCEAN_CONSTITUENTS] = [
[ 2.0, -2.0, 0.0, 0.00], [ 0.0, 0.0, 0.0, 0.00], [ 2.0, -3.0, 1.0, 0.00], [ 2.0, 0.0, 0.0, 0.00], [ 1.0, 0.0, 0.0, 0.25], [ 1.0, -2.0, 0.0, -0.25], [-1.0, 0.0, 0.0, -0.25], [ 1.0, -3.0, 1.0, -0.25], [ 0.0, 2.0, 0.0, 0.00], [ 0.0, 1.0, -1.0, 0.00], [ 2.0, 0.0, 0.0, 0.00], ];
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct OceanLoadingBlq {
pub amplitude_m: [[f64; NUM_OCEAN_CONSTITUENTS]; 3],
pub phase_deg: [[f64; NUM_OCEAN_CONSTITUENTS]; 3],
}
pub fn ocean_tide_loading(
xsta: &[f64; 3],
year: i32,
month: i32,
day: i32,
fhr: f64,
blq: &OceanLoadingBlq,
) -> Result<[f64; 3], TideError> {
validate_ocean_loading_domain(xsta, year, month, day, fhr, blq)?;
Ok(ocean_tide_loading_unchecked(
xsta, year, month, day, fhr, blq,
))
}
fn validate_ocean_loading_domain(
xsta: &[f64; 3],
year: i32,
month: i32,
day: i32,
fhr: f64,
blq: &OceanLoadingBlq,
) -> Result<(), TideError> {
validate::finite_vec3(*xsta, "station position").map_err(invalid_tide_input)?;
validate::civil_datetime_with_second_policy(
i64::from(year),
i64::from(month),
i64::from(day),
0,
0,
0.0,
validate::CivilSecondPolicy::Continuous,
)
.map_err(invalid_tide_input)?;
validate::finite_in_range_exclusive_upper(fhr, 0.0, 24.0, "fractional hour")
.map_err(invalid_tide_input)?;
for component in &blq.amplitude_m {
for &litude in component {
validate::finite(amplitude, "ocean loading amplitude").map_err(invalid_tide_input)?;
}
}
for component in &blq.phase_deg {
for &phase in component {
validate::finite(phase, "ocean loading phase").map_err(invalid_tide_input)?;
}
}
validate::finite_positive(norm(xsta), "station radius").map_err(invalid_tide_input)?;
Ok(())
}
fn ocean_tide_loading_unchecked(
xsta: &[f64; 3],
year: i32,
month: i32,
day: i32,
fhr: f64,
blq: &OceanLoadingBlq,
) -> [f64; 3] {
let arg = arg2_angles(year, month, day, fhr);
let mut component = [0.0_f64; 3];
for (slot, (amplitudes, phases)) in component
.iter_mut()
.zip(blq.amplitude_m.iter().zip(blq.phase_deg.iter()))
{
let mut sum = 0.0;
for ((&litude, &phase_deg), &a) in amplitudes.iter().zip(phases).zip(&arg) {
sum += amplitude * (a - phase_deg * DEG_TO_RAD).cos();
}
*slot = sum;
}
let up = component[0];
let west = component[1];
let south = component[2];
let east = -west;
let north = -south;
let (lat_deg, lon_deg, _height_km) =
itrs_to_geodetic_compute(xsta[0] / KM_TO_M, xsta[1] / KM_TO_M, xsta[2] / KM_TO_M)
.expect("validated station position yields geodetic coordinates");
let (sinlat, coslat) = (lat_deg * DEG_TO_RAD).sin_cos();
let (sinlon, coslon) = (lon_deg * DEG_TO_RAD).sin_cos();
[
east * (-sinlon) + north * (-sinlat * coslon) + up * (coslat * coslon),
east * coslon + north * (-sinlat * sinlon) + up * (coslat * sinlon),
north * coslat + up * sinlat,
]
}
fn arg2_angles(year: i32, month: i32, day: i32, fhr: f64) -> [f64; NUM_OCEAN_CONSTITUENTS] {
let doy = day_of_year(year, month, day);
let fday = fhr * 3600.0;
let icapd = doy + 365 * (year - 1975) + (year - 1973) / 4;
let capt = (27_392.500_528 + 1.000_000_035 * f64::from(icapd)) / 36_525.0;
let h0 = (279.696_68 + (36_000.768_930_485 + 3.03e-4 * capt) * capt) * DEG_TO_RAD;
let s0 = (((1.9e-6 * capt - 0.001_133) * capt + 481_267.883_141_37) * capt + 270.434_358)
* DEG_TO_RAD;
let p0 = (((-1.2e-5 * capt - 0.010_325) * capt + 4_069.034_032_957_7) * capt + 334.329_653)
* DEG_TO_RAD;
let mut angle = [0.0_f64; NUM_OCEAN_CONSTITUENTS];
for (j, slot) in angle.iter_mut().enumerate() {
let a = SPEED_RAD_S[j] * fday
+ ANGFAC[j][0] * h0
+ ANGFAC[j][1] * s0
+ ANGFAC[j][2] * p0
+ ANGFAC[j][3] * TWO_PI;
*slot = a.rem_euclid(TWO_PI);
}
angle
}
fn day_of_year(year: i32, month: i32, day: i32) -> i32 {
let (_, mjd) = cal2jd(year, month, day);
let (_, mjd_jan1) = cal2jd(year, 1, 1);
(mjd - mjd_jan1).round() as i32 + 1
}