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;
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    struct VRing(Vec<Xy>);
97
98    impl Geometry for VRing {
99        type Kind = RingTag;
100        type Point = Xy;
101    }
102
103    impl Ring for VRing {
104        fn points(&self) -> impl ExactSizeIterator<Item = &Xy> + Clone {
105            self.0.iter()
106        }
107        // Inherit defaults: closure() = Closed, point_order() = Clockwise.
108    }
109
110    #[test]
111    fn ring_defaults_are_closed_clockwise() {
112        let r = VRing(vec![
113            Xy(0.0, 0.0),
114            Xy(1.0, 0.0),
115            Xy(1.0, 1.0),
116            Xy(0.0, 1.0),
117            Xy(0.0, 0.0),
118        ]);
119        assert_eq!(r.closure(), Closure::Closed);
120        assert_eq!(r.point_order(), PointOrder::Clockwise);
121        assert_eq!(r.points().count(), 5);
122    }
123
124    #[test]
125    fn ring_iterates_in_declared_order() {
126        let r = VRing(vec![Xy(0.0, 0.0), Xy(1.0, 0.0), Xy(0.0, 1.0), Xy(0.0, 0.0)]);
127        let xs: Vec<f64> = r.points().map(Xy::get::<0>).collect();
128        assert_eq!(xs, vec![0.0, 1.0, 0.0, 0.0]);
129    }
130
131    // A ring impl that overrides both defaults — confirms the trait
132    // methods are not `final`-by-default.
133    struct OpenCcw(Vec<Xy>);
134
135    impl Geometry for OpenCcw {
136        type Kind = RingTag;
137        type Point = Xy;
138    }
139
140    impl Ring for OpenCcw {
141        fn points(&self) -> impl ExactSizeIterator<Item = &Xy> + Clone {
142            self.0.iter()
143        }
144        fn closure(&self) -> Closure {
145            Closure::Open
146        }
147        fn point_order(&self) -> PointOrder {
148            PointOrder::CounterClockwise
149        }
150    }
151
152    #[test]
153    fn ring_defaults_can_be_overridden() {
154        let r = OpenCcw(vec![Xy(0.0, 0.0), Xy(1.0, 0.0), Xy(0.0, 1.0)]);
155        assert_eq!(r.closure(), Closure::Open);
156        assert_eq!(r.point_order(), PointOrder::CounterClockwise);
157        assert_eq!(r.points().count(), 3);
158    }
159}