Skip to main content

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}