Skip to main content

geometry_algorithm/
reverse.rs

1//! `reverse(&mut g)` — flip point order in place.
2//!
3//! Mirrors `boost::geometry::reverse` from
4//! `boost/geometry/algorithms/reverse.hpp`. Per-kind dispatch:
5//!
6//! * `Linestring`, `Ring`         → reverse the backing `Vec<P>`
7//! * `Polygon`                    → reverse outer + every inner ring
8//! * `MultiLinestring`            → reverse each member
9//! * `MultiPolygon`               → reverse each member polygon
10//! * `Point`, `Segment`, `Box`, `MultiPoint` → no-op (Boost ships
11//!   these as silent no-ops; the call sites that drive `reverse` are
12//!   linear/areal)
13//!
14//! `reverse` does NOT change the [`geometry_trait::PointOrder`]
15//! const-generic on `Ring` / `Polygon` — that is a *type-level*
16//! attribute. A clockwise-declared `Ring` mutated to traverse CCW will
17//! compute negative area; [`correct`](fn@crate::correct) re-syncs the
18//! const generic with the stored order.
19
20use geometry_model::{Linestring, MultiLinestring, MultiPolygon, Polygon, Ring};
21use geometry_trait::{Linestring as LinestringTrait, Polygon as PolygonTrait};
22
23/// Reverse the point order of `g` in place.
24///
25/// Mirrors `boost::geometry::reverse(g)` from
26/// `boost/geometry/algorithms/reverse.hpp`.
27pub fn reverse<G: Reverse>(g: &mut G) {
28    g.reverse();
29}
30
31/// Per-kind reverse dispatch. Implemented for the linear / areal model
32/// types; point-like kinds are intentionally absent (reversing a point
33/// is meaningless).
34#[doc(hidden)]
35pub trait Reverse {
36    fn reverse(&mut self);
37}
38
39impl<P: geometry_trait::Point> Reverse for Linestring<P> {
40    fn reverse(&mut self) {
41        self.0.reverse();
42    }
43}
44
45impl<P: geometry_trait::Point, const CW: bool, const CL: bool> Reverse for Ring<P, CW, CL> {
46    fn reverse(&mut self) {
47        self.0.reverse();
48    }
49}
50
51impl<P: geometry_trait::Point, const CW: bool, const CL: bool> Reverse for Polygon<P, CW, CL> {
52    fn reverse(&mut self) {
53        self.outer.reverse();
54        for inner in &mut self.inners {
55            inner.reverse();
56        }
57    }
58}
59
60impl<L: Reverse + LinestringTrait> Reverse for MultiLinestring<L> {
61    fn reverse(&mut self) {
62        for l in &mut self.0 {
63            l.reverse();
64        }
65    }
66}
67
68impl<Pg: Reverse + PolygonTrait> Reverse for MultiPolygon<Pg> {
69    fn reverse(&mut self) {
70        for p in &mut self.0 {
71            p.reverse();
72        }
73    }
74}
75
76#[cfg(test)]
77#[allow(clippy::float_cmp, reason = "Reversed coordinates are exact literals.")]
78mod tests {
79    //! Reference behaviour from
80    //! `boost/geometry/test/algorithms/reverse.cpp` — a linestring's
81    //! points come back in reversed order; a polygon reverses outer and
82    //! inner rings.
83
84    use super::reverse;
85    use geometry_cs::Cartesian;
86    use geometry_model::{Point2D, linestring, polygon};
87    use geometry_trait::{Linestring as _, Point as _, Polygon as _, Ring as _};
88
89    type P = Point2D<f64, Cartesian>;
90
91    #[test]
92    fn reverse_linestring_flips_order() {
93        let mut ls: geometry_model::Linestring<P> = linestring![(0.0, 0.0), (1.0, 1.0), (2.0, 2.0)];
94        reverse(&mut ls);
95        let xs: Vec<f64> = ls.points().map(geometry_trait::Point::get::<0>).collect();
96        assert_eq!(xs, vec![2.0, 1.0, 0.0]);
97    }
98
99    #[test]
100    fn reverse_polygon_flips_outer_and_inner() {
101        let mut pg: geometry_model::Polygon<P> = polygon![
102            [(0.0, 0.0), (0.0, 4.0), (4.0, 4.0), (4.0, 0.0), (0.0, 0.0)],
103            [(1.0, 1.0), (1.0, 2.0), (2.0, 2.0), (2.0, 1.0), (1.0, 1.0)]
104        ];
105        reverse(&mut pg);
106        let outer_first_x = pg.exterior().points().next().unwrap().get::<0>();
107        // First point is unchanged (it is the pivot of the reversal for a
108        // closed ring whose first == last), but the second becomes the old
109        // penultimate. Check the second vertex flipped.
110        let outer_second_x = pg.exterior().points().nth(1).unwrap().get::<0>();
111        assert_eq!(outer_first_x, 0.0);
112        assert_eq!(outer_second_x, 4.0); // was (4.0, 0.0) before reversal
113        assert_eq!(pg.interiors().count(), 1);
114    }
115
116    /// `Reverse for MultiLinestring` maps `reverse` over every member;
117    /// member order itself is unchanged.
118    #[test]
119    fn reverse_multi_linestring_flips_each_member_in_place() {
120        let mut mls: geometry_model::MultiLinestring<geometry_model::Linestring<P>> =
121            geometry_model::MultiLinestring(alloc::vec![
122                linestring![(0.0, 0.0), (1.0, 0.0)],
123                linestring![(5.0, 0.0), (6.0, 0.0), (7.0, 0.0)],
124            ]);
125        reverse(&mut mls);
126        let member_xs: Vec<Vec<f64>> = mls
127            .0
128            .iter()
129            .map(|l| l.points().map(geometry_trait::Point::get::<0>).collect())
130            .collect();
131        // First member is still first (order preserved), each reversed.
132        assert_eq!(member_xs, vec![vec![1.0, 0.0], vec![7.0, 6.0, 5.0]]);
133    }
134
135    /// `Reverse for MultiPolygon` maps `reverse` over every member
136    /// polygon; member order itself is unchanged.
137    #[test]
138    fn reverse_multi_polygon_flips_each_member_in_place() {
139        let a: geometry_model::Polygon<P> =
140            polygon![[(0.0, 0.0), (0.0, 4.0), (4.0, 4.0), (0.0, 0.0)]];
141        let b: geometry_model::Polygon<P> =
142            polygon![[(10.0, 0.0), (10.0, 4.0), (14.0, 4.0), (10.0, 0.0)]];
143        let mut mpg: geometry_model::MultiPolygon<geometry_model::Polygon<P>> =
144            geometry_model::MultiPolygon(alloc::vec![a, b]);
145        reverse(&mut mpg);
146        // Member order preserved: first polygon still starts at x == 0.
147        let firsts: Vec<f64> = mpg
148            .0
149            .iter()
150            .map(|pg| pg.exterior().points().next().unwrap().get::<0>())
151            .collect();
152        assert_eq!(firsts, vec![0.0, 10.0]);
153        // Each member's second vertex is its old penultimate vertex.
154        let seconds: Vec<f64> = mpg
155            .0
156            .iter()
157            .map(|pg| pg.exterior().points().nth(1).unwrap().get::<0>())
158            .collect();
159        assert_eq!(seconds, vec![4.0, 14.0]);
160    }
161}