use core::f64::consts::PI;
pub const DEC_BOUNDARIES_1476: [f64; 37] = [
-90.0_f64 * PI / 180.0,
-87.42857143 * PI / 180.0,
-82.28571429 * PI / 180.0,
-77.14285714 * PI / 180.0,
-72.0 * PI / 180.0,
-66.85714286 * PI / 180.0,
-61.71428571 * PI / 180.0,
-56.57142857 * PI / 180.0,
-51.42857143 * PI / 180.0,
-46.28571429 * PI / 180.0,
-41.14285714 * PI / 180.0,
-36.0 * PI / 180.0,
-30.85714286 * PI / 180.0,
-25.71428571 * PI / 180.0,
-20.57142857 * PI / 180.0,
-15.42857143 * PI / 180.0,
-10.28571429 * PI / 180.0,
-5.142857143 * PI / 180.0,
0.0,
5.142857143 * PI / 180.0,
10.28571429 * PI / 180.0,
15.42857143 * PI / 180.0,
20.57142857 * PI / 180.0,
25.71428571 * PI / 180.0,
30.85714286 * PI / 180.0,
36.0 * PI / 180.0,
41.14285714 * PI / 180.0,
46.28571429 * PI / 180.0,
51.42857143 * PI / 180.0,
56.57142857 * PI / 180.0,
61.71428571 * PI / 180.0,
66.85714286 * PI / 180.0,
72.0 * PI / 180.0,
77.14285714 * PI / 180.0,
82.28571429 * PI / 180.0,
87.42857143 * PI / 180.0,
90.0_f64 * PI / 180.0,
];
const RING_TABLE: [(usize, usize); 36] = [
(1, 1), (3, 2), (9, 5), (15, 14), (21, 29), (27, 50), (33, 77), (38, 110), (43, 148), (48, 191), (52, 239), (56, 291), (60, 347), (63, 407), (65, 470), (67, 535), (68, 602), (69, 670), (69, 739), (68, 808), (67, 876), (65, 943), (63, 1008), (60, 1071), (56, 1131), (52, 1187), (48, 1239), (43, 1287), (38, 1330), (33, 1368), (27, 1401), (21, 1428), (15, 1449), (9, 1464), (3, 1473), (1, 1476), ];
#[derive(Debug, Clone, Copy)]
pub struct AreaBounds {
pub area_nr: usize,
pub space_east: f64,
pub space_west: f64,
pub space_north: f64,
pub space_south: f64,
}
#[must_use]
pub fn area_and_boundaries_1476(ra: f64, dec: f64) -> AreaBounds {
let cos_dec = dec.cos();
if dec > DEC_BOUNDARIES_1476[35] {
return AreaBounds {
area_nr: 1476,
space_east: PI * 2.0,
space_west: PI * 2.0,
space_north: DEC_BOUNDARIES_1476[36] - DEC_BOUNDARIES_1476[35],
space_south: dec - DEC_BOUNDARIES_1476[35],
};
}
for ring in (1usize..=34).rev() {
if dec > DEC_BOUNDARIES_1476[ring] {
let (n_ra, base) = RING_TABLE[ring];
let rot = ra * n_ra as f64 / (2.0 * PI);
let area_nr = base + rot.floor() as usize;
let frac = rot.fract();
let ra_step = 2.0 * PI / n_ra as f64;
let north_boundary = DEC_BOUNDARIES_1476[ring + 1];
return AreaBounds {
area_nr,
space_east: ra_step * (1.0 - frac) * cos_dec,
space_west: ra_step * frac * cos_dec,
space_north: north_boundary - dec,
space_south: dec - DEC_BOUNDARIES_1476[ring],
};
}
}
AreaBounds {
area_nr: 1,
space_east: PI * 2.0,
space_west: PI * 2.0,
space_north: DEC_BOUNDARIES_1476[1] - dec,
space_south: DEC_BOUNDARIES_1476[1] - DEC_BOUNDARIES_1476[0],
}
}
#[must_use]
pub fn areas_in_dec_band_1476(dec_lo: f64, dec_hi: f64) -> Vec<usize> {
let mut out = Vec::new();
for (ring, &(n_ra, base)) in RING_TABLE.iter().enumerate() {
if DEC_BOUNDARIES_1476[ring + 1] >= dec_lo && DEC_BOUNDARIES_1476[ring] <= dec_hi {
out.extend(base..base + n_ra);
}
}
out
}
#[must_use]
pub fn filename_1476(area_nr: usize) -> String {
let area = area_nr.clamp(1, 1476);
let mut ring_idx = 0usize;
for (r, &(n_ra, base)) in RING_TABLE.iter().enumerate() {
if area >= base && area < base + n_ra {
ring_idx = r;
break;
}
if r == 35 {
ring_idx = 35; }
}
let (n_ra, base) = RING_TABLE[ring_idx];
let cell_1indexed = if n_ra == 1 { 1 } else { area - base + 1 };
format!("{:02}{:02}.1476", ring_idx + 1, cell_1indexed)
}
#[must_use]
pub fn find_areas_1476(ra: f64, dec: f64, fov: f64) -> Vec<(usize, f64)> {
let fov = fov.min(5.142857_f64.to_radians()); let fov_half = fov * 0.5;
let dec_n = dec + fov_half;
let dec_s = dec - fov_half;
let cos_n = dec_n.cos().max(1e-6);
let cos_s = dec_s.cos().max(1e-6);
let mut ra_wn = ra - fov_half / cos_n;
if ra_wn < 0.0 {
ra_wn += 2.0 * PI;
}
let mut ra_en = ra + fov_half / cos_n;
if ra_en >= 2.0 * PI {
ra_en -= 2.0 * PI;
}
let mut ra_ws = ra - fov_half / cos_s;
if ra_ws < 0.0 {
ra_ws += 2.0 * PI;
}
let mut ra_es = ra + fov_half / cos_s;
if ra_es >= 2.0 * PI {
ra_es -= 2.0 * PI;
}
let corners = [
(ra_en, dec_n),
(ra_wn, dec_n),
(ra_es, dec_s),
(ra_ws, dec_s),
];
let fov2 = fov * fov;
let corner_fracs = |_b: AreaBounds, space_h_toward: f64, space_v_toward: f64| -> f64 {
let h = space_h_toward.min(fov);
let v = space_v_toward.min(fov);
h * v / fov2
};
let b: Vec<AreaBounds> = corners
.iter()
.map(|&(r, d)| area_and_boundaries_1476(r, d))
.collect();
let f0 = corner_fracs(b[0], b[0].space_west, b[0].space_south);
let f1 = corner_fracs(b[1], b[1].space_east, b[1].space_south);
let f2 = corner_fracs(b[2], b[2].space_west, b[2].space_north);
let f3 = corner_fracs(b[3], b[3].space_east, b[3].space_north);
let raw = [
(b[0].area_nr, f0),
(b[1].area_nr, f1),
(b[2].area_nr, f2),
(b[3].area_nr, f3),
];
let mut seen = [0usize; 4];
let mut result = Vec::with_capacity(4);
for (area, frac) in raw {
if frac < 0.01 {
continue;
}
if seen.contains(&area) {
continue;
}
seen[result.len()] = area;
result.push((area, frac));
}
result
}
#[cfg(test)]
mod tests {
use super::*;
fn deg(d: f64) -> f64 {
d * PI / 180.0
}
#[test]
fn south_pole_area_is_1() {
let b = area_and_boundaries_1476(0.0, deg(-89.0));
assert_eq!(b.area_nr, 1);
}
#[test]
fn north_pole_area_is_1476() {
let b = area_and_boundaries_1476(0.0, deg(89.0));
assert_eq!(b.area_nr, 1476);
}
#[test]
fn equator_area_in_valid_range() {
let b = area_and_boundaries_1476(deg(180.0), 0.0);
assert!(b.area_nr >= 670 && b.area_nr <= 807, "area={}", b.area_nr);
}
#[test]
fn filename_south_pole() {
assert_eq!(filename_1476(1), "0101.1476");
}
#[test]
fn filename_ring1_cell2() {
assert_eq!(filename_1476(3), "0202.1476");
}
#[test]
fn filename_north_pole() {
assert_eq!(filename_1476(1476), "3601.1476");
}
#[test]
fn find_areas_small_fov_returns_at_least_one() {
let areas = find_areas_1476(deg(45.0), deg(20.0), deg(2.0));
assert!(!areas.is_empty(), "should find at least one area");
for (a, f) in &areas {
assert!(*a >= 1 && *a <= 1476, "area out of range: {a}");
assert!(*f >= 0.01, "fraction {f} below threshold");
}
}
#[test]
fn find_areas_no_duplicates() {
let areas = find_areas_1476(deg(0.0), deg(0.0), deg(4.0));
let mut seen = std::collections::HashSet::new();
for (a, _) in areas {
assert!(seen.insert(a), "duplicate area {a}");
}
}
#[test]
fn area_nr_agrees_with_ring_offsets() {
assert_eq!(filename_1476(2), "0201.1476");
assert_eq!(filename_1476(5), "0301.1476");
assert_eq!(filename_1476(14), "0401.1476");
}
#[test]
fn boundary_distances_positive() {
let b = area_and_boundaries_1476(deg(90.0), deg(30.0));
assert!(b.space_north > 0.0, "space_north = {}", b.space_north);
assert!(b.space_south > 0.0, "space_south = {}", b.space_south);
assert!(b.space_east > 0.0, "space_east = {}", b.space_east);
assert!(b.space_west > 0.0, "space_west = {}", b.space_west);
}
}