use crate::geo::LonLat;
use crate::projection::to_pixels;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct Cluster {
pub center: LonLat,
pub count: usize,
pub members: Vec<usize>,
}
pub fn cluster(markers: &[LonLat], zoom: u8, cell_px: f64) -> Vec<Cluster> {
let mut cells: HashMap<(i64, i64), Vec<usize>> = HashMap::new();
for (i, m) in markers.iter().enumerate() {
let (px, py) = to_pixels(m.lon, m.lat, zoom);
let key = ((px / cell_px).floor() as i64, (py / cell_px).floor() as i64);
cells.entry(key).or_default().push(i);
}
cells
.into_values()
.map(|members| {
let count = members.len();
let (slon, slat) = members.iter().fold((0.0, 0.0), |(a, b), &i| {
(a + markers[i].lon, b + markers[i].lat)
});
Cluster {
center: LonLat::new(slon / count as f64, slat / count as f64),
count,
members,
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn distant_markers_stay_separate() {
let ms = vec![LonLat::new(105.0, 21.0), LonLat::new(106.0, 22.0)];
let c = cluster(&ms, 10, 64.0);
assert_eq!(c.len(), 2);
assert!(c.iter().all(|x| x.count == 1));
}
#[test]
fn nearby_markers_merge() {
let ms = vec![
LonLat::new(105.8500, 21.0200),
LonLat::new(105.8501, 21.0201),
LonLat::new(105.8502, 21.0199),
];
let c = cluster(&ms, 14, 64.0);
assert_eq!(c.len(), 1);
assert_eq!(c[0].count, 3);
assert_eq!(c[0].members.len(), 3);
}
#[test]
fn members_cover_all_inputs() {
let ms: Vec<LonLat> = (0..50)
.map(|i| LonLat::new(105.0 + i as f64 * 0.01, 21.0))
.collect();
let c = cluster(&ms, 10, 64.0);
let total: usize = c.iter().map(|x| x.count).sum();
assert_eq!(total, 50);
}
}