use crate::core::cell::cell_to_spherical;
use crate::core::cell_info::cell_area;
use crate::core::constants::AUTHALIC_RADIUS_EARTH;
use crate::core::origin::haversine;
use crate::core::serialization::{
cell_to_children, cell_to_parent, get_resolution, FIRST_HILBERT_RESOLUTION,
};
use crate::traversal::global_neighbors::get_global_cell_neighbors;
use std::collections::HashSet;
const CELL_RADIUS_SAFETY_FACTOR: f64 = 2.0;
const MIN_CELLS_FOR_SUBDIVISION: f64 = 20.0;
lazy_static::lazy_static! {
static ref CELL_RADIUS: Vec<f64> = {
let base = CELL_RADIUS_SAFETY_FACTOR * AUTHALIC_RADIUS_EARTH / 15_f64.sqrt();
let mut radii = Vec::with_capacity(31);
radii.push(CELL_RADIUS_SAFETY_FACTOR * AUTHALIC_RADIUS_EARTH / 3_f64.sqrt());
for r in 1..31 {
radii.push(base / (1_u64 << (r - 1)) as f64);
}
radii
};
}
pub fn meters_to_h(meters: f64) -> f64 {
let s = (meters / (2.0 * AUTHALIC_RADIUS_EARTH)).sin();
s * s
}
pub fn estimate_cell_radius(resolution: i32) -> f64 {
CELL_RADIUS[resolution as usize]
}
pub fn pick_coarse_resolution(radius: f64, target_res: i32) -> i32 {
let cap_area_m2 = 2.0
* std::f64::consts::PI
* AUTHALIC_RADIUS_EARTH
* AUTHALIC_RADIUS_EARTH
* (1.0 - (radius / AUTHALIC_RADIUS_EARTH).cos());
for res in FIRST_HILBERT_RESOLUTION..=target_res {
let c_area = cell_area(res);
if cap_area_m2 / c_area >= MIN_CELLS_FOR_SUBDIVISION {
return res;
}
}
target_res }
pub fn spherical_cap(cell_id: u64, radius: f64) -> Result<Vec<u64>, String> {
let target_res = get_resolution(cell_id);
let coarse_res = pick_coarse_resolution(radius, target_res);
let center = cell_to_spherical(cell_id)?;
let h_radius = meters_to_h(radius);
let start_cell = if coarse_res < target_res {
cell_to_parent(cell_id, Some(coarse_res))?
} else {
cell_id
};
let coarse_cell_radius = estimate_cell_radius(coarse_res);
let h_expanded = meters_to_h(radius + coarse_cell_radius);
let mut coarse_visited: HashSet<u64> = HashSet::new();
coarse_visited.insert(start_cell);
let mut coarse_frontier: HashSet<u64> = HashSet::new();
coarse_frontier.insert(start_cell);
while !coarse_frontier.is_empty() {
let mut next_frontier: HashSet<u64> = HashSet::new();
for &cid in &coarse_frontier {
for neighbor in get_global_cell_neighbors(cid, false) {
if coarse_visited.contains(&neighbor) {
continue;
}
coarse_visited.insert(neighbor);
if haversine(center, cell_to_spherical(neighbor)?) <= h_expanded {
next_frontier.insert(neighbor);
}
}
}
coarse_frontier = next_frontier;
}
let mut result: Vec<u64> = Vec::new();
let mut boundary: Vec<u64> = coarse_visited.into_iter().collect();
for res in coarse_res..target_res {
let cell_radius_val = estimate_cell_radius(res);
let h_inner = if radius > cell_radius_val {
meters_to_h(radius - cell_radius_val)
} else {
-1.0
};
let h_outer = meters_to_h(radius + cell_radius_val);
let mut next_boundary: Vec<u64> = Vec::new();
for &cell in &boundary {
let h = haversine(center, cell_to_spherical(cell)?);
if h <= h_inner {
result.push(cell);
} else if h > h_outer {
} else {
for child in cell_to_children(cell, Some(res + 1))? {
next_boundary.push(child);
}
}
}
boundary = next_boundary;
}
for &cell in &boundary {
if haversine(center, cell_to_spherical(cell)?) <= h_radius {
result.push(cell);
}
}
result.sort();
Ok(result)
}