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;
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}