use crate::formats::{FileLocation, FormatError};
use crate::qtty::Degrees;
const NZ: f64 = 15.0;
#[inline]
fn dlat(odd: bool) -> f64 {
360.0 / (4.0 * NZ - if odd { 1.0 } else { 0.0 })
}
fn nl(lat: f64) -> f64 {
if lat.abs() >= 87.0 {
return 1.0;
}
let cos_lat = lat.to_radians().cos();
let numer = 1.0 - (std::f64::consts::PI / (2.0 * NZ)).cos();
let arg = 1.0 - numer / (cos_lat * cos_lat);
let n = (2.0 * std::f64::consts::PI / arg.acos()).floor();
n.clamp(1.0, 59.0)
}
pub fn decode_globally(
lat0: u32,
lon0: u32,
lat1: u32,
lon1: u32,
odd_is_latest: bool,
) -> Result<(Degrees, Degrees), FormatError> {
let dlat0 = dlat(false);
let dlat1 = dlat(true);
let j = (59.0 * (lat0 as f64 / 131_072.0) - 60.0 * (lat1 as f64 / 131_072.0) + 0.5).floor();
let lat_e = dlat0 * (j.rem_euclid(60.0) + lat0 as f64 / 131_072.0);
let lat_o = dlat1 * (j.rem_euclid(59.0) + lat1 as f64 / 131_072.0);
let lat_e = if lat_e >= 270.0 { lat_e - 360.0 } else { lat_e };
let lat_o = if lat_o >= 270.0 { lat_o - 360.0 } else { lat_o };
if (nl(lat_e) - nl(lat_o)).abs() > 0.5 {
return Err(FormatError::located(
"ADS-B DO-260B §2.2.3.2.6",
FileLocation::default(),
"NL mismatch: even/odd frame pair straddles a latitude zone boundary",
));
}
let lat = if odd_is_latest { lat_o } else { lat_e };
let nl_lat = nl(lat);
let (lon, lon_ref) = if odd_is_latest {
let dlon = if nl_lat <= 1.0 {
360.0
} else {
360.0 / (nl_lat - 1.0)
};
let m = (lon0 as f64 * (nl_lat - 1.0) / 131_072.0 - lon1 as f64 * nl_lat / 131_072.0 + 0.5)
.floor();
(
dlon * (m.rem_euclid(nl_lat - 1.0) + lon1 as f64 / 131_072.0),
lat_o,
)
} else {
let dlon = if nl_lat < 1.0 { 360.0 } else { 360.0 / nl_lat };
let m = (lon0 as f64 * (nl_lat - 1.0) / 131_072.0 - lon1 as f64 * nl_lat / 131_072.0 + 0.5)
.floor();
(
dlon * (m.rem_euclid(nl_lat) + lon0 as f64 / 131_072.0),
lat_e,
)
};
let _ = lon_ref;
let lon = if lon >= 180.0 { lon - 360.0 } else { lon };
Ok((Degrees::new(lat), Degrees::new(lon)))
}
pub fn decode_locally(
cpr_lat: u32,
cpr_lon: u32,
odd: bool,
ref_lat: f64,
ref_lon: f64,
) -> Result<(Degrees, Degrees), FormatError> {
let d_lat = dlat(odd);
let j = (ref_lat / d_lat - cpr_lat as f64 / 131_072.0 + 0.5).floor();
let lat = d_lat * (j + cpr_lat as f64 / 131_072.0);
let lat = if lat >= 270.0 { lat - 360.0 } else { lat };
let nl_val = nl(lat);
let n_lon = if odd {
(nl_val - 1.0).max(1.0)
} else {
nl_val.max(1.0)
};
let d_lon = 360.0 / n_lon;
let m = (ref_lon / d_lon - cpr_lon as f64 / 131_072.0 + 0.5).floor();
let lon = d_lon * (m + cpr_lon as f64 / 131_072.0);
let lon = if lon >= 180.0 { lon - 360.0 } else { lon };
Ok((Degrees::new(lat), Degrees::new(lon)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn global_decode_sun_example() {
let (lat, lon) = decode_globally(93_000, 51_372, 73_974, 49_949, false).unwrap();
assert!((lat.value() - 52.257).abs() < 0.01, "lat={}", lat.value());
assert!((lon.value() - 3.919).abs() < 0.02, "lon={}", lon.value());
}
#[test]
fn local_decode_sun_example() {
let (lat, lon) = decode_locally(93_000, 51_372, false, 52.0, 4.9).unwrap();
assert!((lat.value() - 52.257).abs() < 0.01, "lat={}", lat.value());
assert!((lon.value() - 3.919).abs() < 0.1, "lon={}", lon.value());
}
#[test]
fn nl_equator() {
assert_eq!(nl(0.0) as u32, 59);
}
#[test]
fn nl_polar() {
assert_eq!(nl(87.5) as u32, 1);
}
}