#![allow(clippy::all)]
#![allow(clippy::needless_return)]
use std::cell::RefCell;
use std::f64::consts::PI;
use std::sync::OnceLock;
use crate::egm96_data::EGM96_DATA;
const NMAX: usize = 360;
const NMAX1: usize = 361;
const N361: usize = 361;
const COEFFS: usize = 65341;
#[cfg(feature = "raster_15_min")]
const EGM96_15_BYTES: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/egm96-15.png"));
#[cfg(feature = "raster_5_min")]
const EGM96_5_BYTES: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/egm96-5.png"));
struct Egm96Scratch {
p: Box<[f64; COEFFS + 1]>,
sinml: Box<[f64; N361 + 1]>,
cosml: Box<[f64; N361 + 1]>,
rleg: Box<[f64; N361 + 1]>,
rlnn: Box<[f64; N361 + 1]>,
}
impl Egm96Scratch {
fn new() -> Self {
Self {
p: Box::new([0.0; COEFFS + 1]),
sinml: Box::new([0.0; N361 + 1]),
cosml: Box::new([0.0; N361 + 1]),
rleg: Box::new([0.0; N361 + 1]),
rlnn: Box::new([0.0; N361 + 1]),
}
}
}
thread_local! {
static SCRATCH: RefCell<Egm96Scratch> = RefCell::new(Egm96Scratch::new());
}
fn dscml(rlon: f64, sinml: &mut [f64; N361 + 1], cosml: &mut [f64; N361 + 1]) {
let a = rlon.sin();
let b = rlon.cos();
sinml[1] = a;
cosml[1] = b;
sinml[2] = 2.0 * b * a;
cosml[2] = 2.0 * b * b - 1.0;
for m in 3..=NMAX {
sinml[m] = 2.0 * b * sinml[m - 1] - sinml[m - 2];
cosml[m] = 2.0 * b * cosml[m - 1] - cosml[m - 2];
}
}
fn hundu(
p: &[f64; COEFFS + 1],
sinml: &[f64; N361 + 1],
cosml: &[f64; N361 + 1],
gr: f64,
re: f64,
) -> f64 {
const GM: f64 = 0.3986004418e15;
const AE: f64 = 6378137.0;
let ar = AE / re;
let mut arn = ar;
let mut ac = 0.0;
let mut a = 0.0;
let mut k = 3;
for n in 2..=NMAX {
arn *= ar;
k += 1;
let mut sum = p[k] * EGM96_DATA[k][2] as f64;
let mut sumc = p[k] * EGM96_DATA[k][0] as f64;
for m in 1..=n {
k += 1;
let tempc = EGM96_DATA[k][0] as f64 * cosml[m] + EGM96_DATA[k][1] as f64 * sinml[m];
let temp = EGM96_DATA[k][2] as f64 * cosml[m] + EGM96_DATA[k][3] as f64 * sinml[m];
sumc += p[k] * tempc;
sum += p[k] * temp;
}
ac += sumc;
a += sum * arn;
}
ac += EGM96_DATA[1][0] as f64
+ (p[2] * EGM96_DATA[2][0] as f64)
+ (p[3] * (EGM96_DATA[3][0] as f64 * cosml[1] + EGM96_DATA[3][1] as f64 * sinml[1]));
((a * GM) / (gr * re)) + (ac / 100.0) - 0.53
}
fn radgra(lat: f64, lon: f64, rlat: &mut f64, gr: &mut f64, re: &mut f64) {
const A: f64 = 6378137.0;
const E2: f64 = 0.00669437999013;
const GEQT: f64 = 9.7803253359;
const K: f64 = 0.00193185265246;
let t1 = lat.sin().powi(2);
let n = A / (1.0 - (E2 * t1)).sqrt();
let t2 = n * lat.cos();
let x = t2 * lon.cos();
let y = t2 * lon.sin();
let z = (n * (1.0 - E2)) * lat.sin();
*re = (x * x + y * y + z * z).sqrt(); *rlat = (z / (x * x + y * y).sqrt()).atan(); *gr = GEQT * (1.0 + (K * t1)) / (1.0 - (E2 * t1)).sqrt(); }
fn undulation(lat: f64, lon: f64) -> f64 {
static DRTS_DIRT: OnceLock<([f64; 1301], [f64; 1301])> = OnceLock::new();
let (drts, dirt) = DRTS_DIRT.get_or_init(|| {
let nmax2p = (2 * NMAX) + 1;
let mut drts = [0.0; 1301];
let mut dirt = [0.0; 1301];
for n in 1..=nmax2p {
drts[n] = (n as f64).sqrt();
dirt[n] = 1.0 / drts[n];
}
(drts, dirt)
});
SCRATCH.with(|scratch| {
let mut s = scratch.borrow_mut();
let Egm96Scratch {
p,
sinml,
cosml,
rleg,
rlnn,
} = &mut *s;
let mut rlat = 0.0;
let mut gr = 0.0;
let mut re = 0.0;
radgra(lat, lon, &mut rlat, &mut gr, &mut re);
rlat = (PI / 2.0) - rlat;
let cothet = rlat.cos();
let sithet = rlat.sin();
rlnn[1] = 1.0;
rlnn[2] = sithet * drts[3];
for j in 1..=NMAX1 {
let m = j - 1;
let m1 = m + 1;
for n1 in 3..=m1 {
let n = n1 - 1;
let n2 = 2 * n;
rlnn[n1] = drts[n2 + 1] * dirt[n2] * sithet * rlnn[n];
}
}
for j in 1..=NMAX1 {
let m = j - 1;
let m1 = m + 1;
let m2 = m + 2;
let m3 = m + 3;
if m == 0 {
rleg[1] = 1.0;
rleg[2] = cothet * drts[3];
} else if m == 1 {
rleg[2] = rlnn[2];
rleg[3] = drts[5] * cothet * rleg[2];
}
rleg[m1] = rlnn[m1];
if m2 <= NMAX1 {
rleg[m2] = drts[m1 * 2 + 1] * cothet * rleg[m1];
for n1 in m3..=NMAX1 {
let n = n1 - 1;
if (m != 0 && n < 2) || (m == 1 && n < 3) {
continue;
}
let n2 = 2 * n;
rleg[n1] = drts[n2 + 1]
* dirt[n + m]
* dirt[n - m]
* (drts[n2 - 1] * cothet * rleg[n1 - 1]
- drts[n + m - 1] * drts[n - m - 1] * dirt[n2 - 3] * rleg[n1 - 2]);
}
}
for i in j..=NMAX1 {
p[((i - 1) * i) / 2 + m + 1] = rleg[i];
}
}
dscml(lon, sinml, cosml);
hundu(p, sinml, cosml, gr, re)
})
}
fn wrap_degrees(mut degrees: f64) -> f64 {
degrees += 180.0;
degrees = degrees.rem_euclid(360.0);
degrees - 180.0
}
pub fn egm96_compute_altitude_offset(lat: f64, lon: f64) -> f64 {
let lon = wrap_degrees(lon);
let lat = lat.clamp(-90.0, 90.0);
undulation(lat.to_radians(), lon.to_radians())
}
#[allow(unused)]
fn interpolate<const WIDTH: usize, const HEIGHT: usize>(
lat: f64,
lon: f64,
x_start: f64,
y_start: f64,
x_step: f64,
y_step: f64,
pixels: &[u16],
) -> f64 {
const SCALE: f64 = 0.003;
const OFFSET: f64 = -108.0;
let x = (lon - x_start) / x_step;
let y = (lat - y_start) / y_step;
let x0 = x.floor() as isize;
let y0 = y.floor() as isize;
let x1 = x0 + 1;
let y1 = y0 + 1;
let x0_clamped = x0.clamp(0, (WIDTH - 1) as isize) as usize;
let y0_clamped = y0.clamp(0, (HEIGHT - 1) as isize) as usize;
let x1_clamped = x1.clamp(0, (WIDTH - 1) as isize) as usize;
let y1_clamped = y1.clamp(0, (HEIGHT - 1) as isize) as usize;
let dx = (x - x0 as f64).clamp(0.0, 1.0);
let dy = (y - y0 as f64).clamp(0.0, 1.0);
let top_left = pixels[y0_clamped * WIDTH + x0_clamped] as f64 * SCALE + OFFSET;
let top_right = pixels[y0_clamped * WIDTH + x1_clamped] as f64 * SCALE + OFFSET;
let bottom_left = pixels[y1_clamped * WIDTH + x0_clamped] as f64 * SCALE + OFFSET;
let bottom_right = pixels[y1_clamped * WIDTH + x1_clamped] as f64 * SCALE + OFFSET;
let top = top_left + dx * (top_right - top_left);
let bottom = bottom_left + dx * (bottom_right - bottom_left);
top + dy * (bottom - top)
}
#[cfg(any(feature = "raster_15_min", feature = "raster_5_min"))]
fn load_image<const WIDTH: usize, const HEIGHT: usize>(bytes: &[u8]) -> Vec<u16> {
let decoder = png::Decoder::new(std::io::Cursor::new(bytes));
let mut reader = decoder.read_info().expect("Failed to check info");
let mut buf = vec![
0;
reader
.output_buffer_size()
.expect("output buffer too large")
];
let info = reader.next_frame(&mut buf).expect("Failed to get frame");
buf.truncate(info.buffer_size());
assert!(buf.len() == WIDTH * HEIGHT * 2);
let mut out = vec![0; WIDTH * HEIGHT];
for row in 0..HEIGHT {
for col in 0..WIDTH {
let index = 2 * (col + row * WIDTH);
out[row * WIDTH + col] =
u16::from_be_bytes(buf[index..(index + 2)].try_into().expect("pair"));
}
}
out
}
#[cfg(feature = "raster_5_min")]
pub fn egm96_raster_5_min_altitude_offset(lat: f64, lon: f64) -> f64 {
const WIDTH: usize = 4320;
const HEIGHT: usize = 2161;
static IMAGE: OnceLock<Vec<u16>> = OnceLock::new();
let image = IMAGE.get_or_init(|| load_image::<WIDTH, HEIGHT>(EGM96_5_BYTES));
let mut lon = wrap_degrees(lon);
if lon < 0.0 {
lon += 360.0;
}
let lat = lat.clamp(-90.0, 90.0);
interpolate::<WIDTH, HEIGHT>(
lat,
lon,
-0.04166666666666666,
90.04166666666666666,
0.08333333333333333,
-0.08333333333333333,
image,
)
}
#[cfg(feature = "raster_15_min")]
pub fn egm96_raster_15_min_altitude_offset(lat: f64, lon: f64) -> f64 {
const WIDTH: usize = 1440;
const HEIGHT: usize = 721;
static IMAGE: OnceLock<Vec<u16>> = OnceLock::new();
let image = IMAGE.get_or_init(|| load_image::<WIDTH, HEIGHT>(EGM96_15_BYTES));
let mut lon = wrap_degrees(lon);
if lon < 0.0 {
lon += 360.0;
}
let lat = lat.clamp(-90.0, 90.0);
interpolate::<WIDTH, HEIGHT>(lat, lon, -0.125, 90.125, 0.25, -0.25, image)
}
pub fn egm96_altitude_offset(lat: f64, lon: f64) -> f64 {
#[cfg(feature = "raster_5_min")]
{
egm96_raster_5_min_altitude_offset(lat, lon)
}
#[cfg(all(feature = "raster_15_min", not(feature = "raster_5_min")))]
{
egm96_raster_15_min_altitude_offset(lat, lon)
}
#[cfg(all(not(feature = "raster_15_min"), not(feature = "raster_5_min")))]
{
egm96_compute_altitude_offset(lat, lon)
}
}
#[cfg(test)]
#[path = "algo_test.rs"]
mod algo_test;