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