use h3o::{CellIndex, LatLng, Resolution};
fn clamp_resolution(res: u8) -> Resolution {
Resolution::try_from(res.min(15)).unwrap_or(Resolution::Zero)
}
pub fn lat_lng_to_cell(lat: f64, lon: f64, res: u8) -> u64 {
match LatLng::new(lat, lon) {
Ok(ll) => u64::from(ll.to_cell(clamp_resolution(res))),
Err(_) => 0,
}
}
pub fn cell_to_lat_lng(cell: u64) -> (f64, f64) {
match CellIndex::try_from(cell) {
Ok(c) => {
let ll = LatLng::from(c);
(ll.lat(), ll.lng())
}
Err(_) => (0.0, 0.0),
}
}
pub fn grid_disk(cell: u64, k: u32) -> Vec<u64> {
match CellIndex::try_from(cell) {
Ok(c) => c
.grid_disk::<Vec<CellIndex>>(k)
.into_iter()
.map(u64::from)
.collect(),
Err(_) => Vec::new(),
}
}
pub fn edge_length_km(res: u8) -> f64 {
clamp_resolution(res).edge_length_km()
}
pub fn cell_to_parent(cell: u64, res: u8) -> u64 {
match CellIndex::try_from(cell) {
Ok(c) => c.parent(clamp_resolution(res)).map(u64::from).unwrap_or(0),
Err(_) => 0,
}
}
#[cfg(test)]
mod tests {
use super::*;
const PARIS_LAT: f64 = 48.8566;
const PARIS_LON: f64 = 2.3522;
#[test]
fn round_trip_within_cell_tolerance() {
for (lat, lon, res) in [
(PARIS_LAT, PARIS_LON, 9u8),
(-23.550_520, -46.633_309, 10), (0.0, 0.0, 7), (51.5074, -0.1278, 12), ] {
let cell = lat_lng_to_cell(lat, lon, res);
assert_ne!(cell, 0, "cell should encode for ({lat},{lon})@{res}");
let (rlat, rlon) = cell_to_lat_lng(cell);
assert!(
(rlat - lat).abs() < 0.05 && (rlon - lon).abs() < 0.05,
"round-trip drift too large: ({lat},{lon}) -> ({rlat},{rlon})"
);
}
}
#[test]
fn grid_disk_k1_returns_self_plus_six_neighbors() {
let cell = lat_lng_to_cell(PARIS_LAT, PARIS_LON, 9);
let ring = grid_disk(cell, 1);
assert_eq!(ring.len(), 7, "k=1 disk over a hexagon is self + 6");
assert!(
ring.contains(&cell),
"the disk must include the center cell"
);
}
#[test]
fn grid_disk_k0_is_just_self() {
let cell = lat_lng_to_cell(PARIS_LAT, PARIS_LON, 9);
let ring = grid_disk(cell, 0);
assert_eq!(ring, vec![cell]);
}
#[test]
fn cell_to_parent_truncates_resolution() {
let fine = lat_lng_to_cell(PARIS_LAT, PARIS_LON, 9);
let coarse = cell_to_parent(fine, 5);
assert_ne!(coarse, 0);
assert_eq!(coarse, lat_lng_to_cell(PARIS_LAT, PARIS_LON, 5));
assert_eq!(cell_to_parent(fine, 12), 0);
}
#[test]
fn edge_length_km_is_positive_and_shrinks_with_resolution() {
let coarse = edge_length_km(5);
let fine = edge_length_km(12);
assert!(coarse > 0.0 && fine > 0.0);
assert!(coarse > fine, "edge length must shrink as resolution grows");
assert!(edge_length_km(200) > 0.0);
}
#[test]
fn invalid_inputs_are_sentinelled() {
assert_eq!(lat_lng_to_cell(f64::NAN, 0.0, 9), 0);
assert_eq!(lat_lng_to_cell(0.0, f64::INFINITY, 9), 0);
assert_eq!(cell_to_lat_lng(0), (0.0, 0.0));
assert!(grid_disk(0, 1).is_empty());
assert_eq!(cell_to_parent(0, 5), 0);
}
}