Skip to main content

geometry_model/
polygon.rs

1//! Default `model::Polygon<P, CW, CL>` — an exterior [`Ring`] plus zero
2//! or more interior rings (holes).
3//!
4//! Mirrors `boost::geometry::model::polygon<Point, ClockWise, Closed,
5//! PointList, RingList, PointAlloc, RingAlloc>` declared in
6//! `boost/geometry/geometries/polygon.hpp:66-100`, together with the
7//! trait specialisations the same header provides under
8//! `namespace traits` (`tag`, `ring_const_type`, `ring_mutable_type`,
9//! `exterior_ring`, `interior_const_type`, `interior_mutable_type`,
10//! `interior_rings`, `point_order`, `closure`) at
11//! `boost/geometry/geometries/polygon.hpp:150-340`. The Rust port
12//! collapses those specialisations into the two trait impls below
13//! ([`Geometry`], [`PolygonTrait`]), keyed off the generic point
14//! parameter `P` and the two const-generic booleans inherited from
15//! [`Ring`].
16//!
17//! Boost encodes closure and traversal direction at the type level
18//! through the same two `bool` template parameters that flow down to
19//! the exterior and interior `model::ring`s
20//! (`boost/geometry/geometries/polygon.hpp:84`); the Rust port mirrors
21//! that by sharing the same two const generics between the polygon and
22//! every ring it contains, so a polygon with custom orientation is a
23//! distinct type, exactly like
24//! `model::polygon<P, false, false>` is in C++. Boost defaults
25//! `ClockWise = true, Closed = true`
26//! (`boost/geometry/geometries/polygon.hpp:69-70`); the Rust defaults
27//! match.
28
29use alloc::vec::Vec;
30
31use geometry_tag::PolygonTag;
32use geometry_trait::{Geometry, Point as PointTrait, Polygon as PolygonTrait};
33
34use crate::Ring;
35
36/// Default Polygon — an outer [`Ring`] and a `Vec` of inner rings,
37/// all sharing the same point type and the same closure / traversal
38/// const generics.
39///
40/// Mirrors `boost::geometry::model::polygon<Point, ClockWise, Closed>`
41/// from `boost/geometry/geometries/polygon.hpp:66-100`. The defaults
42/// (`CLOCKWISE = true`, `CLOSED = true`) match Boost's defaults
43/// (`boost/geometry/geometries/polygon.hpp:69-70`).
44#[derive(Debug, Clone, PartialEq)]
45#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
46pub struct Polygon<P: PointTrait, const CLOCKWISE: bool = true, const CLOSED: bool = true> {
47    /// The exterior ring (outer boundary). Mirrors `m_outer` in
48    /// `boost/geometry/geometries/polygon.hpp:139`.
49    pub outer: Ring<P, CLOCKWISE, CLOSED>,
50    /// The interior rings (holes), in declared order. Mirrors
51    /// `m_inners` in `boost/geometry/geometries/polygon.hpp:140`.
52    pub inners: Vec<Ring<P, CLOCKWISE, CLOSED>>,
53}
54
55impl<P: PointTrait, const CW: bool, const CL: bool> Polygon<P, CW, CL> {
56    /// Construct a polygon from an outer ring with no holes.
57    ///
58    /// Rust analogue of constructing a `boost::geometry::model::polygon`
59    /// then assigning to `.outer()` and leaving `.inners()` empty
60    /// (`boost/geometry/geometries/polygon.hpp:87-91, 96-101`).
61    #[must_use]
62    pub const fn new(outer: Ring<P, CW, CL>) -> Self {
63        Self {
64            outer,
65            inners: Vec::new(),
66        }
67    }
68
69    /// Construct a polygon from an outer ring plus a `Vec` of inner
70    /// rings.
71    ///
72    /// Rust analogue of constructing a `boost::geometry::model::polygon`
73    /// and then assigning to both `.outer()` and `.inners()`
74    /// (`boost/geometry/geometries/polygon.hpp:87-91`).
75    #[must_use]
76    pub const fn with_inners(outer: Ring<P, CW, CL>, inners: Vec<Ring<P, CW, CL>>) -> Self {
77        Self { outer, inners }
78    }
79}
80
81impl<P: PointTrait, const CW: bool, const CL: bool> Default for Polygon<P, CW, CL> {
82    #[inline]
83    fn default() -> Self {
84        Self {
85            outer: Ring::new(),
86            inners: Vec::new(),
87        }
88    }
89}
90
91/// Tag this concrete type as a Polygon. Mirrors the
92/// `traits::tag<model::polygon<...>>` specialisation at
93/// `boost/geometry/geometries/polygon.hpp:158-172`.
94impl<P: PointTrait, const CW: bool, const CL: bool> Geometry for Polygon<P, CW, CL> {
95    type Kind = PolygonTag;
96    type Point = P;
97}
98
99/// Wire the Polygon concept up. The `exterior()` accessor hands back
100/// `&self.outer` and `interiors()` hands back `self.inners.iter()` —
101/// `slice::Iter` is already `ExactSizeIterator`. Mirrors the
102/// `traits::exterior_ring` and `traits::interior_rings` specialisations
103/// at `boost/geometry/geometries/polygon.hpp:200-260` that dispatch on
104/// the same generic parameters.
105impl<P: PointTrait, const CW: bool, const CL: bool> PolygonTrait for Polygon<P, CW, CL> {
106    type Ring = Ring<P, CW, CL>;
107
108    fn exterior(&self) -> &Self::Ring {
109        &self.outer
110    }
111
112    fn interiors(&self) -> impl ExactSizeIterator<Item = &Self::Ring> {
113        self.inners.iter()
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    //! Construction + tag-resolution + `check_polygon` witness for
120    //! [`Polygon`]. Mirrors `boost/geometry/test/core/tag.cpp` (the
121    //! polygon-tag arm) and the exterior/interior accessor tests in
122    //! `boost/geometry/test/geometries/polygon.cpp`.
123
124    use super::Polygon;
125    use crate::point::Point2D;
126    use crate::ring::Ring;
127    use alloc::vec;
128    use geometry_cs::Cartesian;
129    use geometry_tag::PolygonTag;
130    use geometry_trait::{Geometry, Polygon as PolygonTrait, check_polygon};
131
132    type P = Point2D<f64, Cartesian>;
133
134    fn unit_square_outer() -> Ring<P> {
135        Ring::from_vec(vec![
136            Point2D::new(0.0, 0.0),
137            Point2D::new(10.0, 0.0),
138            Point2D::new(10.0, 10.0),
139            Point2D::new(0.0, 10.0),
140            Point2D::new(0.0, 0.0),
141        ])
142    }
143
144    fn hole(x: f64, y: f64) -> Ring<P> {
145        Ring::from_vec(vec![
146            Point2D::new(x, y),
147            Point2D::new(x + 1.0, y),
148            Point2D::new(x + 1.0, y + 1.0),
149            Point2D::new(x, y + 1.0),
150            Point2D::new(x, y),
151        ])
152    }
153
154    #[test]
155    fn polygon_new_has_no_holes() {
156        let poly: Polygon<P> = Polygon::new(unit_square_outer());
157        assert_eq!(poly.exterior().0.len(), 5);
158        assert_eq!(poly.interiors().count(), 0);
159    }
160
161    #[test]
162    fn polygon_with_two_holes() {
163        let mut poly: Polygon<P> = Polygon::new(unit_square_outer());
164        poly.inners.push(hole(1.0, 1.0));
165        poly.inners.push(hole(5.0, 5.0));
166        assert_eq!(poly.interiors().count(), 2);
167    }
168
169    #[test]
170    fn polygon_with_inners_constructor() {
171        let poly: Polygon<P> =
172            Polygon::with_inners(unit_square_outer(), vec![hole(1.0, 1.0), hole(5.0, 5.0)]);
173        assert_eq!(poly.interiors().count(), 2);
174    }
175
176    #[test]
177    fn polygon_kind_is_polygon_tag() {
178        fn k<T: Geometry<Kind = PolygonTag>>() {}
179        k::<Polygon<P>>();
180        k::<Polygon<P, false, false>>();
181    }
182
183    #[test]
184    fn polygon_satisfies_concept() {
185        check_polygon::<Polygon<P>>();
186        check_polygon::<Polygon<P, false, false>>();
187    }
188}