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}