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}