Skip to main content

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}