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}