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;
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 struct VRing(Vec<Xy>);
98
99 impl Geometry for VRing {
100 type Kind = RingTag;
101 type Point = Xy;
102 }
103
104 impl Ring for VRing {
105 fn points(&self) -> impl ExactSizeIterator<Item = &Xy> + Clone {
106 self.0.iter()
107 }
108 }
109
110 struct VPoly {
111 outer: VRing,
112 inners: Vec<VRing>,
113 }
114
115 impl Geometry for VPoly {
116 type Kind = PolygonTag;
117 type Point = Xy;
118 }
119
120 impl Polygon for VPoly {
121 type Ring = VRing;
122
123 fn exterior(&self) -> &VRing {
124 &self.outer
125 }
126
127 fn interiors(&self) -> impl ExactSizeIterator<Item = &VRing> {
128 self.inners.iter()
129 }
130 }
131
132 #[test]
133 fn polygon_with_two_inner_rings() {
134 let poly = VPoly {
135 outer: VRing(vec![
136 Xy(0.0, 0.0),
137 Xy(10.0, 0.0),
138 Xy(10.0, 10.0),
139 Xy(0.0, 10.0),
140 Xy(0.0, 0.0),
141 ]),
142 inners: vec![
143 VRing(vec![
144 Xy(1.0, 1.0),
145 Xy(2.0, 1.0),
146 Xy(2.0, 2.0),
147 Xy(1.0, 2.0),
148 Xy(1.0, 1.0),
149 ]),
150 VRing(vec![
151 Xy(5.0, 5.0),
152 Xy(6.0, 5.0),
153 Xy(6.0, 6.0),
154 Xy(5.0, 6.0),
155 Xy(5.0, 5.0),
156 ]),
157 ],
158 };
159
160 assert_eq!(poly.exterior().points().count(), 5);
161 assert_eq!(poly.interiors().count(), 2);
162 let first = poly.exterior().points().next().unwrap();
163 assert_eq!(first.get::<0>().to_bits(), 0.0_f64.to_bits());
164 assert_eq!(first.get::<1>().to_bits(), 0.0_f64.to_bits());
165
166 let inner_counts: Vec<usize> = poly.interiors().map(|r| r.points().count()).collect();
167 assert_eq!(inner_counts, vec![5, 5]);
168 }
169}