Skip to main content

geometry_trait/
ring.rs

1//! The [`Ring`] concept: an ordered sequence of points forming a
2//! closed boundary.
3//!
4//! Mirrors `doc/concept/ring.qbk` and the model in
5//! `boost/geometry/geometries/ring.hpp`. The two ring-only metafunctions
6//! `boost::geometry::traits::closure<G>` and
7//! `boost::geometry::traits::point_order<G>` (from
8//! `boost/geometry/core/closure.hpp` and
9//! `boost/geometry/core/point_order.hpp`) fold into trait methods on
10//! [`Ring`] here, with the same defaults Boost ships
11//! (`closed` / `clockwise`).
12
13use crate::closure::Closure;
14use crate::geometry::Geometry;
15use crate::point_order::PointOrder;
16use geometry_tag::RingTag;
17
18/// A ring — an ordered sequence of points whose first and last
19/// either coincide ([`Closure::Closed`]) or are implicitly connected
20/// ([`Closure::Open`]).
21///
22/// Mirrors the Ring concept (`doc/concept/ring.qbk`); the canonical
23/// model is `boost::geometry::model::ring` in
24/// `boost/geometry/geometries/ring.hpp`, which is parameterised on
25/// `bool ClockWise = true, bool Closed = true` — the Boost defaults
26/// match the defaults on [`Ring::point_order`] and [`Ring::closure`].
27///
28/// As with [`crate::Linestring`], points are exposed as an
29/// `ExactSizeIterator + Clone` returned via RPITIT so each impl can
30/// reuse whatever iterator its container provides.
31///
32/// # Examples
33///
34/// ```
35/// use geometry_trait::{Closure, PointOrder, Ring};
36/// fn ring_defaults<R: Ring>(r: &R) -> (Closure, PointOrder) {
37///     (r.closure(), r.point_order())
38/// }
39/// ```
40pub trait Ring: Geometry<Kind = RingTag> {
41    /// The points of this ring, in declared order.
42    ///
43    /// Plays the role of `boost::begin(r)` / `boost::end(r)` from
44    /// `boost/geometry/geometries/ring.hpp` when read together.
45    fn points(&self) -> impl ExactSizeIterator<Item = &Self::Point> + Clone;
46
47    /// Whether this ring's last point repeats its first.
48    ///
49    /// Mirrors `boost::geometry::traits::closure<R>::value`
50    /// (`boost/geometry/core/closure.hpp`). Defaults to
51    /// [`Closure::Closed`], matching Boost's default specialisation
52    /// `traits::closure<G>::value = closed`.
53    fn closure(&self) -> Closure {
54        Closure::Closed
55    }
56
57    /// The traversal direction of this ring's boundary.
58    ///
59    /// Mirrors `boost::geometry::traits::point_order<R>::value`
60    /// (`boost/geometry/core/point_order.hpp`). Defaults to
61    /// [`PointOrder::Clockwise`], matching Boost's default
62    /// specialisation `traits::point_order<G>::value = clockwise`.
63    fn point_order(&self) -> PointOrder {
64        PointOrder::Clockwise
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    extern crate alloc;
71
72    use super::*;
73    use crate::point::{Point, PointMut};
74    use alloc::vec;
75    use alloc::vec::Vec;
76    use geometry_cs::Cartesian;
77    use geometry_tag::PointTag;
78
79    struct Xy(f64, f64);
80
81    impl Geometry for Xy {
82        type Kind = PointTag;
83        type Point = Self;
84    }
85
86    impl Point for Xy {
87        type Scalar = f64;
88        type Cs = Cartesian;
89        const DIM: usize = 2;
90
91        fn get<const D: usize>(&self) -> f64 {
92            if D == 0 { self.0 } else { self.1 }
93        }
94    }
95
96    impl PointMut for Xy {
97        fn set<const D: usize>(&mut self, v: f64) {
98            if D == 0 {
99                self.0 = v;
100            } else {
101                self.1 = v;
102            }
103        }
104    }
105
106    struct VRing(Vec<Xy>);
107
108    impl Geometry for VRing {
109        type Kind = RingTag;
110        type Point = Xy;
111    }
112
113    impl Ring for VRing {
114        fn points(&self) -> impl ExactSizeIterator<Item = &Xy> + Clone {
115            self.0.iter()
116        }
117        // Inherit defaults: closure() = Closed, point_order() = Clockwise.
118    }
119
120    #[test]
121    fn ring_defaults_are_closed_clockwise() {
122        let r = VRing(vec![
123            Xy(0.0, 0.0),
124            Xy(1.0, 0.0),
125            Xy(1.0, 1.0),
126            Xy(0.0, 1.0),
127            Xy(0.0, 0.0),
128        ]);
129        assert_eq!(r.closure(), Closure::Closed);
130        assert_eq!(r.point_order(), PointOrder::Clockwise);
131        assert_eq!(r.points().count(), 5);
132    }
133
134    #[test]
135    fn ring_iterates_in_declared_order() {
136        let r = VRing(vec![Xy(0.0, 0.0), Xy(1.0, 0.0), Xy(0.0, 1.0), Xy(0.0, 0.0)]);
137        let xs: Vec<f64> = r.points().map(Xy::get::<0>).collect();
138        assert_eq!(xs, vec![0.0, 1.0, 0.0, 0.0]);
139    }
140
141    // A ring impl that overrides both defaults — confirms the trait
142    // methods are not `final`-by-default.
143    struct OpenCcw(Vec<Xy>);
144
145    impl Geometry for OpenCcw {
146        type Kind = RingTag;
147        type Point = Xy;
148    }
149
150    impl Ring for OpenCcw {
151        fn points(&self) -> impl ExactSizeIterator<Item = &Xy> + Clone {
152            self.0.iter()
153        }
154        fn closure(&self) -> Closure {
155            Closure::Open
156        }
157        fn point_order(&self) -> PointOrder {
158            PointOrder::CounterClockwise
159        }
160    }
161
162    #[test]
163    fn ring_defaults_can_be_overridden() {
164        let r = OpenCcw(vec![Xy(0.0, 0.0), Xy(1.0, 0.0), Xy(0.0, 1.0)]);
165        assert_eq!(r.closure(), Closure::Open);
166        assert_eq!(r.point_order(), PointOrder::CounterClockwise);
167        assert_eq!(r.points().count(), 3);
168    }
169}