geometry_model/ring.rs
1//! Default `model::Ring<P, CW, CL>` — an ordered sequence of points
2//! forming a (closed or open, clockwise or counter-clockwise)
3//! boundary, stored in a `Vec<P>`.
4//!
5//! Mirrors `boost::geometry::model::ring<Point, ClockWise, Closed,
6//! Container, Allocator>` declared in
7//! `boost/geometry/geometries/ring.hpp:56-99`, together with the
8//! trait specialisations the same header provides under
9//! `namespace traits` (`tag`, `point_order`, `closure`) at
10//! `boost/geometry/geometries/ring.hpp:104-168`. The Rust port
11//! collapses those four specialisations into the two trait impls
12//! below ([`Geometry`], [`RingTrait`]), keyed off the generic point
13//! parameter `P` and the two const-generic booleans.
14//!
15//! Boost encodes closure and traversal direction at the type level
16//! through the two `bool` template parameters; the Rust port mirrors
17//! that with two const generics so a ring with custom orientation is
18//! a distinct type, exactly like `model::ring<P, false, false>` is in
19//! C++. Boost defaults `ClockWise = true, Closed = true`
20//! (`boost/geometry/geometries/ring.hpp:59`); the Rust defaults
21//! match.
22
23use alloc::vec::Vec;
24
25use geometry_tag::RingTag;
26use geometry_trait::{Closure, Geometry, Point as PointTrait, PointOrder, Ring as RingTrait};
27
28/// Default Ring — a `Vec<P>` newtype carrying closure and traversal
29/// direction as const generics.
30///
31/// Mirrors `boost::geometry::model::ring<Point, ClockWise, Closed>`
32/// from `boost/geometry/geometries/ring.hpp:56-99`. The defaults
33/// (`CLOCKWISE = true`, `CLOSED = true`) match Boost's defaults
34/// (`boost/geometry/geometries/ring.hpp:59`).
35#[derive(Debug, Clone, PartialEq)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
37pub struct Ring<P: PointTrait, const CLOCKWISE: bool = true, const CLOSED: bool = true>(pub Vec<P>);
38
39impl<P: PointTrait, const CW: bool, const CL: bool> Ring<P, CW, CL> {
40 /// Construct an empty ring.
41 ///
42 /// Mirrors the default `ring()` constructor at
43 /// `boost/geometry/geometries/ring.hpp:71-73`.
44 #[must_use]
45 pub const fn new() -> Self {
46 Self(Vec::new())
47 }
48
49 /// Wrap an existing `Vec<P>` as a ring without copying.
50 ///
51 /// Rust analogue of the iterator-range `ring(Iterator, Iterator)`
52 /// constructor at `boost/geometry/geometries/ring.hpp:76-79`; the
53 /// `Vec` is moved in rather than copied element-by-element.
54 #[must_use]
55 pub const fn from_vec(v: Vec<P>) -> Self {
56 Self(v)
57 }
58
59 /// Append a point to the back of the ring.
60 ///
61 /// Plays the role of `std::vector::push_back` on the inherited
62 /// `base_type` in `boost/geometry/geometries/ring.hpp:63-67`.
63 pub fn push(&mut self, p: P) {
64 self.0.push(p);
65 }
66}
67
68impl<P: PointTrait, const CW: bool, const CL: bool> Default for Ring<P, CW, CL> {
69 #[inline]
70 fn default() -> Self {
71 Self::new()
72 }
73}
74
75/// Tag this concrete type as a Ring. Mirrors the
76/// `traits::tag<model::ring<...>>` specialisation at
77/// `boost/geometry/geometries/ring.hpp:108-118`.
78impl<P: PointTrait, const CW: bool, const CL: bool> Geometry for Ring<P, CW, CL> {
79 type Kind = RingTag;
80 type Point = P;
81}
82
83/// Wire the Ring concept up. The `points()` iterator hands back
84/// `self.0.iter()` — `slice::Iter` is already
85/// `ExactSizeIterator + Clone`. The `closure()` and `point_order()`
86/// methods read the two const-generic booleans, mirroring the four
87/// `traits::point_order` / `traits::closure` specialisations at
88/// `boost/geometry/geometries/ring.hpp:121-168` that dispatch on the
89/// same two template parameters.
90impl<P: PointTrait, const CW: bool, const CL: bool> RingTrait for Ring<P, CW, CL> {
91 fn points(&self) -> impl ExactSizeIterator<Item = &P> + Clone {
92 self.0.iter()
93 }
94
95 fn closure(&self) -> Closure {
96 if CL { Closure::Closed } else { Closure::Open }
97 }
98
99 fn point_order(&self) -> PointOrder {
100 if CW {
101 PointOrder::Clockwise
102 } else {
103 PointOrder::CounterClockwise
104 }
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 //! Iteration + tag-resolution + closure/order + `check_ring`
111 //! witness for [`Ring`]. Mirrors
112 //! `boost/geometry/test/core/tag.cpp` (the ring-tag arm) and
113 //! `boost/geometry/test/core/ring.cpp` (the closure / `point_order`
114 //! cases on `model::ring<P, CW, CL>`).
115
116 use super::Ring;
117 use crate::point::Point2D;
118 use alloc::vec;
119 use alloc::vec::Vec;
120 use geometry_cs::Cartesian;
121 use geometry_tag::RingTag;
122 use geometry_trait::{
123 Closure, Geometry, Point as _, PointOrder, Ring as RingTrait, check_ring,
124 };
125
126 #[test]
127 fn ring_iterates_in_declared_order() {
128 let mut r = Ring::<Point2D<f64, Cartesian>>::new();
129 r.push(Point2D::new(0.0, 0.0));
130 r.push(Point2D::new(1.0, 0.0));
131 r.push(Point2D::new(0.0, 1.0));
132 r.push(Point2D::new(0.0, 0.0));
133 assert_eq!(r.points().count(), 4);
134 let xs: Vec<u64> = r.points().map(|p| p.get::<0>().to_bits()).collect();
135 assert_eq!(
136 xs,
137 vec![
138 0.0_f64.to_bits(),
139 1.0_f64.to_bits(),
140 0.0_f64.to_bits(),
141 0.0_f64.to_bits(),
142 ]
143 );
144 }
145
146 #[test]
147 fn ring_defaults_are_closed_clockwise() {
148 let r = Ring::<Point2D<f64, Cartesian>>::new();
149 assert_eq!(r.closure(), Closure::Closed);
150 assert_eq!(r.point_order(), PointOrder::Clockwise);
151 }
152
153 #[test]
154 fn ring_with_custom_const_generics() {
155 let r = Ring::<Point2D<f64, Cartesian>, false, false>::new();
156 assert_eq!(r.closure(), Closure::Open);
157 assert_eq!(r.point_order(), PointOrder::CounterClockwise);
158 }
159
160 #[test]
161 fn ring_kind_is_ring_tag() {
162 fn k<T: Geometry<Kind = RingTag>>() {}
163 k::<Ring<Point2D<f64, Cartesian>>>();
164 k::<Ring<Point2D<f64, Cartesian>, false, false>>();
165 }
166
167 #[test]
168 fn ring_satisfies_concept() {
169 check_ring::<Ring<Point2D<f64, Cartesian>>>();
170 check_ring::<Ring<Point2D<f64, Cartesian>, false, false>>();
171 }
172}