use crate::portable_math::PortableFloat;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct CellId {
pub i: i32,
pub j: i32,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Grid {
pub cell_deg: f64,
}
pub const PREREGISTERED_CELL_DEG: f64 = 0.5;
pub const EARTH_RADIUS_M: f64 = 6_371_008.8;
impl Grid {
pub fn new(cell_deg: f64) -> Option<Self> {
if cell_deg.is_finite() && (0.01..=10.0).contains(&cell_deg) {
Some(Self { cell_deg })
} else {
None
}
}
pub fn cell_of(&self, lat: f64, lon: f64) -> CellId {
let rows = (180.0 / self.cell_deg).ceil() as i32;
let lon = if lon >= 180.0 { lon - 360.0 } else { lon };
CellId {
i: (((lat + 90.0) / self.cell_deg).floor() as i32).min(rows - 1),
j: ((lon + 180.0) / self.cell_deg).floor() as i32,
}
}
pub fn bounds(&self, c: CellId) -> (f64, f64, f64, f64) {
let s = c.i as f64 * self.cell_deg - 90.0;
let w = c.j as f64 * self.cell_deg - 180.0;
(
s,
w,
(s + self.cell_deg).min(90.0),
(w + self.cell_deg).min(180.0),
)
}
pub fn center(&self, c: CellId) -> (f64, f64) {
let (s, w, n, e) = self.bounds(c);
((s + n) / 2.0, (w + e) / 2.0)
}
}
pub fn haversine_m(lat1: f64, lon1: f64, lat2: f64, lon2: f64) -> f64 {
let (p1, p2) = (lat1.to_radians(), lat2.to_radians());
let dp = p2 - p1;
let dl = (lon2 - lon1).to_radians();
let a = (dp / 2.0).psin().ppowi(2) + p1.pcos() * p2.pcos() * (dl / 2.0).psin().ppowi(2);
2.0 * EARTH_RADIUS_M * a.sqrt().min(1.0).pasin()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cells_are_stable_and_contain_their_points() {
let g = Grid::new(0.5).unwrap();
let c = g.cell_of(59.76, 24.9);
let (s, w, n, e) = g.bounds(c);
assert!(s <= 59.76 && 59.76 < n && w <= 24.9 && 24.9 < e);
assert_eq!(g.cell_of(-90.0, -180.0), CellId { i: 0, j: 0 });
assert!(Grid::new(0.0).is_none() && Grid::new(f64::NAN).is_none());
}
#[test]
fn the_pole_and_the_antimeridian_stay_inside_the_grid() {
let g = Grid::new(0.5).unwrap();
assert_eq!(g.cell_of(90.0, 0.0).i, 359);
assert_eq!(g.cell_of(0.0, 180.0), g.cell_of(0.0, -180.0));
assert_eq!(g.cell_of(0.0, 180.0).j, 0);
assert_eq!(g.cell_of(0.0, 179.99).j, 719);
let g = Grid::new(0.07).unwrap();
let c = g.cell_of(90.0, 10.0);
let (s, _, n, _) = g.bounds(c);
assert!(s < 90.0 && n == 90.0, "{s} {n}");
assert_eq!(c.i, (180.0_f64 / 0.07).ceil() as i32 - 1);
}
#[test]
fn haversine_one_degree_of_latitude() {
let d = haversine_m(0.0, 0.0, 1.0, 0.0);
assert!((d - 111_195.0).abs() < 50.0, "{d}");
}
}