geometry_trait/polygon.rs
1//! The [`Polygon`] concept: an exterior ring plus zero or more interior
2//! rings (holes), all sharing the same point type.
3//!
4//! Mirrors `doc/concept/polygon.qbk` and the model in
5//! `boost/geometry/geometries/polygon.hpp`. The three polygon-only
6//! metafunctions
7//! `boost::geometry::traits::ring_const_type<P>` /
8//! `boost::geometry::traits::ring_mutable_type<P>`
9//! (`boost/geometry/core/ring_type.hpp`),
10//! `boost::geometry::traits::exterior_ring<P>`
11//! (`boost/geometry/core/exterior_ring.hpp`), and
12//! `boost::geometry::traits::interior_rings<P>` /
13//! `boost::geometry::traits::interior_const_type<P>` /
14//! `boost::geometry::traits::interior_mutable_type<P>`
15//! (`boost/geometry/core/interior_rings.hpp`)
16//! fold into a single associated type plus two accessor methods here.
17
18use crate::geometry::Geometry;
19use crate::ring::Ring;
20use geometry_tag::PolygonTag;
21
22/// A polygon — an exterior ring plus zero or more interior rings.
23///
24/// Mirrors the Polygon concept (`doc/concept/polygon.qbk`); the
25/// canonical model is `boost::geometry::model::polygon` in
26/// `boost/geometry/geometries/polygon.hpp`. All rings share the
27/// polygon's point type, matching the constraint that
28/// `traits::ring_const_type<P>::type` and the polygon's
29/// `traits::point_type<P>::type` are mutually consistent
30/// (`boost/geometry/core/{ring_type,point_type}.hpp`).
31///
32/// `Ring` is an associated type — locked in proposal §10.3 — so the
33/// inner ring kind is fixed at the impl site and dispatch stays
34/// monomorphic (Boost's `traits::ring_const_type<P>::type`).
35///
36/// As with [`crate::Linestring`] and [`crate::Ring`], the interior
37/// rings are exposed via RPITIT so each impl can hand back whatever
38/// iterator its underlying container provides.
39///
40/// # Examples
41///
42/// ```
43/// use geometry_trait::Polygon;
44/// fn hole_count<P: Polygon>(p: &P) -> usize { p.interiors().len() }
45/// ```
46pub trait Polygon: Geometry<Kind = PolygonTag> {
47 /// The ring type used for both the exterior and the interior rings.
48 ///
49 /// Mirrors `boost::geometry::traits::ring_const_type<P>::type`
50 /// (`boost/geometry/core/ring_type.hpp`).
51 type Ring: Ring<Point = Self::Point>;
52
53 /// The exterior ring (outer boundary) of this polygon.
54 ///
55 /// Mirrors `boost::geometry::traits::exterior_ring<P>::get(p)`
56 /// (`boost/geometry/core/exterior_ring.hpp`).
57 fn exterior(&self) -> &Self::Ring;
58
59 /// The interior rings (holes) of this polygon, in declared order.
60 ///
61 /// Mirrors `boost::geometry::traits::interior_rings<P>::get(p)`
62 /// (`boost/geometry/core/interior_rings.hpp`). The returned
63 /// iterator is `ExactSizeIterator` so callers can ask for the
64 /// hole count without consuming it — the analogue of
65 /// `boost::size(traits::interior_rings<P>::get(p))`.
66 fn interiors(&self) -> impl ExactSizeIterator<Item = &Self::Ring>;
67}
68
69#[cfg(test)]
70mod tests {
71 extern crate alloc;
72
73 use super::*;
74 use crate::point::{Point, PointMut};
75 use alloc::vec;
76 use alloc::vec::Vec;
77 use geometry_cs::Cartesian;
78 use geometry_tag::{PointTag, RingTag};
79
80 struct Xy(f64, f64);
81
82 impl Geometry for Xy {
83 type Kind = PointTag;
84 type Point = Self;
85 }
86
87 impl Point for Xy {
88 type Scalar = f64;
89 type Cs = Cartesian;
90 const DIM: usize = 2;
91
92 fn get<const D: usize>(&self) -> f64 {
93 if D == 0 { self.0 } else { self.1 }
94 }
95 }
96
97 impl PointMut for Xy {
98 fn set<const D: usize>(&mut self, v: f64) {
99 if D == 0 {
100 self.0 = v;
101 } else {
102 self.1 = v;
103 }
104 }
105 }
106
107 struct VRing(Vec<Xy>);
108
109 impl Geometry for VRing {
110 type Kind = RingTag;
111 type Point = Xy;
112 }
113
114 impl Ring for VRing {
115 fn points(&self) -> impl ExactSizeIterator<Item = &Xy> + Clone {
116 self.0.iter()
117 }
118 }
119
120 struct VPoly {
121 outer: VRing,
122 inners: Vec<VRing>,
123 }
124
125 impl Geometry for VPoly {
126 type Kind = PolygonTag;
127 type Point = Xy;
128 }
129
130 impl Polygon for VPoly {
131 type Ring = VRing;
132
133 fn exterior(&self) -> &VRing {
134 &self.outer
135 }
136
137 fn interiors(&self) -> impl ExactSizeIterator<Item = &VRing> {
138 self.inners.iter()
139 }
140 }
141
142 #[test]
143 fn polygon_with_two_inner_rings() {
144 let poly = VPoly {
145 outer: VRing(vec![
146 Xy(0.0, 0.0),
147 Xy(10.0, 0.0),
148 Xy(10.0, 10.0),
149 Xy(0.0, 10.0),
150 Xy(0.0, 0.0),
151 ]),
152 inners: vec![
153 VRing(vec![
154 Xy(1.0, 1.0),
155 Xy(2.0, 1.0),
156 Xy(2.0, 2.0),
157 Xy(1.0, 2.0),
158 Xy(1.0, 1.0),
159 ]),
160 VRing(vec![
161 Xy(5.0, 5.0),
162 Xy(6.0, 5.0),
163 Xy(6.0, 6.0),
164 Xy(5.0, 6.0),
165 Xy(5.0, 5.0),
166 ]),
167 ],
168 };
169
170 assert_eq!(poly.exterior().points().count(), 5);
171 assert_eq!(poly.interiors().count(), 2);
172
173 let inner_counts: Vec<usize> = poly.interiors().map(|r| r.points().count()).collect();
174 assert_eq!(inner_counts, vec![5, 5]);
175 }
176}