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