1use alloc::vec::Vec;
23
24use geometry_coords::CoordinateScalar;
25use geometry_model::{Linestring, Polygon, Ring, Segment};
26use geometry_strategy::{CartesianIntersects, IntersectsStrategy};
27use geometry_trait::{Linestring as LinestringTrait, Point, Polygon as PolygonTrait};
28
29#[inline]
36#[must_use]
37pub fn is_simple<G: IsSimple>(g: &G) -> bool {
38 g.is_simple()
39}
40
41#[doc(hidden)]
44pub trait IsSimple {
45 fn is_simple(&self) -> bool;
47}
48
49impl<P> IsSimple for Linestring<P>
50where
51 P: Point,
52 P::Scalar: CoordinateScalar,
53 CartesianIntersects: IntersectsStrategy<Segment<P>, Segment<P>>,
54 P: geometry_trait::PointMut + Default + Copy,
55{
56 fn is_simple(&self) -> bool {
57 let pts: Vec<P> = self.points().copied().collect();
58 linestring_points_simple(&pts)
59 }
60}
61
62impl<P, const CW: bool, const CL: bool> IsSimple for Polygon<P, CW, CL>
63where
64 P: Point,
65 P::Scalar: CoordinateScalar,
66{
67 fn is_simple(&self) -> bool {
68 ring_lacks_duplicates(self.exterior()) && self.interiors().all(|r| ring_lacks_duplicates(r))
76 }
77}
78
79fn ring_lacks_duplicates<P, const CW: bool, const CL: bool>(r: &Ring<P, CW, CL>) -> bool
86where
87 P: Point,
88 P::Scalar: CoordinateScalar,
89{
90 let pts: &[P] = &r.0;
91 if pts.is_empty() {
92 return false;
93 }
94 for w in pts.windows(2) {
95 if points_equal(&w[0], &w[1]) {
96 return false;
97 }
98 }
99 if matches!(
100 geometry_trait::Ring::closure(r),
101 geometry_trait::Closure::Open
102 ) && pts.len() >= 2
103 && points_equal(&pts[pts.len() - 1], &pts[0])
104 {
105 return false;
108 }
109 true
110}
111
112fn linestring_points_simple<P>(pts: &[P]) -> bool
114where
115 P: Point + geometry_trait::PointMut + Default + Copy,
116 P::Scalar: CoordinateScalar,
117 CartesianIntersects: IntersectsStrategy<Segment<P>, Segment<P>>,
118{
119 if pts.len() < 2 {
120 return true;
121 }
122
123 let closed = points_equal(&pts[0], &pts[pts.len() - 1]);
126
127 let segs: Vec<Segment<P>> = pts.windows(2).map(|w| Segment::new(w[0], w[1])).collect();
128
129 for i in 0..segs.len() {
130 if points_equal(&pts[i], &pts[i + 1]) {
133 return false;
134 }
135 for j in (i + 1)..segs.len() {
136 let adjacent = j == i + 1;
137 if adjacent {
138 if doubles_back(&pts[i], &pts[i + 1], &pts[j + 1]) {
142 return false;
143 }
144 } else if closed && i == 0 && j == segs.len() - 1 {
145 } else if CartesianIntersects.intersects(&segs[i], &segs[j]) {
149 return false;
150 }
151 }
152 }
153 true
154}
155
156#[inline]
158fn points_equal<P: Point>(a: &P, b: &P) -> bool {
159 a.get::<0>() == b.get::<0>() && a.get::<1>() == b.get::<1>()
160}
161
162#[inline]
166fn doubles_back<P: Point>(p0: &P, p1: &P, p2: &P) -> bool
167where
168 P::Scalar: CoordinateScalar,
169{
170 let ax = p1.get::<0>() - p0.get::<0>();
171 let ay = p1.get::<1>() - p0.get::<1>();
172 let bx = p2.get::<0>() - p1.get::<0>();
173 let by = p2.get::<1>() - p1.get::<1>();
174 let cross = ax * by - ay * bx;
175 if cross != P::Scalar::ZERO {
176 return false;
177 }
178 ax * bx + ay * by < P::Scalar::ZERO
181}
182
183#[cfg(test)]
184mod tests {
185 use super::is_simple;
189 use geometry_cs::Cartesian;
190 use geometry_model::{Linestring, Point2D, Polygon, linestring, polygon};
191
192 type Pt = Point2D<f64, Cartesian>;
193
194 #[test]
195 fn two_point_is_simple() {
196 let ls: Linestring<Pt> = linestring![(0., 0.), (1., 2.)];
197 assert!(is_simple(&ls));
198 }
199
200 #[test]
201 fn three_point_is_simple() {
202 let ls: Linestring<Pt> = linestring![(0., 0.), (1., 2.), (2., 3.)];
203 assert!(is_simple(&ls));
204 }
205
206 #[test]
207 fn consecutive_duplicate_not_simple() {
208 let ls: Linestring<Pt> = linestring![(0., 0.), (0., 0.), (1., 0.)];
209 assert!(!is_simple(&ls));
210 }
211
212 #[test]
213 fn figure_eight_not_simple() {
214 let ls: Linestring<Pt> =
215 linestring![(0., 0.), (1., 0.), (2., 0.), (1., 1.), (1., 0.), (1., -1.)];
216 assert!(!is_simple(&ls));
217 }
218
219 #[test]
220 fn bowtie_linestring_not_simple() {
221 let ls: Linestring<Pt> = linestring![(0., 0.), (2., 2.), (2., 0.), (0., 2.)];
223 assert!(!is_simple(&ls));
224 }
225
226 #[test]
227 fn closed_simple_quadrilateral() {
228 let ls: Linestring<Pt> = linestring![(0., 0.), (1., 0.), (1., 1.), (0., 0.)];
229 assert!(is_simple(&ls));
230 }
231
232 #[test]
233 fn closed_square_is_simple() {
234 let ls: Linestring<Pt> = linestring![(0., 0.), (10., 0.), (10., 10.), (0., 10.), (0., 0.)];
235 assert!(is_simple(&ls));
236 }
237
238 #[test]
239 fn unit_square_polygon_is_simple() {
240 let pg: Polygon<Pt> = polygon![[(0., 0.), (4., 0.), (4., 3.), (0., 3.), (0., 0.)]];
241 assert!(is_simple(&pg));
242 }
243
244 #[test]
245 fn polygon_with_disjoint_hole_is_simple() {
246 let pg: Polygon<Pt> = polygon![
247 [(0., 0.), (10., 0.), (10., 10.), (0., 10.), (0., 0.)],
248 [(2., 2.), (4., 2.), (4., 4.), (2., 4.), (2., 2.)],
249 ];
250 assert!(is_simple(&pg));
251 }
252
253 #[test]
254 fn bowtie_polygon_is_simple_but_invalid() {
255 let pg: Polygon<Pt> = polygon![[(0., 0.), (2., 2.), (0., 2.), (2., 0.), (0., 0.)]];
260 assert!(is_simple(&pg));
261 }
262
263 #[test]
264 fn hole_touching_outer_is_simple() {
265 let pg: Polygon<Pt> = polygon![
270 [(0., 0.), (10., 0.), (10., 10.), (0., 10.), (0., 0.)],
271 [(0., 0.), (5., 5.), (10., 0.), (5., 0.), (0., 0.)],
272 ];
273 assert!(is_simple(&pg));
274 }
275
276 #[test]
277 fn polygon_with_consecutive_duplicate_is_not_simple() {
278 let pg: Polygon<Pt> =
281 polygon![[(0., 0.), (4., 0.), (4., 0.), (4., 4.), (0., 4.), (0., 0.)]];
282 assert!(!is_simple(&pg));
283 }
284
285 #[test]
286 fn polygon_with_duplicate_in_hole_is_not_simple() {
287 let pg: Polygon<Pt> = polygon![
288 [(0., 0.), (10., 0.), (10., 10.), (0., 10.), (0., 0.)],
289 [(2., 2.), (4., 2.), (4., 2.), (4., 4.), (2., 4.), (2., 2.)],
290 ];
291 assert!(!is_simple(&pg));
292 }
293
294 #[test]
295 fn polygon_with_empty_exterior_is_not_simple() {
296 use geometry_model::Ring;
299 let pg: Polygon<Pt> = Polygon::new(Ring::new());
300 assert!(!is_simple(&pg));
301 }
302}