use crate::geometry::primitives::Point;
#[cfg(feature = "plotting")]
use polylabel_mini::polylabel;
#[derive(Debug, Clone, PartialEq)]
pub struct Polygon {
vertices: Vec<Point>,
}
impl Polygon {
pub fn new(vertices: Vec<Point>) -> Self {
Self { vertices }
}
pub fn vertices(&self) -> &[Point] {
&self.vertices
}
pub fn area(&self) -> f64 {
if self.vertices.len() < 3 {
return 0.0;
}
let mut area = 0.0;
let n = self.vertices.len();
for i in 0..n {
let j = (i + 1) % n;
area += self.vertices[i].x() * self.vertices[j].y();
area -= self.vertices[j].x() * self.vertices[i].y();
}
(area / 2.0).abs()
}
pub fn centroid(&self) -> Point {
if self.vertices.is_empty() {
return Point::new(0.0, 0.0);
}
let mut cx = 0.0;
let mut cy = 0.0;
let mut area = 0.0;
let n = self.vertices.len();
for i in 0..n {
let j = (i + 1) % n;
let cross = self.vertices[i].x() * self.vertices[j].y()
- self.vertices[j].x() * self.vertices[i].y();
area += cross;
cx += (self.vertices[i].x() + self.vertices[j].x()) * cross;
cy += (self.vertices[i].y() + self.vertices[j].y()) * cross;
}
area *= 0.5;
if area.abs() < 1e-10 {
cx = self.vertices.iter().map(|p| p.x()).sum::<f64>() / n as f64;
cy = self.vertices.iter().map(|p| p.y()).sum::<f64>() / n as f64;
} else {
cx /= 6.0 * area;
cy /= 6.0 * area;
}
Point::new(cx, cy)
}
#[cfg(feature = "plotting")]
pub fn pole_of_inaccessibility(&self, precision: f64) -> Point {
if self.vertices.len() < 3 {
return self.centroid();
}
let signed_area_x2: f64 = (0..self.vertices.len())
.map(|i| {
let j = (i + 1) % self.vertices.len();
self.vertices[i].x() * self.vertices[j].y()
- self.vertices[j].x() * self.vertices[i].y()
})
.sum();
let oriented: Vec<Point> = if signed_area_x2 < 0.0 {
self.vertices.iter().rev().copied().collect()
} else {
self.vertices.clone()
};
let mut points: Vec<polylabel_mini::Point> = oriented
.iter()
.map(|p| polylabel_mini::Point { x: p.x(), y: p.y() })
.collect();
if let (Some(first), Some(last)) = (oriented.first(), oriented.last()) {
if (first.x() - last.x()).abs() > 1e-10 || (first.y() - last.y()).abs() > 1e-10 {
points.push(polylabel_mini::Point {
x: first.x(),
y: first.y(),
});
}
}
let exterior = polylabel_mini::LineString { points };
let poly = polylabel_mini::Polygon {
exterior,
interiors: vec![], };
let pole = polylabel(&poly, precision);
Point::new(pole.x, pole.y)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::geometry::shapes::{Circle, Ellipse};
use crate::geometry::traits::Polygonize;
#[test]
fn test_polygon_area_square() {
let polygon = Polygon::new(vec![
Point::new(0.0, 0.0),
Point::new(1.0, 0.0),
Point::new(1.0, 1.0),
Point::new(0.0, 1.0),
]);
assert!((polygon.area() - 1.0).abs() < 1e-10);
}
#[test]
fn test_from_circle() {
let circle = Circle::new(Point::new(0.0, 0.0), 5.0);
let polygon = circle.polygonize(32);
assert_eq!(polygon.vertices().len(), 32);
for vertex in polygon.vertices() {
let dist = ((vertex.x() - 0.0).powi(2) + (vertex.y() - 0.0).powi(2)).sqrt();
assert!((dist - 5.0).abs() < 1e-10);
}
}
#[test]
fn test_from_ellipse() {
let ellipse = Ellipse::new(Point::new(1.0, 2.0), 4.0, 2.0, 0.0);
let polygon = ellipse.polygonize(64);
assert_eq!(polygon.vertices().len(), 64);
}
#[test]
#[cfg(feature = "plotting")]
fn test_pole_of_inaccessibility_square() {
let polygon = Polygon::new(vec![
Point::new(0.0, 0.0),
Point::new(10.0, 0.0),
Point::new(10.0, 10.0),
Point::new(0.0, 10.0),
]);
let pole = polygon.pole_of_inaccessibility(0.1);
assert!((pole.x() - 5.0).abs() < 1.0);
assert!((pole.y() - 5.0).abs() < 1.0);
}
#[test]
#[cfg(feature = "plotting")]
fn test_pole_of_inaccessibility_l_shape() {
let polygon = Polygon::new(vec![
Point::new(0.0, 0.0),
Point::new(4.0, 0.0),
Point::new(4.0, 1.0),
Point::new(1.0, 1.0),
Point::new(1.0, 4.0),
Point::new(0.0, 4.0),
]);
let pole = polygon.pole_of_inaccessibility(0.1);
let centroid = polygon.centroid();
assert!(pole.x() < 1.5);
assert!(pole.y() < 1.5);
assert!(centroid.x() > pole.x());
assert!(centroid.y() > pole.y());
}
#[test]
#[cfg(feature = "plotting")]
fn test_pole_of_inaccessibility_circle() {
let circle = Circle::new(Point::new(3.0, 4.0), 5.0);
let polygon = circle.polygonize(64);
let pole = polygon.pole_of_inaccessibility(0.1);
assert!((pole.x() - 3.0).abs() < 0.5);
assert!((pole.y() - 4.0).abs() < 0.5);
}
#[test]
#[cfg(feature = "plotting")]
fn test_pole_degenerate_polygon() {
let polygon = Polygon::new(vec![
Point::new(0.0, 0.0),
Point::new(1.0, 0.0),
Point::new(0.5, 1.0),
]);
let pole = polygon.pole_of_inaccessibility(0.1);
assert!(pole.x() >= 0.0 && pole.x() <= 1.0);
assert!(pole.y() >= 0.0 && pole.y() <= 1.0);
}
}