gistools/geometry/tools/lines/
intersections.rs

1use crate::geometry::orient2d;
2use alloc::fmt::Debug;
3use libm::atan2;
4use s2json::{GetXY, NewXY, Point};
5
6/// An intersection of two segments
7/// u and t are where the intersection occurs
8#[derive(Debug, Clone, Copy, PartialEq)]
9pub struct IntersectionOfSegments<Q: NewXY> {
10    /// the point of intersection
11    pub point: Q,
12    /// where along the first segment the intersection occurs
13    pub u: f64,
14    /// where along the second segment the intersection occurs
15    pub t: f64,
16}
17
18/// An intersection of two segments including displacement vectors
19/// u and t are where the intersection occurs
20#[derive(Debug, Clone, Copy, PartialEq)]
21pub struct IntersectionOfSegmentsRobust<Q: NewXY> {
22    /// the point of intersection
23    pub point: Q,
24    /// where along the first segment the intersection occurs
25    pub u: f64,
26    /// where along the second segment the intersection occurs
27    pub t: f64,
28    /// displacement vector from the first segment
29    pub u_vec: Q,
30    /// displacement vector from the second segment
31    pub t_vec: Q,
32    /// Absolute angle of segment 'a' in radians [-PI, PI]
33    pub u_angle: f64,
34    /// Absolute angle of segment 'b' in radians [-PI, PI]
35    pub t_angle: f64,
36}
37impl<Q: NewXY> IntersectionOfSegmentsRobust<Q> {
38    /// Create a new IntersectionOfSegmentsRobust
39    #[allow(clippy::too_many_arguments)]
40    pub fn new(
41        x: f64,
42        y: f64,
43        u: f64,
44        t: f64,
45        u_vec: Q,
46        t_vec: Q,
47        u_angle: f64,
48        t_angle: f64,
49    ) -> Self {
50        Self { point: Q::new_xy(x, y), u, t, u_vec, t_vec, u_angle, t_angle }
51    }
52}
53
54/// Find the intersection of two segments
55///
56/// NOTE: Segments that are only touching eachothers endpoints are considered intersections
57///
58/// ## Parameters
59/// - `a`: the first segment
60/// - `b`: the second segment
61///
62/// ## Returns
63/// A point if the segments intersect where the intersection occurs, otherwise undefined
64pub fn intersection_of_segments<P: GetXY, Q: NewXY>(
65    a: (&P, &P),
66    b: (&P, &P),
67) -> Option<IntersectionOfSegments<Q>> {
68    let (p, p2) = a;
69    let (q, q2) = b;
70
71    let r = Point::new_xy(p2.x() - p.x(), p2.y() - p.y());
72    let s = Point::new_xy(q2.x() - q.x(), q2.y() - q.y());
73
74    let cross = r.x() * s.y() - r.y() * s.x();
75    if cross == 0. {
76        return None;
77    }
78
79    let u = ((q.x() - p.x()) * s.y() - (q.y() - p.y()) * s.x()) / cross;
80    let t = ((q.x() - p.x()) * r.y() - (q.y() - p.y()) * r.x()) / cross;
81
82    if (0. ..=1.).contains(&t) && (0. ..=1.).contains(&u) {
83        Some(IntersectionOfSegments {
84            point: Q::new_xy(p.x() + u * r.x(), p.y() + u * r.y()),
85            u,
86            t,
87        })
88    } else {
89        None
90    }
91}
92
93/// Find the intersection of two segments. A more robust approach that uses predicates to ensure no
94/// false positives/negatives
95///
96/// NOTE:
97/// If the segments are touching at end points, they PASS in this function. However, the caviat is
98/// that if the segments are coming from the same ring, then the result will be undefined (not
99/// considered an intersection).
100///
101/// NOTE:
102/// The resultant vectors are displacement vectors not normalized.
103///
104/// ## Parameters
105/// - `a`: the first segment
106/// - `b`: the second segment
107/// - `same_ring`: if both segments are from the same ring. By default it assumes they are
108/// - `a_ring_id`: the ring id of the first segment if provided
109/// - `b_ring_id`: the ring id of the second segment if provided
110///
111/// ## Returns
112/// A point if the segments intersect where the intersection occurs, otherwise undefined
113pub fn intersection_of_segments_robust<P: GetXY + PartialEq, Q: NewXY + Clone>(
114    a: (&P, &P),
115    b: (&P, &P),
116    same_ring: bool,
117) -> Option<IntersectionOfSegmentsRobust<Q>> {
118    let (x1, y1) = a.0.xy();
119    let (x2, y2) = a.1.xy();
120    let (x3, y3) = b.0.xy();
121    let (x4, y4) = b.1.xy();
122    let (dx_a, dy_a) = (x2 - x1, y2 - y1);
123    let (dx_b, dy_b) = (x4 - x3, y4 - y3);
124    let (dx_c, dy_c) = (x1 - x3, y1 - y3);
125
126    // corner case: A segment has 0 length
127    if (dx_a == 0. && dy_a == 0.) || (dx_b == 0. && dy_b == 0.) {
128        return None;
129    }
130
131    // Handle zero-length segments specifically (atan2(0,0) is 0, but good to be explicit)
132    let u_angle = if dx_a == 0. && dy_a == 0. { 0. } else { atan2(dy_a, dx_a) };
133    let t_angle = if dx_b == 0. && dy_b == 0. { 0. } else { atan2(dy_b, dx_b) };
134
135    if same_ring {
136        if a.1 == b.0 || a.1 == b.1 || a.0 == b.0 || a.0 == b.1 {
137            return None;
138        }
139    } else {
140        let zero = Q::new_xy(0., 0.);
141        if a.1 == b.0 {
142            let u_vec = Q::new_xy(dx_a, dy_a);
143            return Some(IntersectionOfSegmentsRobust::new(
144                x2, y2, 1., 0., u_vec, zero, u_angle, t_angle,
145            ));
146        }
147        if a.1 == b.1 {
148            let u_vec = Q::new_xy(dx_a, dy_a);
149            let t_vec = Q::new_xy(dx_b, dy_b);
150            return Some(IntersectionOfSegmentsRobust::new(
151                x2, y2, 1., 1., u_vec, t_vec, u_angle, t_angle,
152            ));
153        }
154        if a.0 == b.0 {
155            let zero_2 = zero.clone();
156            return Some(IntersectionOfSegmentsRobust::new(
157                x1, y1, 0., 0., zero, zero_2, u_angle, t_angle,
158            ));
159        }
160        if a.0 == b.1 {
161            let t_vec = Q::new_xy(dx_b, dy_b);
162            return Some(IntersectionOfSegmentsRobust::new(
163                x1, y1, 0., 1., zero, t_vec, u_angle, t_angle,
164            ));
165        }
166    }
167
168    // build numerators and denominator. Extrapolate vectors from them
169    let denom = dy_b * dx_a - dx_b * dy_a;
170    if denom == 0. {
171        return None;
172    }
173    let orient1 = orient2d(x1, y1, x2, y2, x3, y3);
174    let orient2 = orient2d(x1, y1, x2, y2, x4, y4);
175    if (orient1 > 0. && orient2 > 0.) || (orient1 < 0. && orient2 < 0.) {
176        return None;
177    }
178
179    // build vectors and angles, then normalize the vectors
180    let nume_a = dx_b * dy_c - dy_b * dx_c;
181    let nume_b = dx_a * dy_c - dy_a * dx_c;
182    let u_a = nume_a / denom;
183    let u_b = nume_b / denom;
184
185    if (0. ..=1.).contains(&u_a) && (0. ..=1.).contains(&u_b) {
186        let mut x = x1 + u_a * dx_a;
187        let mut y = y1 + u_a * dy_a;
188        let u_vec = Q::new_xy(u_a * dx_a, u_a * dy_a);
189        let t_vec = Q::new_xy(u_b * dx_b, u_b * dy_b);
190        // If the intersection is at one of the endpoints becauase of float errors, move it to towards
191        // the other endpoint by the smallest amount possible
192        if u_a != 0. && u_a != 1. && u_b != 0. && u_b != 1. {
193            if u_a <= 0.5 && x == x1 && y == y1 {
194                if dx_a != 0. {
195                    x = if dx_a > 0. { x.next_up() } else { x.next_down() };
196                }
197                if dy_a != 0. {
198                    y = if dy_a > 0. { y.next_up() } else { y.next_down() };
199                }
200            } else if u_a > 0.5 && x == x2 && y == y2 {
201                if dx_a != 0. {
202                    x = if dx_a < 0. { x.next_up() } else { x.next_down() };
203                }
204                if dy_a != 0. {
205                    y = if dy_a < 0. { y.next_up() } else { y.next_down() };
206                }
207            } else if u_b <= 0.5 && x == x3 && y == y3 {
208                if dx_b != 0. {
209                    x = if dx_b > 0. { x.next_up() } else { x.next_down() };
210                }
211                if dy_b != 0. {
212                    y = if dy_b > 0. { y.next_up() } else { y.next_down() };
213                }
214            } else if u_b > 0.5 && x == x4 && y == y4 {
215                if dx_b != 0. {
216                    x = if dx_b < 0. { x.next_up() } else { x.next_down() };
217                }
218                if dy_b != 0. {
219                    y = if dy_b < 0. { y.next_up() } else { y.next_down() };
220                }
221            }
222        }
223        Some(IntersectionOfSegmentsRobust::new(x, y, u_a, u_b, u_vec, t_vec, u_angle, t_angle))
224    } else {
225        None
226    }
227}