// S2 cell metrics — area and edge length at each level. // // Earth radius: 6,371,000 m. // Total surface area: 4 * PI * R^2 = ~5.1e14 m^2. // At level 0, there are 6 face cells. Each covers ~8.5e13 m^2. // At each subsequent level, the area is divided by 4. use std::f64::consts::PI; /// Mean Earth radius in meters (IUGG definition). pub const EARTH_RADIUS_M: f64 = 6_371_000.0; /// Earth surface area in m^2. pub const EARTH_AREA_M2: f64 = 4.0 * PI * EARTH_RADIUS_M * EARTH_RADIUS_M; /// Average area of an S2 cell at the given level, in m^2. /// There are 6 * 4^level cells at level L. #[inline] pub fn avg_area_m2(level: u8) -> f64 { EARTH_AREA_M2 / (6.0 * 4.0_f64.powi(level as i32)) } /// Average edge length of an S2 cell at the given level, in meters. /// Approximation: edge ~ sqrt(area). #[inline] pub fn avg_edge_m(level: u8) -> f64 { avg_area_m2(level).sqrt() } /// Maximum edge length of an S2 cell at the given level, in meters. /// The maximum edge is about 1.41x the average (diagonal factor for quad cells). #[inline] pub fn max_edge_m(level: u8) -> f64 { avg_edge_m(level) * 1.414 } /// Minimum edge length of an S2 cell at the given level, in meters. /// The minimum is about 0.71x the average. #[inline] pub fn min_edge_m(level: u8) -> f64 { avg_edge_m(level) * 0.707 } /// Average angular edge length in radians at the given level. #[inline] pub fn avg_edge_radians(level: u8) -> f64 { avg_edge_m(level) / EARTH_RADIUS_M } /// Number of cells at a given level. #[inline] pub fn num_cells(level: u8) -> u64 { 6u64 * (1u64 << (2 * level as u32)) } #[cfg(test)] mod tests { use super::*; #[test] fn level_0_area() { let area = avg_area_m2(0); // ~85,011 km^2 per face let area_km2 = area / 1e6; assert!( (area_km2 - 85_011_012.0).abs() / 85_011_012.0 < 0.01, "level 0 area: {area_km2} km^2" ); } #[test] fn area_halves_each_level() { for level in 0..29u8 { let a = avg_area_m2(level); let b = avg_area_m2(level + 1); let ratio = a / b; assert!( (ratio - 4.0).abs() < 1e-10, "area ratio at level {level}: {ratio}" ); } } #[test] fn level_30_area() { let area = avg_area_m2(30); // At level 30, ~0.48 cm^2 per cell let area_cm2 = area * 1e4; assert!(area_cm2 > 0.0 && area_cm2 < 10.0, "level 30 area: {area_cm2} cm^2"); } #[test] fn num_cells_level_0() { assert_eq!(num_cells(0), 6); } #[test] fn num_cells_level_1() { assert_eq!(num_cells(1), 24); } #[test] fn edge_length_reasonable() { // Level 12 should have edges around 2-3 km. let edge_km = avg_edge_m(12) / 1000.0; assert!( edge_km > 0.5 && edge_km < 10.0, "level 12 edge: {edge_km} km" ); } #[test] fn max_edge_greater_than_avg() { for level in 0..30u8 { assert!(max_edge_m(level) > avg_edge_m(level)); } } }