use crate::Polygon;
pub fn polygon_centroid(p: &Polygon) -> Option<(i32, i32)> {
centroid_ring(&p.exterior)
}
pub fn linestring_centroid(line: &[(i32, i32)]) -> Option<(i32, i32)> {
if line.len() < 2 {
return line.first().copied();
}
let mut total = 0.0_f64;
let mut cx = 0.0_f64;
let mut cy = 0.0_f64;
for w in line.windows(2) {
let (x0, y0) = (w[0].0 as f64, w[0].1 as f64);
let (x1, y1) = (w[1].0 as f64, w[1].1 as f64);
let dx = x1 - x0;
let dy = y1 - y0;
let len = (dx * dx + dy * dy).sqrt();
if len == 0.0 {
continue;
}
total += len;
cx += (x0 + x1) * 0.5 * len;
cy += (y0 + y1) * 0.5 * len;
}
if total == 0.0 {
return None;
}
Some(((cx / total).round() as i32, (cy / total).round() as i32))
}
fn centroid_ring(ring: &[(i32, i32)]) -> Option<(i32, i32)> {
if ring.len() < 3 {
return None;
}
let mut a2 = 0.0_f64;
let mut cx = 0.0_f64;
let mut cy = 0.0_f64;
let n = ring.len();
for i in 0..n {
let (x0, y0) = (ring[i].0 as f64, ring[i].1 as f64);
let (x1, y1) = (ring[(i + 1) % n].0 as f64, ring[(i + 1) % n].1 as f64);
let cross = x0 * y1 - x1 * y0;
a2 += cross;
cx += (x0 + x1) * cross;
cy += (y0 + y1) * cross;
}
if a2 == 0.0 {
let (sx, sy) = ring.iter().fold((0.0_f64, 0.0_f64), |(a, b), p| {
(a + p.0 as f64, b + p.1 as f64)
});
let n = ring.len() as f64;
return Some(((sx / n).round() as i32, (sy / n).round() as i32));
}
let factor = 1.0 / (3.0 * a2);
Some(((cx * factor).round() as i32, (cy * factor).round() as i32))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn square_centroid_is_center() {
let p = Polygon {
exterior: vec![(0, 0), (10, 0), (10, 10), (0, 10), (0, 0)],
holes: vec![],
};
assert_eq!(polygon_centroid(&p), Some((5, 5)));
}
#[test]
fn linestring_midpoint() {
assert_eq!(linestring_centroid(&[(0, 0), (10, 0)]), Some((5, 0)));
}
#[test]
fn degenerate_polygon_returns_none() {
let p = Polygon {
exterior: vec![(0, 0), (1, 0)],
holes: vec![],
};
assert_eq!(polygon_centroid(&p), None);
}
}