1use geometry_coords::CoordinateScalar;
23use geometry_cs::CoordinateSystem;
24use geometry_model::{
25 Box as ModelBox, DynGeometry, DynGeometryCollection, Linestring, MultiLinestring, MultiPoint,
26 MultiPolygon, Point as ModelPoint, Polygon, Ring, Segment,
27};
28use geometry_strategy::{AreaStrategy, ShoelaceArea};
29use geometry_trait::{
30 Closure, Linestring as LinestringTrait, Point as PointTrait, Ring as RingTrait,
31};
32
33pub fn correct<G: Correct>(g: &mut G) {
38 g.correct();
39}
40
41#[inline]
49pub fn correct_closure<G: CorrectClosure>(g: &mut G) {
50 g.correct_closure();
51}
52
53#[doc(hidden)]
55pub trait Correct {
56 fn correct(&mut self);
57}
58
59#[doc(hidden)]
64pub trait CorrectClosure {
65 fn correct_closure(&mut self);
66}
67
68fn fix_closure<P, const CW: bool, const CL: bool>(r: &mut Ring<P, CW, CL>)
71where
72 P: PointTrait + Copy,
73{
74 if r.0.len() <= 2 {
78 return;
79 }
80 let first = r.0[0];
81 let last = *r.0.last().unwrap();
82 let should_be_closed = matches!(r.closure(), Closure::Closed);
83 let already_closed = coords_equal(&first, &last);
84 match (should_be_closed, already_closed) {
85 (true, false) => r.0.push(first), (false, true) => {
87 r.0.pop(); }
89 _ => {}
90 }
91}
92
93fn coords_equal<P: PointTrait>(a: &P, b: &P) -> bool {
97 geometry_trait::fold_dims(true, a, |acc, _p, d| {
98 acc && match d {
99 0 => a.get::<0>() == b.get::<0>(),
100 1 => a.get::<1>() == b.get::<1>(),
101 2 => a.get::<2>() == b.get::<2>(),
102 3 => a.get::<3>() == b.get::<3>(),
103 _ => unreachable!("fold_dims caps at MAX_DIM"),
104 }
105 })
106}
107
108fn fix_orientation<P, const CW: bool, const CL: bool>(r: &mut Ring<P, CW, CL>, want_positive: bool)
110where
111 P: PointTrait,
112 ShoelaceArea: AreaStrategy<Ring<P, CW, CL>, Out = P::Scalar>,
113{
114 let a = ShoelaceArea.area(&*r);
115 let zero = <P::Scalar as CoordinateScalar>::ZERO;
116 let is_positive = a > zero;
117 let is_negative = a < zero;
118 if (want_positive && is_negative) || (!want_positive && is_positive) {
121 r.0.reverse();
122 }
123}
124
125impl<P, const CW: bool, const CL: bool> Correct for Ring<P, CW, CL>
126where
127 P: PointTrait + Copy,
128 P::Cs: CoordinateSystem,
129 ShoelaceArea: AreaStrategy<Ring<P, CW, CL>, Out = P::Scalar>,
130{
131 fn correct(&mut self) {
132 fix_closure(self);
133 fix_orientation(self, true);
141 }
142}
143
144impl<P, const CW: bool, const CL: bool> CorrectClosure for Ring<P, CW, CL>
147where
148 P: PointTrait + Copy,
149{
150 fn correct_closure(&mut self) {
151 fix_closure(self);
152 }
153}
154
155impl<P, const CW: bool, const CL: bool> Correct for Polygon<P, CW, CL>
156where
157 P: PointTrait + Copy,
158 P::Cs: CoordinateSystem,
159 ShoelaceArea: AreaStrategy<Ring<P, CW, CL>, Out = P::Scalar>,
160{
161 fn correct(&mut self) {
162 fix_closure(&mut self.outer);
163 fix_orientation(&mut self.outer, true);
166 for inner in &mut self.inners {
167 fix_closure(inner);
168 fix_orientation(inner, false);
173 }
174 }
175}
176
177impl<P, const CW: bool, const CL: bool> CorrectClosure for Polygon<P, CW, CL>
180where
181 P: PointTrait + Copy,
182{
183 fn correct_closure(&mut self) {
184 fix_closure(&mut self.outer);
185 for inner in &mut self.inners {
186 fix_closure(inner);
187 }
188 }
189}
190
191impl<Pg: Correct + geometry_trait::Polygon> Correct for MultiPolygon<Pg> {
192 fn correct(&mut self) {
193 for p in &mut self.0 {
194 p.correct();
195 }
196 }
197}
198
199impl<Pg> CorrectClosure for MultiPolygon<Pg>
202where
203 Pg: CorrectClosure + geometry_trait::Polygon,
204{
205 fn correct_closure(&mut self) {
206 for polygon in &mut self.0 {
207 polygon.correct_closure();
208 }
209 }
210}
211
212impl<T, const D: usize, Cs> CorrectClosure for ModelPoint<T, D, Cs>
214where
215 T: CoordinateScalar,
216 Cs: CoordinateSystem,
217{
218 fn correct_closure(&mut self) {}
219}
220
221impl<P: PointTrait> CorrectClosure for Linestring<P> {
224 fn correct_closure(&mut self) {}
225}
226
227impl<P: PointTrait> CorrectClosure for Segment<P> {
229 fn correct_closure(&mut self) {}
230}
231
232impl<P: PointTrait> CorrectClosure for ModelBox<P> {
234 fn correct_closure(&mut self) {}
235}
236
237impl<P: PointTrait> CorrectClosure for MultiPoint<P> {
240 fn correct_closure(&mut self) {}
241}
242
243impl<L: LinestringTrait> CorrectClosure for MultiLinestring<L> {
246 fn correct_closure(&mut self) {}
247}
248
249impl<T, Cs> CorrectClosure for DynGeometry<T, Cs>
252where
253 T: CoordinateScalar,
254 Cs: CoordinateSystem + Copy,
255{
256 fn correct_closure(&mut self) {
257 match self {
258 DynGeometry::Point(_)
259 | DynGeometry::LineString(_)
260 | DynGeometry::MultiPoint(_)
261 | DynGeometry::MultiLineString(_) => {}
262 DynGeometry::Polygon(polygon) => polygon.correct_closure(),
263 DynGeometry::MultiPolygon(multi_polygon) => multi_polygon.correct_closure(),
264 DynGeometry::GeometryCollection(geometries) => {
265 for geometry in geometries {
266 geometry.correct_closure();
267 }
268 }
269 }
270 }
271}
272
273impl<T, Cs> CorrectClosure for DynGeometryCollection<T, Cs>
276where
277 T: CoordinateScalar,
278 Cs: CoordinateSystem + Copy,
279{
280 fn correct_closure(&mut self) {
281 for geometry in &mut self.0 {
282 geometry.correct_closure();
283 }
284 }
285}
286
287#[cfg(test)]
288mod tests {
289 #![allow(clippy::float_cmp, reason = "Areas are exact integer literals.")]
295
296 use super::correct;
297 use crate::area::ring_area;
298 use geometry_cs::Cartesian;
299 use geometry_model::{Point2D, Ring};
300
301 type P = Point2D<f64, Cartesian>;
302
303 #[test]
304 fn ccw_exterior_of_cw_ring_is_reversed() {
305 let mut r: Ring<P> = Ring::from_vec(vec![
308 P::new(0.0, 0.0),
309 P::new(2.0, 0.0),
310 P::new(2.0, 2.0),
311 P::new(0.0, 2.0),
312 P::new(0.0, 0.0),
313 ]);
314 assert!(ring_area(&r) < 0.0, "precondition: CCW ring is negative");
315 correct(&mut r);
316 assert_eq!(ring_area(&r), 4.0);
317 }
318
319 #[test]
320 fn already_correct_ring_is_unchanged() {
321 let mut r: Ring<P> = Ring::from_vec(vec![
324 P::new(0.0, 0.0),
325 P::new(0.0, 2.0),
326 P::new(2.0, 2.0),
327 P::new(2.0, 0.0),
328 P::new(0.0, 0.0),
329 ]);
330 assert_eq!(ring_area(&r), 4.0);
331 correct(&mut r);
332 assert_eq!(ring_area(&r), 4.0);
333 }
334
335 #[test]
336 fn two_point_ring_is_left_untouched() {
337 use geometry_trait::Ring as _;
341 let mut r: Ring<P> = Ring::from_vec(vec![P::new(0.0, 0.0), P::new(1.0, 1.0)]);
342 correct(&mut r);
343 assert_eq!(r.points().count(), 2, "2-point ring must stay 2 points");
344 }
345
346 #[test]
350 fn open_declared_ring_drops_redundant_closing_vertex() {
351 use geometry_trait::Ring as _;
352 let mut r: Ring<P, true, false> = Ring::from_vec(vec![
354 P::new(0.0, 0.0),
355 P::new(0.0, 2.0),
356 P::new(2.0, 2.0),
357 P::new(2.0, 0.0),
358 P::new(0.0, 0.0),
359 ]);
360 correct(&mut r);
361 assert_eq!(r.points().count(), 4);
363 assert_eq!(ring_area(&r), 4.0);
365 }
366
367 #[test]
370 fn multipolygon_corrects_each_member() {
371 use geometry_model::{MultiPolygon, Polygon};
372 let ccw_square = || {
373 Polygon::<P>::new(Ring::from_vec(vec![
374 P::new(0.0, 0.0),
375 P::new(2.0, 0.0),
376 P::new(2.0, 2.0),
377 P::new(0.0, 2.0),
378 P::new(0.0, 0.0),
379 ]))
380 };
381 let mut mpg: MultiPolygon<Polygon<P>> = MultiPolygon(vec![ccw_square(), ccw_square()]);
382 assert!(ring_area(&mpg.0[0].outer) < 0.0);
384 correct(&mut mpg);
385 assert_eq!(ring_area(&mpg.0[0].outer), 4.0);
386 assert_eq!(ring_area(&mpg.0[1].outer), 4.0);
387 }
388
389 #[test]
393 fn coords_equal_compares_the_third_ordinate() {
394 use geometry_model::Point3D;
395 use geometry_trait::Ring as _;
396 type P3 = Point3D<f64, Cartesian>;
397 let mut r: Ring<P3, true, false> = Ring::from_vec(vec![
401 P3::new(0.0, 0.0, 5.0),
402 P3::new(1.0, 0.0, 5.0),
403 P3::new(1.0, 1.0, 5.0),
404 P3::new(0.0, 0.0, 5.0),
405 ]);
406 let before = r.points().count();
410 super::fix_closure(&mut r);
411 assert_eq!(before, 4);
412 assert_eq!(r.points().count(), 3, "closing vertex dropped");
413
414 let mut open: Ring<P3, true, false> = Ring::from_vec(vec![
416 P3::new(0.0, 0.0, 5.0),
417 P3::new(1.0, 0.0, 5.0),
418 P3::new(1.0, 1.0, 5.0),
419 P3::new(0.0, 0.0, 9.0), ]);
421 super::fix_closure(&mut open);
422 assert_eq!(open.points().count(), 4, "z differs → not closed → kept");
423 }
424
425 #[test]
426 fn ccw_ring_correctly_wound_is_a_noop() {
427 let mut r: Ring<P, false> = Ring::from_vec(vec![
433 P::new(1.0, 0.0),
434 P::new(0.0, 1.0),
435 P::new(-1.0, 0.0),
436 P::new(0.0, -1.0),
437 P::new(1.0, 0.0),
438 ]);
439 assert_eq!(ring_area(&r), 2.0, "precondition: correctly wound");
440 correct(&mut r);
441 assert_eq!(ring_area(&r), 2.0, "correct() must be a no-op");
442 }
443
444 #[test]
445 fn ccw_ring_wrongly_wound_is_reversed() {
446 let mut r: Ring<P, false> = Ring::from_vec(vec![
449 P::new(1.0, 0.0),
450 P::new(0.0, -1.0),
451 P::new(-1.0, 0.0),
452 P::new(0.0, 1.0),
453 P::new(1.0, 0.0),
454 ]);
455 assert_eq!(ring_area(&r), -2.0, "precondition: wrongly wound");
456 correct(&mut r);
457 assert_eq!(ring_area(&r), 2.0);
458 }
459
460 #[test]
461 fn ccw_polygon_with_hole_correctly_wound_is_a_noop() {
462 use geometry_model::Polygon;
467 let outer: Ring<P, false> = Ring::from_vec(vec![
468 P::new(0.0, 0.0),
469 P::new(4.0, 0.0),
470 P::new(4.0, 4.0),
471 P::new(0.0, 4.0),
472 P::new(0.0, 0.0),
473 ]);
474 let hole: Ring<P, false> = Ring::from_vec(vec![
475 P::new(1.0, 1.0),
476 P::new(1.0, 2.0),
477 P::new(2.0, 2.0),
478 P::new(2.0, 1.0),
479 P::new(1.0, 1.0),
480 ]);
481 let mut pg: Polygon<P, false> = Polygon::new(outer);
482 pg.inners.push(hole);
483 assert_eq!(ring_area(&pg.outer), 16.0, "precondition: outer CCW-stored");
484 assert_eq!(
485 ring_area(&pg.inners[0]),
486 -1.0,
487 "precondition: hole CW-stored"
488 );
489 correct(&mut pg);
490 assert_eq!(ring_area(&pg.outer), 16.0, "outer must be untouched");
491 assert_eq!(ring_area(&pg.inners[0]), -1.0, "hole must be untouched");
492 }
493
494 #[test]
495 fn ccw_polygon_wrongly_wound_is_fixed() {
496 use geometry_model::Polygon;
499 let outer: Ring<P, false> = Ring::from_vec(vec![
500 P::new(0.0, 0.0),
501 P::new(0.0, 4.0),
502 P::new(4.0, 4.0),
503 P::new(4.0, 0.0),
504 P::new(0.0, 0.0),
505 ]);
506 let hole: Ring<P, false> = Ring::from_vec(vec![
507 P::new(1.0, 1.0),
508 P::new(2.0, 1.0),
509 P::new(2.0, 2.0),
510 P::new(1.0, 2.0),
511 P::new(1.0, 1.0),
512 ]);
513 let mut pg: Polygon<P, false> = Polygon::new(outer);
514 pg.inners.push(hole);
515 assert_eq!(ring_area(&pg.outer), -16.0, "precondition: outer wrong");
516 assert_eq!(ring_area(&pg.inners[0]), 1.0, "precondition: hole wrong");
517 correct(&mut pg);
518 assert_eq!(ring_area(&pg.outer), 16.0);
519 assert_eq!(ring_area(&pg.inners[0]), -1.0);
520 }
521}