gistools/geometry/tools/polys/
intersections.rs

1use crate::{
2    data_structures::{BoxIndex, BoxIndexAccessor},
3    geometry::{IntersectionOfSegmentsRobust, intersection_of_segments_robust},
4};
5use alloc::{collections::BTreeMap, rc::Rc, vec, vec::Vec};
6use core::{cell::RefCell, cmp::Ordering};
7use libm::{fmax, fmin};
8use s2json::{BBox, FullXY, GetXY, NewXY, Point};
9
10/// A segment in a polygon
11#[derive(Debug, Clone, Copy, PartialEq)]
12pub struct Segment {
13    /// segment id
14    pub id: usize,
15    /// index in the polys
16    pub poly_index: usize,
17    /// index in the polys[polygon_index]
18    pub ring_index: usize,
19    /// index in the polys[polygon_index][ring_index][from]
20    pub from: usize,
21    /// index in the polys[polygon_index][ring_index][to]
22    pub to: usize,
23    /// Bounding box
24    pub bbox: BBox,
25}
26impl BoxIndexAccessor for Segment {
27    fn bbox(&self) -> BBox {
28        self.bbox
29    }
30}
31
32/// An intersection of two segments
33#[derive(Debug, Clone, Copy, PartialEq)]
34pub struct Intersection {
35    /// The first segment
36    pub segment1: Segment,
37    /// The second segment
38    pub segment2: Segment,
39    /// The intersection
40    pub point: Point,
41    /// The distance along segment1 that the intersection occurs from [0,1]
42    pub u: f64,
43    /// The distance along segment2 that the intersection occurs from [0,1]
44    pub t: f64,
45}
46
47/// Local Intersection to a [poly_index][ring_index]
48#[derive(Debug, Clone, PartialEq)]
49pub struct RingIntersection {
50    /// The index of the ring's start
51    pub from: usize,
52    /// The index of the ring's end
53    pub to: usize,
54    /// The intersection point
55    pub point: Rc<RefCell<Point>>,
56    /// The t value (where the intersection occurs on the line segment from 0->1)
57    pub t: f64,
58    /// The vector from the start to the intersection
59    pub t_vec: Point,
60    /// The angle of the vector from the start to the intersection
61    pub t_angle: f64,
62}
63impl RingIntersection {
64    /// Create a new Intersection
65    pub fn new<P: FullXY>(
66        from: usize,
67        to: usize,
68        point: Rc<RefCell<Point>>,
69        t: f64,
70        t_vec: &P,
71        t_angle: f64,
72    ) -> Self {
73        RingIntersection { from, to, point, t, t_vec: t_vec.into(), t_angle }
74    }
75}
76/// [poly_index][ring_index] -> Intersections
77pub type RingIntersectionLookup = BTreeMap<usize, BTreeMap<usize, Vec<RingIntersection>>>;
78
79/// Find all intersections within a collection of polygons
80///
81/// NOTE: Use [`polygons_intersections_ref`] instead if each polygon is their own ref
82///
83/// ## Parameters
84/// - `polygons`: the collection of polygons
85/// - `include_self_intersections`: whether to include self intersections or not
86///
87/// ## Returns
88/// Found intersections
89pub fn polygons_intersections<P: GetXY + PartialEq>(
90    vector_polygons: &[Vec<Vec<P>>],
91    include_self_intersections: bool,
92) -> Vec<Intersection> {
93    let mut res: Vec<Intersection> = vec![];
94    // build all segments
95    let segments = build_polygon_segments(vector_polygons);
96
97    // setup a 2D box index
98    let box_index = BoxIndex::new(segments.clone(), None);
99
100    // iterate each segment and check for intersections with other segments
101    for segment1 in segments {
102        let potential_intersections = box_index.search(
103            &segment1.bbox(),
104            Some(|seg: &Segment| {
105                seg.id != segment1.id
106                    && seg.id > segment1.id
107                    // if self-intersections are not included skip all segments from the same polyIndex
108                    // otherwise skip all segments from the same ringIndex whose end points interact
109                    && (if !include_self_intersections {
110                            seg.poly_index != segment1.poly_index
111                        } else {
112                            seg.ring_index != segment1.ring_index
113                                || (seg.from != segment1.from
114                                    && seg.to != segment1.to
115                                    && seg.to != segment1.from
116                                    && seg.from != segment1.to)
117                        })
118            }),
119        );
120        for segment2 in potential_intersections {
121            if let Some(int_point) =
122                find_polygon_intersection(vector_polygons, &segment1, &segment2)
123            {
124                let IntersectionOfSegmentsRobust { point, u, t, .. } = int_point;
125                res.push(Intersection { segment1, segment2, point, u, t });
126            }
127        }
128    }
129
130    res
131}
132
133/// Build all segments
134///
135/// ## Parameters
136/// - `vector_polygons`: the collection of polygons
137///
138/// ## Returns
139/// The collection of segments
140pub fn build_polygon_segments<P: GetXY>(vector_polygons: &[Vec<Vec<P>>]) -> Vec<Segment> {
141    let mut segments = vec![];
142
143    for (p, polygon) in vector_polygons.iter().enumerate() {
144        for (r, ring) in polygon.iter().enumerate() {
145            for s in 0..ring.len() - 1 {
146                let from = &ring[s];
147                let to = &ring[s + 1];
148                segments.push(Segment {
149                    id: segments.len(),
150                    poly_index: p,
151                    ring_index: r,
152                    from: s,
153                    to: s + 1,
154                    bbox: BBox::new(
155                        fmin(from.x(), to.x()),
156                        fmin(from.y(), to.y()),
157                        fmax(from.x(), to.x()),
158                        fmax(from.y(), to.y()),
159                    ),
160                });
161            }
162        }
163    }
164
165    segments
166}
167
168/// Run through the vector_polygons and Builds the ring intersection lookup
169///
170/// ## Parameters
171/// - `vector_polygons`: the collection of polygons
172///
173/// ## Returns
174/// The ring intersection lookup for all rings in the multipolygon collection
175pub fn polygons_intersections_lookup<P: FullXY>(
176    vector_polygons: &[Vec<Vec<P>>],
177    segment_filter: Option<impl Fn(&Segment, &Segment) -> bool>,
178) -> RingIntersectionLookup {
179    let segments = build_polygon_segments(vector_polygons);
180    let mut ring_intersect_lookup: RingIntersectionLookup = BTreeMap::new();
181
182    // setup a 2D box index
183    let box_index = BoxIndex::new(segments.clone(), None);
184    // iterate each segment and check for intersections with other segments
185    for seg1 in segments {
186        let potential_intersections = box_index.search(
187            &seg1.bbox(),
188            Some(|seg2: &Segment| {
189                segment_filter.as_ref().map(|f| f(&seg1, seg2)).unwrap_or_else(|| {
190                    // if same id ignore
191                    seg2.id != seg1.id &&
192                    // only pass forward not backward
193                    seg2.id > seg1.id &&
194                    // if same poly_index ignore
195                    seg2.poly_index != seg1.poly_index
196                })
197            }),
198        );
199        for seg2 in potential_intersections {
200            let p_int = find_polygon_intersection::<P, P>(vector_polygons, &seg1, &seg2);
201            // ignore points that interact tangentially or precisely at an existing edge or vertex.
202            if let Some(int) = p_int {
203                let IntersectionOfSegmentsRobust { u, t, point, u_angle, t_angle, u_vec, t_vec } =
204                    int;
205                // skip if u and t are equal
206                if u == t && (u == 0. || u == 1.) {
207                    continue;
208                }
209                let p = Rc::new(RefCell::new((&point).into()));
210                // first segment intersection
211                let s1 = ring_intersect_lookup
212                    .entry(seg1.poly_index)
213                    .or_default()
214                    .entry(seg1.ring_index)
215                    .or_default();
216                s1.push(RingIntersection::new(seg1.from, seg1.to, p.clone(), u, &u_vec, u_angle));
217                // second segment intersection
218                let s2 = ring_intersect_lookup
219                    .entry(seg2.poly_index)
220                    .or_default()
221                    .entry(seg2.ring_index)
222                    .or_default();
223                s2.push(RingIntersection::new(seg2.from, seg2.to, p, t, &t_vec, t_angle));
224            }
225        }
226    }
227
228    // finally clean the intersections before return
229    for (_, polys) in ring_intersect_lookup.iter_mut() {
230        for (_, intersections) in polys.iter_mut() {
231            *intersections = clean_intersections(intersections);
232        }
233    }
234
235    ring_intersect_lookup
236}
237
238/// Find the intersection of two segments if it exists
239///
240/// ## Parameters
241/// - `vector_polygons`: the collection of polygons
242/// - `segment1`: the first segment
243/// - `segment2`: the second segment
244///
245/// ## Returns
246/// The intersection if it exists. Undefined otherwise.
247pub fn find_polygon_intersection<P: GetXY + PartialEq, Q: NewXY + Clone>(
248    vector_polygons: &[Vec<Vec<P>>],
249    segment1: &Segment,
250    segment2: &Segment,
251) -> Option<IntersectionOfSegmentsRobust<Q>> {
252    let p1 = &vector_polygons[segment1.poly_index][segment1.ring_index][segment1.from];
253    let p2 = &vector_polygons[segment1.poly_index][segment1.ring_index][segment1.to];
254    let q1 = &vector_polygons[segment2.poly_index][segment2.ring_index][segment2.from];
255    let q2 = &vector_polygons[segment2.poly_index][segment2.ring_index][segment2.to];
256    intersection_of_segments_robust(
257        (p1, p2),
258        (q1, q2),
259        segment1.poly_index == segment2.poly_index && segment1.ring_index == segment2.ring_index,
260    )
261}
262
263/// Find all intersections within a collection of polygons where each polygon is their own ref.
264///
265/// NOTE: Use [`polygons_intersections`] instead if you are referencing a full VectorMultiPolygon instead
266///
267/// ## Parameters
268/// - `polygons`: the collection of polygons
269/// - `include_self_intersections`: whether to include self intersections or not
270///
271/// ## Returns
272/// Found intersections
273pub fn polygons_intersections_ref<P: GetXY + PartialEq>(
274    vector_polygons: &[&Vec<Vec<P>>],
275    include_self_intersections: bool,
276) -> Vec<Intersection> {
277    let mut res: Vec<Intersection> = vec![];
278    // build all segments
279    let segments = build_polygon_segments_ref(vector_polygons);
280
281    // setup a 2D box index
282    let box_index = BoxIndex::new(segments.clone(), None);
283
284    // iterate each segment and check for intersections with other segments
285    for segment1 in segments {
286        let potential_intersections = box_index.search(
287            &segment1.bbox(),
288            Some(|seg: &Segment| {
289                seg.id != segment1.id
290                    && seg.id > segment1.id
291                    // if self-intersections are not included skip all segments from the same polyIndex
292                    // otherwise skip all segments from the same ringIndex whose end points interact
293                    && (if !include_self_intersections {
294                            seg.poly_index != segment1.poly_index
295                        } else {
296                            seg.ring_index != segment1.ring_index
297                                || (seg.from != segment1.from
298                                    && seg.to != segment1.to
299                                    && seg.to != segment1.from
300                                    && seg.from != segment1.to)
301                        })
302            }),
303        );
304        for segment2 in potential_intersections {
305            if let Some(int_point) = find_intersection_ref(vector_polygons, &segment1, &segment2) {
306                res.push(Intersection {
307                    segment1,
308                    segment2,
309                    point: int_point.point,
310                    u: int_point.u,
311                    t: int_point.t,
312                });
313            }
314        }
315    }
316
317    res
318}
319
320/// Build all segments
321///
322/// ## Parameters
323/// - `vector_polygons`: the collection of polygons
324///
325/// ## Returns
326/// The collection of segments
327pub fn build_polygon_segments_ref<P: GetXY>(vector_polygons: &[&Vec<Vec<P>>]) -> Vec<Segment> {
328    let mut segments = vec![];
329
330    for (p, polygon) in vector_polygons.iter().enumerate() {
331        for (r, ring) in polygon.iter().enumerate() {
332            for s in 0..ring.len() - 1 {
333                let from = &ring[s];
334                let to = &ring[s + 1];
335                segments.push(Segment {
336                    id: segments.len(),
337                    poly_index: p,
338                    ring_index: r,
339                    from: s,
340                    to: s + 1,
341                    bbox: BBox::new(
342                        fmin(from.x(), to.x()),
343                        fmin(from.y(), to.y()),
344                        fmax(from.x(), to.x()),
345                        fmax(from.y(), to.y()),
346                    ),
347                });
348            }
349        }
350    }
351
352    segments
353}
354
355/// Find the intersection of two segments if it exists
356///
357/// ## Parameters
358/// - `vector_polygons`: the collection of polygons
359/// - `segment1`: the first segment
360/// - `segment2`: the second segment
361///
362/// ## Returns
363/// The intersection if it exists. Undefined otherwise.
364fn find_intersection_ref<P: GetXY + PartialEq, Q: NewXY + Clone>(
365    vector_polygons: &[&Vec<Vec<P>>],
366    segment1: &Segment,
367    segment2: &Segment,
368) -> Option<IntersectionOfSegmentsRobust<Q>> {
369    let p1 = &vector_polygons[segment1.poly_index][segment1.ring_index][segment1.from];
370    let p2 = &vector_polygons[segment1.poly_index][segment1.ring_index][segment1.to];
371    let q1 = &vector_polygons[segment2.poly_index][segment2.ring_index][segment2.from];
372    let q2 = &vector_polygons[segment2.poly_index][segment2.ring_index][segment2.to];
373    intersection_of_segments_robust(
374        (p1, p2),
375        (q1, q2),
376        segment1.poly_index == segment2.poly_index && segment1.ring_index == segment2.ring_index,
377    )
378}
379
380/// Given a ring's of intersections, clean them up
381///
382/// ## Parameters
383/// - `intersections`: a collection of intersections to clean up
384///
385/// ## Returns
386/// The cleaned up intersections
387fn clean_intersections(intersections: &mut [RingIntersection]) -> Vec<RingIntersection> {
388    if intersections.is_empty() {
389        return vec![];
390    }
391    intersections
392        .sort_by(|a, b| a.from.cmp(&b.from).then(a.t.partial_cmp(&b.t).unwrap_or(Ordering::Equal)));
393
394    // 1) Remove duplicates
395    let mut dedup_ints: Vec<RingIntersection> = vec![];
396    for int in intersections.iter() {
397        if dedup_ints.iter().any(|c| c.from == int.from && c.t == int.t && c.point == int.point) {
398            continue;
399        }
400
401        dedup_ints.push(int.clone());
402    }
403    // 2) Cancel out any intersections with other rings we only touch once with a single point
404    if dedup_ints.len() == 2 {
405        let first = &dedup_ints[0];
406        let second = &dedup_ints[1];
407        if (first.t == 0. || first.t == 1.)
408            && (second.t == 0. || second.t == 1.)
409            && first.point == second.point
410        {
411            return vec![];
412        }
413    }
414    // 3) Intersections whose t values are not 0 or 1 but are equal to the start, end, or other
415    // intersections with different t values need to be shifted by the smallest float possible to ensure
416    // it doesn't conflict but on the line segment.
417    update_intersection_points(&mut dedup_ints);
418
419    dedup_ints
420}
421
422/// Update all intersection points to ensure they are not equal to the start or end points if their t
423/// values are not 0 or 1.
424///
425/// When there is an intersection that the resultant point is equal to one of the segment edges,
426/// then we shift the point by the smallest float possible.
427///
428/// NOTE: If we have two or more points that are equal to one of the segment edges BUT the t values
429/// are barely different, we need to keep shifting forward as needed.
430///
431/// NOTE: What if we have TWO points that are equal to one of the segment edges BUT the t values
432/// are different? We need to shift again as needed. There are also cases where two different lines
433/// intersect another line and the resultant intersection is the same point but the t value along
434/// the line is different.
435///
436/// ## Parameters
437/// - `intersections`: the collection of intersections
438fn update_intersection_points(intersections: &mut [RingIntersection]) {
439    let mut starts = vec![];
440    let mut ends = vec![];
441    for i in 1..intersections.len() {
442        let int = &intersections[i];
443        let prev = &intersections[i - 1];
444        if int.from != prev.from || int.t == 0. || int.t == 1. || prev.t == 0. || prev.t == 1. {
445            continue;
446        }
447        if i != 0 && int.point == prev.point && int.t != prev.t {
448            // because they are sorted by t, starts we want to inc "forward" the NEXT one; ends we want to dec "back" the PREVIOUS one
449            if int.t <= 0.5 {
450                starts.push(i);
451            } else {
452                ends.push(i - 1);
453            }
454        }
455    }
456    // Choose direction as further away from the end point it's closer to.
457    for (i, start) in starts.into_iter().enumerate() {
458        let RingIntersection { point, t_vec, .. } = &mut intersections[start];
459        let point = &mut point.borrow_mut();
460        if t_vec.0 != 0.0 {
461            (0..=(i + 1)).for_each(|_| {
462                point.0 = if t_vec.0 > 0.0 { point.0.next_up() } else { point.0.next_down() }
463            });
464        }
465        if t_vec.1 != 0.0 {
466            (0..=(i + 1)).for_each(|_| {
467                point.1 = if t_vec.1 > 0.0 { point.1.next_up() } else { point.1.next_down() }
468            });
469        }
470    }
471    for (i, end) in ends.into_iter().enumerate() {
472        let RingIntersection { point, t_vec, .. } = &mut intersections[end];
473        let point = &mut point.borrow_mut();
474        if t_vec.0 != 0.0 {
475            (0..=(i + 1)).for_each(|_| {
476                point.0 = if t_vec.0 < 0.0 { point.0.next_up() } else { point.0.next_down() }
477            });
478        }
479        if t_vec.1 != 0.0 {
480            (0..=(i + 1)).for_each(|_| {
481                point.1 = if t_vec.1 < 0.0 { point.1.next_up() } else { point.1.next_down() }
482            });
483        }
484    }
485}