geo/algorithm/sweep/
segment.rs

1use super::*;
2use crate::GeoFloat;
3use std::{cmp::Ordering, fmt::Debug};
4
5/// A segment of input [`LineOrPoint`] generated during the sweep.
6#[derive(Clone)]
7pub(super) struct Segment<C: Cross> {
8    pub(super) geom: LineOrPoint<C::Scalar>,
9    pub(super) cross: C,
10    pub(super) first_segment: bool,
11    pub(super) left_event_done: bool,
12    pub(super) overlapping: Option<IMSegment<C>>,
13    pub(super) is_overlapping: bool,
14}
15
16impl<C: Cross> Segment<C> {
17    pub fn new(cross: C, geom: Option<LineOrPoint<C::Scalar>>) -> Self {
18        let first_segment = geom.is_none();
19        let geom = geom.unwrap_or_else(|| cross.line());
20        Self {
21            geom,
22            cross,
23            first_segment,
24            left_event_done: false,
25            overlapping: None,
26            is_overlapping: false,
27        }
28    }
29
30    /// Split a line segment into pieces at points of intersection.
31    ///
32    /// The initial segment is mutated in place, and extra-segment(s) are
33    /// returned if any. Assume exact arithmetic, the ordering of self should
34    /// remain the same among active segments. However, with finite-precision,
35    /// this may not be the case.
36    pub fn adjust_for_intersection(
37        &mut self,
38        intersection: LineOrPoint<C::Scalar>,
39    ) -> SplitSegments<C::Scalar> {
40        use SplitSegments::*;
41
42        // We only support splitting a line segment.
43        debug_assert!(self.geom.is_line());
44        let (p, q) = self.geom.end_points();
45
46        if !intersection.is_line() {
47            // Handle point intersection
48            let r = intersection.left();
49            debug_assert!(
50                p <= r,
51                "intersection point was not ordered within the line: {p:?} <= {r:?} <=> {q:?}",
52            );
53            if p == r || q == r {
54                // If the intersection is at the end point, the
55                // segment doesn't need to be split.
56                Unchanged { overlap: false }
57            } else {
58                // Otherwise, split it. Mutate `self` to be the
59                // first part, and return the second part.
60                self.geom = (p, r).into();
61                // self.first_segment = false;
62                SplitOnce {
63                    overlap: None,
64                    right: (r, q).into(),
65                }
66            }
67        } else {
68            let (r1, r2) = intersection.end_points();
69            debug_assert!(
70                p <= r1 && r2 <= q,
71                "overlapping segment was not ordered within the line!"
72            );
73            if p == r1 {
74                if r2 == q {
75                    // The whole segment overlaps.
76                    Unchanged { overlap: true }
77                } else {
78                    self.geom = (p, r2).into();
79                    // self.first_segment = false;
80                    SplitOnce {
81                        overlap: Some(false),
82                        right: (r2, q).into(),
83                    }
84                }
85            } else if r2 == q {
86                self.geom = (p, r1).into();
87                // self.first_segment = false;
88                SplitOnce {
89                    overlap: Some(true),
90                    right: (r1, q).into(),
91                }
92            } else {
93                self.geom = (p, r1).into();
94                // self.first_segment = false;
95                SplitTwice {
96                    right: (r2, q).into(),
97                }
98            }
99        }
100    }
101}
102
103/// A more concise debug impl.
104impl<C: Cross> Debug for Segment<C> {
105    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106        write!(
107            f,
108            "Segment{{ {geom:?}\n\tof {c:?}\n\t{first} [{has}/{ovl}] }}",
109            c = self.cross,
110            geom = self.geom,
111            first = if self.first_segment { "[1st]" } else { "" },
112            has = if self.overlapping.is_some() {
113                "HAS"
114            } else {
115                "NON"
116            },
117            ovl = if self.is_overlapping { "OVL" } else { "NON" },
118        )
119    }
120}
121
122/// Partial equality based on key.
123///
124/// This is consistent with the `PartialOrd` impl.
125impl<C: Cross> PartialEq for Segment<C> {
126    #[inline]
127    fn eq(&self, other: &Self) -> bool {
128        self.partial_cmp(other) == Some(Ordering::Equal)
129    }
130}
131
132/// Partial ordering defined as per algorithm.
133///
134/// This is requires the same pre-conditions as for [`LineOrPoint`].
135impl<C: Cross> PartialOrd for Segment<C> {
136    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
137        self.geom.partial_cmp(&other.geom)
138    }
139}
140
141/// Stores the type of split and extra geometries from adjusting a
142/// segment for intersection.
143#[derive(Debug)]
144pub(super) enum SplitSegments<T: GeoFloat> {
145    Unchanged {
146        overlap: bool,
147    },
148    SplitOnce {
149        overlap: Option<bool>,
150        right: LineOrPoint<T>,
151    },
152    SplitTwice {
153        right: LineOrPoint<T>,
154    },
155}
156
157#[cfg(test)]
158mod tests {
159
160    use super::*;
161
162    impl<T: GeoFloat> PartialEq for SplitSegments<T> {
163        fn eq(&self, other: &Self) -> bool {
164            match (self, other) {
165                (
166                    Self::Unchanged { overlap: l_overlap },
167                    Self::Unchanged { overlap: r_overlap },
168                ) => l_overlap == r_overlap,
169                (
170                    Self::SplitOnce {
171                        overlap: l_overlap,
172                        right: l_right,
173                    },
174                    Self::SplitOnce {
175                        overlap: r_overlap,
176                        right: r_right,
177                    },
178                ) => l_overlap == r_overlap && l_right.coords_equal(r_right),
179                (Self::SplitTwice { right: l_right }, Self::SplitTwice { right: r_right }) => {
180                    l_right.coords_equal(r_right)
181                }
182                _ => false,
183            }
184        }
185    }
186
187    #[test]
188    fn test_split() {
189        let lines: Vec<_> = vec![
190            LineOrPoint::from(((0., 0.).into(), (10., 10.).into())),
191            ((10.0, 0.).into(), (0., 10.).into()).into(),
192            ((0., 0.).into(), (0., 10.).into()).into(),
193            ((0., 0.).into(), (5., 5.).into()).into(),
194            ((10., 10.).into(), (5., 5.).into()).into(),
195        ]
196        .into_iter()
197        .map(|lp| Segment::new(lp, None))
198        .collect();
199
200        struct TestCase {
201            a: usize,
202            b: usize,
203            isec: Option<LineOrPoint<f64>>,
204            split: Option<SplitSegments<f64>>,
205        }
206
207        impl TestCase {
208            fn assert_equality(&self, lines: &[Segment<LineOrPoint<f64>>]) {
209                let isec = lines[self.a]
210                    .geom
211                    .intersect_line_ordered(&lines[self.b].geom);
212                assert_eq!(isec, self.isec);
213
214                if isec.is_none() {
215                    return;
216                }
217                let isec = isec.unwrap();
218                let mut copy_seg = lines[self.a].clone();
219                let split = copy_seg.adjust_for_intersection(isec);
220                assert_eq!(&split, self.split.as_ref().unwrap(),)
221            }
222        }
223
224        let test_cases = vec![
225            TestCase {
226                a: 0,
227                b: 0,
228                isec: Some(lines[0].geom),
229                split: Some(SplitSegments::Unchanged { overlap: true }),
230            },
231            TestCase {
232                a: 0,
233                b: 1,
234                isec: Some(LineOrPoint::from(SweepPoint::from((5., 5.)))),
235                split: Some(SplitSegments::SplitOnce {
236                    overlap: None,
237                    right: LineOrPoint::from(((5., 5.).into(), (10., 10.).into())),
238                }),
239            },
240            TestCase {
241                a: 0,
242                b: 2,
243                isec: Some(LineOrPoint::from(SweepPoint::from((0., 0.)))),
244                split: Some(SplitSegments::Unchanged { overlap: false }),
245            },
246            TestCase {
247                a: 0,
248                b: 3,
249                isec: Some(LineOrPoint::from(((0., 0.).into(), (5., 5.).into()))),
250                split: Some(SplitSegments::SplitOnce {
251                    overlap: Some(false),
252                    right: LineOrPoint::from(((5., 5.).into(), (10., 10.).into())),
253                }),
254            },
255            TestCase {
256                a: 0,
257                b: 4,
258                isec: Some(LineOrPoint::from(((5., 5.).into(), (10., 10.).into()))),
259                split: Some(SplitSegments::SplitOnce {
260                    overlap: Some(true),
261                    right: LineOrPoint::from(((5., 5.).into(), (10., 10.).into())),
262                }),
263            },
264        ];
265
266        test_cases.iter().for_each(|t| t.assert_equality(&lines));
267    }
268}