use std::collections::{HashMap, HashSet};
use crate::coordinate_systems::{Cartesian, LonLat};
use crate::core::cell::{cell_to_spherical, lonlat_to_cell, spherical_to_cell};
use crate::core::compact::compact;
use crate::core::coordinate_transforms::{from_lon_lat, to_cartesian, to_spherical};
use crate::core::serialization::{
cell_to_children, cell_to_parent, FIRST_HILBERT_RESOLUTION, MAX_RESOLUTION,
};
use crate::geometry::spherical_polygon::{
point_in_spherical_polygon, ring_segment_normals, ring_winding_sign,
};
use crate::traversal::cap::estimate_cell_radius;
use crate::traversal::lattice_flood_fill::{triple_space_flood_fill, FloodInput};
use crate::traversal::lattice_neighbors::get_lattice_neighbors;
use crate::utils::great_circle::sample_great_circle_arc;
type SegmentMap = HashMap<u64, Vec<usize>>;
struct DenseSampleResult {
boundary_cells: Vec<u64>,
boundary_set: HashSet<u64>,
segment_map: SegmentMap,
}
fn dense_sample_boundary(
ring: &[LonLat],
ring_vecs: &[Cartesian],
resolution: i32,
) -> Result<DenseSampleResult, String> {
let mut boundary_cells: Vec<u64> = Vec::new();
let mut boundary_set: HashSet<u64> = HashSet::new();
let mut segment_map: SegmentMap = HashMap::new();
let cell_radius = estimate_cell_radius(resolution);
let sample_interval = cell_radius * 0.4;
let record_cell = |cell: u64,
seg_idx: usize,
boundary_cells: &mut Vec<u64>,
boundary_set: &mut HashSet<u64>,
segment_map: &mut SegmentMap| {
if boundary_set.insert(cell) {
boundary_cells.push(cell);
}
let entry = segment_map.entry(cell).or_default();
if entry.last() != Some(&seg_idx) {
entry.push(seg_idx);
}
};
let n = ring.len();
let mut vertex_cells: Vec<u64> = Vec::with_capacity(n);
for v in ring {
vertex_cells.push(lonlat_to_cell(*v, resolution)?);
}
for i in 0..n {
let next_i = (i + 1) % n;
record_cell(
vertex_cells[i],
i,
&mut boundary_cells,
&mut boundary_set,
&mut segment_map,
);
let samples = sample_great_circle_arc(ring_vecs[i], ring_vecs[next_i], sample_interval);
for s in samples {
let cell = spherical_to_cell(to_spherical(s), resolution)?;
record_cell(
cell,
i,
&mut boundary_cells,
&mut boundary_set,
&mut segment_map,
);
}
record_cell(
vertex_cells[next_i],
i,
&mut boundary_cells,
&mut boundary_set,
&mut segment_map,
);
}
Ok(DenseSampleResult {
boundary_cells,
boundary_set,
segment_map,
})
}
fn filter_boundary_cells(
boundary_cells: &[u64],
segment_map: &SegmentMap,
seg_normals: &[Cartesian],
ring_vecs: &[Cartesian],
interior_sign: i32,
) -> Result<Vec<u64>, String> {
let mut out: Vec<u64> = Vec::new();
for &cell in boundary_cells {
let cv = to_cartesian(cell_to_spherical(cell)?);
let segments = match segment_map.get(&cell) {
Some(s) => s,
None => {
if point_in_spherical_polygon(cv, ring_vecs) {
out.push(cell);
}
continue;
}
};
let mut all_inside = true;
let mut any_inside = false;
let mut ambiguous = false;
for &seg_idx in segments {
let n = seg_normals[seg_idx];
let dot = n.x() * cv.x() + n.y() * cv.y() + n.z() * cv.z();
if dot.abs() < 1e-14 {
ambiguous = true;
break;
}
if dot * (interior_sign as f64) > 0.0 {
any_inside = true;
} else {
all_inside = false;
}
}
if ambiguous || (any_inside && !all_inside) {
if point_in_spherical_polygon(cv, ring_vecs) {
out.push(cell);
}
} else if all_inside {
out.push(cell);
}
}
Ok(out)
}
fn expand_shell(boundary_cells: &[u64], boundary_set: &HashSet<u64>) -> Vec<u64> {
let mut shell_cells: Vec<u64> = Vec::new();
let mut shell_set: HashSet<u64> = HashSet::new();
for &cell in boundary_cells {
for neighbor in get_lattice_neighbors(cell, true) {
if boundary_set.contains(&neighbor) {
continue;
}
if shell_set.insert(neighbor) {
shell_cells.push(neighbor);
}
}
}
shell_cells
}
fn flood_interior(
interior_seeds: &[u64],
visited: &mut HashSet<u64>,
boundary_size: usize,
resolution: i32,
) -> Result<Vec<u64>, String> {
for &cell in interior_seeds {
visited.insert(cell);
}
let max_interior =
(boundary_size as f64) * (boundary_size as f64) / (4.0 * std::f64::consts::PI);
let use_coarse_phase = resolution > FIRST_HILBERT_RESOLUTION
&& resolution < MAX_RESOLUTION
&& max_interior > 1000.0;
if !use_coarse_phase {
let result = triple_space_flood_fill(
FloodInput::Firewall(visited),
interior_seeds,
resolution,
None,
);
let mut out: Vec<u64> =
Vec::with_capacity(interior_seeds.len() + result.interior_cells.len());
out.extend_from_slice(interior_seeds);
out.extend(result.interior_cells);
return Ok(out);
}
let parent_res = resolution - 1;
let mut coarse_firewall: HashSet<u64> = HashSet::new();
for &cell in visited.iter() {
coarse_firewall.insert(cell_to_parent(cell, Some(parent_res))?);
}
let phase1 = triple_space_flood_fill(
FloodInput::Firewall(visited),
interior_seeds,
resolution,
Some(3),
);
let mut coarse_interior_set: Option<HashSet<u64>> = None;
let mut phase3_delta: Vec<u64> = Vec::new();
let mut coarse_interior_cells: Vec<u64> = Vec::new();
if !phase1.frontier_cell_ids.is_empty() {
let mut coarse_seeds: HashSet<u64> = HashSet::new();
for &cell in &phase1.frontier_cell_ids {
let parent = cell_to_parent(cell, Some(parent_res))?;
if !coarse_firewall.contains(&parent) {
coarse_seeds.insert(parent);
}
}
if !coarse_seeds.is_empty() {
let mut coarse_visited: HashSet<u64> = coarse_firewall.clone();
for &seed in &coarse_seeds {
coarse_visited.insert(seed);
}
let coarse_seed_vec: Vec<u64> = coarse_seeds.iter().copied().collect();
let coarse_result = triple_space_flood_fill(
FloodInput::Firewall(&mut coarse_visited),
&coarse_seed_vec,
parent_res,
None,
);
let mut coarse_interior: Vec<u64> =
Vec::with_capacity(coarse_seed_vec.len() + coarse_result.interior_cells.len());
coarse_interior.extend(coarse_seed_vec);
coarse_interior.extend(coarse_result.interior_cells);
let mut coarse_set: HashSet<u64> = HashSet::with_capacity(coarse_interior.len());
for &c in &coarse_interior {
coarse_set.insert(c);
}
coarse_interior_cells.extend(coarse_interior.iter().copied());
coarse_interior_set = Some(coarse_set);
for coarse_cell in coarse_interior {
for child in cell_to_children(coarse_cell, Some(resolution))? {
if !visited.contains(&child) {
visited.insert(child);
phase3_delta.push(child);
}
}
}
}
}
let mut interior_cells: Vec<u64> = Vec::new();
if coarse_interior_set.is_none() {
interior_cells.extend_from_slice(interior_seeds);
interior_cells.extend(phase1.interior_cells.iter().copied());
} else {
let coarse_set = coarse_interior_set.as_ref().unwrap();
for &cell in interior_seeds {
let parent = cell_to_parent(cell, Some(parent_res))?;
if !coarse_set.contains(&parent) {
interior_cells.push(cell);
}
}
for &cell in &phase1.interior_cells {
let parent = cell_to_parent(cell, Some(parent_res))?;
if !coarse_set.contains(&parent) {
interior_cells.push(cell);
}
}
interior_cells.extend(coarse_interior_cells);
}
let phase3 = triple_space_flood_fill(
FloodInput::Reuse {
state: phase1.state,
delta: phase3_delta,
},
&phase1.frontier_cell_ids,
resolution,
None,
);
interior_cells.extend(phase3.interior_cells);
Ok(interior_cells)
}
pub fn polygon_to_cells(ring: &[LonLat], resolution: i32) -> Result<Vec<u64>, String> {
if ring.len() < 3 {
return Ok(Vec::new());
}
let mut ring_vecs: Vec<Cartesian> = Vec::with_capacity(ring.len());
for v in ring {
ring_vecs.push(to_cartesian(from_lon_lat(*v)));
}
let DenseSampleResult {
boundary_cells,
boundary_set,
segment_map,
} = dense_sample_boundary(ring, &ring_vecs, resolution)?;
let interior_sign = ring_winding_sign(&ring_vecs);
let seg_normals = ring_segment_normals(&ring_vecs);
let filtered_boundary = filter_boundary_cells(
&boundary_cells,
&segment_map,
&seg_normals,
&ring_vecs,
interior_sign,
)?;
let shell_cells = expand_shell(&boundary_cells, &boundary_set);
if shell_cells.is_empty() {
return compact(&filtered_boundary);
}
let mut interior_seeds: Vec<u64> = Vec::new();
let mut visited: HashSet<u64> = boundary_set.clone();
for cell in shell_cells {
let cv = to_cartesian(cell_to_spherical(cell)?);
if point_in_spherical_polygon(cv, &ring_vecs) {
interior_seeds.push(cell);
} else {
visited.insert(cell); }
}
if interior_seeds.is_empty() {
return compact(&filtered_boundary);
}
let interior_cells = flood_interior(
&interior_seeds,
&mut visited,
boundary_set.len(),
resolution,
)?;
let mut combined: Vec<u64> = Vec::with_capacity(filtered_boundary.len() + interior_cells.len());
combined.extend(filtered_boundary);
combined.extend(interior_cells);
compact(&combined)
}