#![expect(
clippy::cast_possible_truncation,
reason = "shard index (usize->i32) — count always small"
)]
#![expect(
clippy::cast_possible_wrap,
reason = "usize -> i32 for shard count — always small"
)]
use std::collections::HashMap;
use crate::s2::cell_index::{CellIndex, CellIndexContentsIterator, CellIndexRangeIterator};
use crate::s2::{Cell, CellId, CellUnion, Region};
#[derive(Debug)]
pub struct RegionSharder {
index: CellIndex,
}
impl RegionSharder {
pub fn new(shards: &[CellUnion]) -> Self {
let mut index = CellIndex::new();
for (i, shard) in shards.iter().enumerate() {
index.add_cell_union(shard, i as i32);
}
index.build();
RegionSharder { index }
}
pub fn get_most_intersecting_shard(&self, region: &dyn Region, default_shard: i32) -> i32 {
let intersections = self.get_intersections_by_shard(region);
let mut best_shard = default_shard;
let mut best_sum: u64 = 0;
for (&shard, covering) in &intersections {
let sum: u64 = covering.iter().map(|id| id.lsb()).sum();
if sum > best_sum || (sum == best_sum && shard < best_shard) {
best_shard = shard;
best_sum = sum;
}
}
best_shard
}
pub fn get_intersecting_shards(&self, region: &dyn Region) -> Vec<i32> {
let intersections = self.get_intersections_by_shard(region);
intersections
.into_iter()
.filter(|(_, cells)| !cells.is_empty())
.map(|(shard, _)| shard)
.collect()
}
fn get_intersections_by_shard(&self, region: &dyn Region) -> HashMap<i32, Vec<CellId>> {
let region_cells = region.cell_union_bound();
let region_covering = CellUnion::from_cell_ids(region_cells);
let mut shards: HashMap<i32, Vec<CellId>> = HashMap::new();
let mut range_it = CellIndexRangeIterator::new_non_empty(&self.index);
let mut contents_it = CellIndexContentsIterator::new(&self.index);
range_it.begin();
for &cell_id in region_covering.cell_ids() {
let range_min = cell_id.range_min();
let range_max = cell_id.range_max();
range_it.seek(range_min);
if range_it.done() {
continue;
}
if range_it.start_id() > range_min
&& range_it.prev()
&& range_it.limit_id() <= range_min
{
range_it.next();
}
while !range_it.done() && range_it.start_id() <= range_max {
contents_it.start_union(&range_it);
while !contents_it.done() {
let label = contents_it.label();
let index_cell = contents_it.cell_id();
if index_cell.range_max() >= range_min && index_cell.range_min() <= range_max {
shards.entry(label).or_default().push(index_cell);
}
contents_it.next();
}
range_it.next();
}
}
let mut refined = HashMap::new();
for (shard, cells) in shards {
let mut good_cells = Vec::new();
for cell_id in cells {
if region.intersects_cell(&Cell::from_cell_id(cell_id)) {
good_cells.push(cell_id);
}
}
if !good_cells.is_empty() {
refined.insert(shard, good_cells);
}
}
refined
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::s2::cap::Cap;
use crate::s2::text_format;
#[test]
fn test_single_shard() {
let shards = vec![CellUnion::from_cell_ids(
(0..6).map(CellId::from_face).collect(),
)];
let sharder = RegionSharder::new(&shards);
let cap = Cap::from_center_angle(
text_format::parse_point("0:0"),
crate::s1::Angle::from_degrees(10.0),
);
let intersecting = sharder.get_intersecting_shards(&cap);
assert_eq!(intersecting, vec![0]);
}
#[test]
fn test_no_overlap() {
let shard0 = CellUnion::from_cell_ids(vec![CellId::from_face(0)]);
let shard1 = CellUnion::from_cell_ids(vec![CellId::from_face(5)]);
let sharder = RegionSharder::new(&[shard0, shard1]);
let cap = Cap::from_center_angle(
text_format::parse_point("0:90"),
crate::s1::Angle::from_degrees(1.0),
);
let result = sharder.get_most_intersecting_shard(&cap, -1);
assert!(result == -1 || result == 0 || result == 1);
}
#[test]
fn test_most_intersecting() {
let shards: Vec<CellUnion> = (0..6)
.map(|f| CellUnion::from_cell_ids(vec![CellId::from_face(f)]))
.collect();
let sharder = RegionSharder::new(&shards);
let cap = Cap::from_center_angle(
CellId::from_face(0).to_point(),
crate::s1::Angle::from_degrees(5.0),
);
let best = sharder.get_most_intersecting_shard(&cap, -1);
assert_eq!(best, 0, "cap at face 0 center should be in shard 0");
}
#[test]
fn test_multiple_intersecting_shards() {
let shards: Vec<CellUnion> = (0..6)
.map(|f| CellUnion::from_cell_ids(vec![CellId::from_face(f)]))
.collect();
let sharder = RegionSharder::new(&shards);
let cap = Cap::from_center_angle(
text_format::parse_point("0:0"),
crate::s1::Angle::from_degrees(60.0),
);
let intersecting = sharder.get_intersecting_shards(&cap);
assert!(
intersecting.len() > 1,
"large cap should intersect multiple face shards, got {intersecting:?}"
);
}
}