1extern crate alloc;
42
43use geometry_coords::CoordinateScalar;
44use geometry_cs::{CartesianFamily, CoordinateSystem};
45use geometry_tag::{LinestringTag, PointTag, PolygonTag, SameAs, SegmentTag};
46use geometry_trait::{
47 Geometry, Linestring as LinestringTrait, Point as PointTrait, Polygon as PolygonTrait,
48 Ring as RingTrait, Segment as SegmentTrait, segment_end, segment_start,
49};
50
51use crate::within::{WithinPoly, WithinStrategy};
52
53pub use crate::reversal::Reversed;
54
55pub trait IntersectsStrategy<A, B> {
63 fn intersects(&self, a: &A, b: &B) -> bool;
65}
66
67#[derive(Debug, Default, Clone, Copy)]
76pub struct CartesianIntersects;
77
78impl<A, B> IntersectsStrategy<A, B> for CartesianIntersects
79where
80 A: Geometry,
81 B: Geometry,
82 A::Kind: IntersectsPairStrategy<B::Kind>,
83 <A::Kind as IntersectsPairStrategy<B::Kind>>::S: IntersectsStrategy<A, B>,
84{
85 #[inline]
86 fn intersects(&self, a: &A, b: &B) -> bool {
87 <<A::Kind as IntersectsPairStrategy<B::Kind>>::S as Default>::default().intersects(a, b)
88 }
89}
90
91#[derive(Debug, Default, Clone, Copy)]
101pub struct IxPointPoint;
102#[derive(Debug, Default, Clone, Copy)]
104pub struct IxPointSegment;
105#[derive(Debug, Default, Clone, Copy)]
107pub struct IxSegmentSegment;
108#[derive(Debug, Default, Clone, Copy)]
110pub struct IxLinestringSegment;
111#[derive(Debug, Default, Clone, Copy)]
113pub struct IxLinestringLinestring;
114#[derive(Debug, Default, Clone, Copy)]
116pub struct IxPointPolygon;
117#[derive(Debug, Default, Clone, Copy)]
119pub struct IxLinestringPolygon;
120#[derive(Debug, Default, Clone, Copy)]
122pub struct IxPolygonPolygon;
123#[derive(Debug, Default, Clone, Copy)]
125pub struct IxSegmentPoint;
126#[derive(Debug, Default, Clone, Copy)]
129pub struct IxSegmentLinestring;
130#[derive(Debug, Default, Clone, Copy)]
132pub struct IxPolygonPoint;
133#[derive(Debug, Default, Clone, Copy)]
136pub struct IxPolygonLinestring;
137
138impl<A, B> IntersectsStrategy<A, B> for IxPointPoint
145where
146 A: PointTrait,
147 B: PointTrait<Scalar = A::Scalar>,
148 <A::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
149{
150 #[inline]
151 fn intersects(&self, a: &A, b: &B) -> bool {
152 points_equal::<A, B>(a, b, A::DIM)
153 }
154}
155
156impl<P, S> IntersectsStrategy<P, S> for IxPointSegment
162where
163 P: PointTrait,
164 S: SegmentTrait<Point = P>,
165 P: geometry_trait::PointMut + Default,
166 P::Scalar: CoordinateScalar,
167 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
168{
169 #[inline]
170 fn intersects(&self, p: &P, s: &S) -> bool {
171 point_on_segment(p, &segment_start(s), &segment_end(s))
172 }
173}
174
175impl<A, B, P> IntersectsStrategy<A, B> for IxSegmentSegment
178where
179 A: SegmentTrait<Point = P>,
180 B: SegmentTrait<Point = P>,
181 P: PointTrait + geometry_trait::PointMut + Default,
182 P::Scalar: CoordinateScalar,
183 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
184{
185 #[inline]
186 fn intersects(&self, a: &A, b: &B) -> bool {
187 segments_intersect(
188 &segment_start(a),
189 &segment_end(a),
190 &segment_start(b),
191 &segment_end(b),
192 )
193 }
194}
195
196impl<L, S, P> IntersectsStrategy<L, S> for IxLinestringSegment
199where
200 L: LinestringTrait<Point = P>,
201 S: SegmentTrait<Point = P>,
202 P: PointTrait + geometry_trait::PointMut + Default,
203 P::Scalar: CoordinateScalar,
204 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
205{
206 #[inline]
207 fn intersects(&self, ls: &L, s: &S) -> bool {
208 let s1 = segment_start(s);
209 let s2 = segment_end(s);
210 let mut it = ls.points();
211 let Some(mut prev) = it.next() else {
212 return false;
213 };
214 for curr in it {
215 if segments_intersect(prev, curr, &s1, &s2) {
216 return true;
217 }
218 prev = curr;
219 }
220 false
221 }
222}
223
224impl<A, B, P> IntersectsStrategy<A, B> for IxLinestringLinestring
227where
228 A: LinestringTrait<Point = P>,
229 B: LinestringTrait<Point = P>,
230 P: PointTrait,
231 P::Scalar: CoordinateScalar,
232 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
233{
234 #[inline]
235 fn intersects(&self, a: &A, b: &B) -> bool {
236 let mut ia = a.points();
237 let Some(mut pa) = ia.next() else {
238 return false;
239 };
240 for qa in ia {
241 let mut ib = b.points();
242 let Some(mut pb) = ib.next() else {
243 return false;
244 };
245 for qb in ib {
246 if segments_intersect(pa, qa, pb, qb) {
247 return true;
248 }
249 pb = qb;
250 }
251 pa = qa;
252 }
253 false
254 }
255}
256
257impl<P, G> IntersectsStrategy<P, G> for IxPointPolygon
263where
264 P: PointTrait,
265 G: PolygonTrait<Point = P>,
266 P::Scalar: CoordinateScalar,
267 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
268{
269 #[inline]
270 fn intersects(&self, p: &P, pg: &G) -> bool {
271 WithinPoly.covered_by(p, pg)
272 }
273}
274
275impl<L, G, P> IntersectsStrategy<L, G> for IxLinestringPolygon
278where
279 L: LinestringTrait<Point = P>,
280 G: PolygonTrait<Point = P>,
281 P: PointTrait,
282 P::Scalar: CoordinateScalar,
283 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
284{
285 fn intersects(&self, ls: &L, pg: &G) -> bool {
286 let Some(first) = ls.points().next() else {
290 return false;
291 };
292 if WithinPoly.covered_by(first, pg) {
293 return true;
294 }
295 if linestring_crosses_ring(ls, pg.exterior()) {
297 return true;
298 }
299 for hole in pg.interiors() {
300 if linestring_crosses_ring(ls, hole) {
301 return true;
302 }
303 }
304 false
305 }
306}
307
308impl<A, B, P> IntersectsStrategy<A, B> for IxPolygonPolygon
311where
312 A: PolygonTrait<Point = P>,
313 B: PolygonTrait<Point = P>,
314 P: PointTrait,
315 P::Scalar: CoordinateScalar,
316 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
317{
318 fn intersects(&self, a: &A, b: &B) -> bool {
319 if let Some(v) = a.exterior().points().next() {
321 if WithinPoly.covered_by(v, b) {
322 return true;
323 }
324 }
325 if let Some(v) = b.exterior().points().next() {
326 if WithinPoly.covered_by(v, a) {
327 return true;
328 }
329 }
330 if rings_cross(a.exterior(), b.exterior()) {
332 return true;
333 }
334 for hole in a.interiors() {
335 if rings_cross(hole, b.exterior()) {
336 return true;
337 }
338 }
339 for hole in b.interiors() {
340 if rings_cross(a.exterior(), hole) {
341 return true;
342 }
343 }
344 false
345 }
346}
347
348impl<S, P> IntersectsStrategy<S, P> for IxSegmentPoint
355where
356 S: SegmentTrait<Point = P>,
357 P: PointTrait + geometry_trait::PointMut + Default,
358 P::Scalar: CoordinateScalar,
359 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
360{
361 #[inline]
362 fn intersects(&self, s: &S, p: &P) -> bool {
363 IxPointSegment.intersects(p, s)
364 }
365}
366
367impl<S, L, P> IntersectsStrategy<S, L> for IxSegmentLinestring
368where
369 S: SegmentTrait<Point = P>,
370 L: LinestringTrait<Point = P>,
371 P: PointTrait + geometry_trait::PointMut + Default,
372 P::Scalar: CoordinateScalar,
373 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
374{
375 #[inline]
376 fn intersects(&self, s: &S, ls: &L) -> bool {
377 IxLinestringSegment.intersects(ls, s)
378 }
379}
380
381impl<G, P> IntersectsStrategy<G, P> for IxPolygonPoint
382where
383 G: PolygonTrait<Point = P>,
384 P: PointTrait,
385 P::Scalar: CoordinateScalar,
386 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
387{
388 #[inline]
389 fn intersects(&self, pg: &G, p: &P) -> bool {
390 IxPointPolygon.intersects(p, pg)
391 }
392}
393
394impl<G, L, P> IntersectsStrategy<G, L> for IxPolygonLinestring
395where
396 G: PolygonTrait<Point = P>,
397 L: LinestringTrait<Point = P>,
398 P: PointTrait,
399 P::Scalar: CoordinateScalar,
400 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
401{
402 #[inline]
403 fn intersects(&self, pg: &G, ls: &L) -> bool {
404 IxLinestringPolygon.intersects(ls, pg)
405 }
406}
407
408#[doc(hidden)]
415pub trait IntersectsPairStrategy<K2> {
416 type S: Default;
418}
419
420impl IntersectsPairStrategy<PointTag> for PointTag {
421 type S = IxPointPoint;
422}
423impl IntersectsPairStrategy<SegmentTag> for PointTag {
424 type S = IxPointSegment;
425}
426impl IntersectsPairStrategy<SegmentTag> for SegmentTag {
427 type S = IxSegmentSegment;
428}
429impl IntersectsPairStrategy<SegmentTag> for LinestringTag {
430 type S = IxLinestringSegment;
431}
432impl IntersectsPairStrategy<LinestringTag> for LinestringTag {
433 type S = IxLinestringLinestring;
434}
435impl IntersectsPairStrategy<PolygonTag> for PointTag {
436 type S = IxPointPolygon;
437}
438impl IntersectsPairStrategy<PolygonTag> for LinestringTag {
439 type S = IxLinestringPolygon;
440}
441impl IntersectsPairStrategy<PolygonTag> for PolygonTag {
442 type S = IxPolygonPolygon;
443}
444impl IntersectsPairStrategy<PointTag> for SegmentTag {
445 type S = IxSegmentPoint;
446}
447impl IntersectsPairStrategy<LinestringTag> for SegmentTag {
448 type S = IxSegmentLinestring;
449}
450impl IntersectsPairStrategy<PointTag> for PolygonTag {
451 type S = IxPolygonPoint;
452}
453impl IntersectsPairStrategy<LinestringTag> for PolygonTag {
454 type S = IxPolygonLinestring;
455}
456
457#[inline]
461fn points_equal<A, B>(a: &A, b: &B, dim: usize) -> bool
462where
463 A: PointTrait,
464 B: PointTrait<Scalar = A::Scalar>,
465{
466 let mut i = 0;
467 while i < dim {
468 let eq = match i {
471 0 => a.get::<0>() == b.get::<0>(),
472 1 => a.get::<1>() == b.get::<1>(),
473 2 => a.get::<2>() == b.get::<2>(),
474 3 => a.get::<3>() == b.get::<3>(),
475 _ => panic!("points_equal: dimension exceeds MAX_DIM (4)"),
476 };
477 if !eq {
478 return false;
479 }
480 i += 1;
481 }
482 true
483}
484
485fn point_on_segment<P>(p: &P, s1: &P, s2: &P) -> bool
491where
492 P: PointTrait,
493 P::Scalar: CoordinateScalar,
494{
495 let px = p.get::<0>();
496 let py = p.get::<1>();
497 let ax = s1.get::<0>();
498 let ay = s1.get::<1>();
499 let bx = s2.get::<0>();
500 let by = s2.get::<1>();
501 let cross = (bx - ax) * (py - ay) - (by - ay) * (px - ax);
502 if cross != P::Scalar::ZERO {
503 return false;
504 }
505 let (xlo, xhi) = if ax <= bx { (ax, bx) } else { (bx, ax) };
506 let (ylo, yhi) = if ay <= by { (ay, by) } else { (by, ay) };
507 xlo <= px && px <= xhi && ylo <= py && py <= yhi
508}
509
510fn segments_intersect<P>(p1: &P, p2: &P, p3: &P, p4: &P) -> bool
515where
516 P: PointTrait,
517 P::Scalar: CoordinateScalar,
518{
519 let x1 = p1.get::<0>();
520 let y1 = p1.get::<1>();
521 let x2 = p2.get::<0>();
522 let y2 = p2.get::<1>();
523 let x3 = p3.get::<0>();
524 let y3 = p3.get::<1>();
525 let x4 = p4.get::<0>();
526 let y4 = p4.get::<1>();
527
528 let d1 = side_sign((x3, y3), (x4, y4), (x1, y1));
529 let d2 = side_sign((x3, y3), (x4, y4), (x2, y2));
530 let d3 = side_sign((x1, y1), (x2, y2), (x3, y3));
531 let d4 = side_sign((x1, y1), (x2, y2), (x4, y4));
532
533 if ((d1 > 0 && d2 < 0) || (d1 < 0 && d2 > 0)) && ((d3 > 0 && d4 < 0) || (d3 < 0 && d4 > 0)) {
536 return true;
537 }
538
539 if d1 == 0 && point_on_segment(p1, p3, p4) {
543 return true;
544 }
545 if d2 == 0 && point_on_segment(p2, p3, p4) {
546 return true;
547 }
548 if d3 == 0 && point_on_segment(p3, p1, p2) {
549 return true;
550 }
551 if d4 == 0 && point_on_segment(p4, p1, p2) {
552 return true;
553 }
554 false
555}
556
557#[inline]
561fn side_sign<T: CoordinateScalar>(a: (T, T), b: (T, T), c: (T, T)) -> i32 {
562 let v = (b.0 - a.0) * (c.1 - a.1) - (b.1 - a.1) * (c.0 - a.0);
563 if v > T::ZERO {
564 1
565 } else if v < T::ZERO {
566 -1
567 } else {
568 0
569 }
570}
571
572#[inline]
577fn segment_bounds_disjoint<P>(p1: &P, p2: &P, p3: &P, p4: &P) -> bool
578where
579 P: PointTrait,
580{
581 let x1 = p1.get::<0>();
582 let y1 = p1.get::<1>();
583 let x2 = p2.get::<0>();
584 let y2 = p2.get::<1>();
585 let x3 = p3.get::<0>();
586 let y3 = p3.get::<1>();
587 let x4 = p4.get::<0>();
588 let y4 = p4.get::<1>();
589
590 (x1 < x3 && x1 < x4 && x2 < x3 && x2 < x4)
591 || (x3 < x1 && x3 < x2 && x4 < x1 && x4 < x2)
592 || (y1 < y3 && y1 < y4 && y2 < y3 && y2 < y4)
593 || (y3 < y1 && y3 < y2 && y4 < y1 && y4 < y2)
594}
595
596fn linestring_crosses_ring<L, R, P>(ls: &L, r: &R) -> bool
599where
600 L: LinestringTrait<Point = P>,
601 R: RingTrait<Point = P>,
602 P: PointTrait,
603 P::Scalar: CoordinateScalar,
604{
605 let mut ils = ls.points();
606 let Some(mut pls) = ils.next() else {
607 return false;
608 };
609 for qls in ils {
610 if ring_edge_crosses_segment(r, pls, qls) {
611 return true;
612 }
613 pls = qls;
614 }
615 false
616}
617
618fn ring_edge_crosses_segment<R, P>(r: &R, pls: &P, qls: &P) -> bool
620where
621 R: RingTrait<Point = P>,
622 P: PointTrait,
623 P::Scalar: CoordinateScalar,
624{
625 let mut ir = r.points();
626 let Some(mut pr) = ir.next() else {
627 return false;
628 };
629 let first = pr;
630 let mut has_edge = false;
631 for qr in ir {
632 has_edge = true;
633 if !segment_bounds_disjoint(pls, qls, pr, qr) && segments_intersect(pls, qls, pr, qr) {
634 return true;
635 }
636 pr = qr;
637 }
638 if has_edge && pr.get::<0>() == first.get::<0>() && pr.get::<1>() == first.get::<1>() {
639 return false;
640 }
641 !segment_bounds_disjoint(pls, qls, pr, first) && segments_intersect(pls, qls, pr, first)
644}
645
646fn rings_cross<Ra, Rb, P>(a: &Ra, b: &Rb) -> bool
651where
652 Ra: RingTrait<Point = P>,
653 Rb: RingTrait<Point = P>,
654 P: PointTrait,
655 P::Scalar: CoordinateScalar,
656{
657 let edges_a = ring_edges(a);
658 let edges_b = ring_edges(b);
659 for (pa, qa) in &edges_a {
660 for (pb, qb) in &edges_b {
661 if segments_intersect(*pa, *qa, *pb, *qb) {
662 return true;
663 }
664 }
665 }
666 false
667}
668
669fn ring_edges<R>(r: &R) -> alloc::vec::Vec<(&R::Point, &R::Point)>
673where
674 R: RingTrait,
675 R::Point: PointTrait,
676{
677 let pts: alloc::vec::Vec<&R::Point> = r.points().collect();
678 let mut out = alloc::vec::Vec::with_capacity(pts.len());
679 if pts.len() < 2 {
680 return out;
681 }
682 for w in pts.windows(2) {
683 out.push((w[0], w[1]));
684 }
685 out.push((*pts.last().unwrap(), *pts.first().unwrap()));
686 out
687}
688
689#[cfg(test)]
690mod tests {
691 use super::{CartesianIntersects, IntersectsStrategy, Reversed, linestring_crosses_ring};
696 use geometry_cs::Cartesian;
697 use geometry_model::{Point2D, Polygon, Ring, Segment, linestring, polygon};
698
699 type P = Point2D<f64, Cartesian>;
700
701 fn pt(x: f64, y: f64) -> P {
702 Point2D::new(x, y)
703 }
704
705 use geometry_model::Linestring;
706 type LS = Linestring<P>;
707
708 #[test]
710 fn ls_crosses_segment() {
711 let ls: LS = linestring![(1.0, 1.0), (3.0, 3.0), (2.0, 5.0)];
712 let s = Segment::new(pt(2.0, 0.0), pt(2.0, 6.0));
713 assert!(CartesianIntersects.intersects(&ls, &s));
714 }
715
716 #[test]
718 fn ls_touches_segment_endpoint() {
719 let ls: LS = linestring![(1.0, 1.0), (3.0, 3.0)];
720 let s = Segment::new(pt(1.0, 0.0), pt(1.0, 1.0));
721 assert!(CartesianIntersects.intersects(&ls, &s));
722 }
723
724 #[test]
726 fn ls_disjoint_from_segment() {
727 let ls: LS = linestring![(1.0, 1.0), (3.0, 3.0)];
728 let s = Segment::new(pt(3.0, 0.0), pt(4.0, 1.0));
729 assert!(!CartesianIntersects.intersects(&ls, &s));
730 }
731
732 #[test]
734 fn ls_crosses_ls() {
735 let a: LS = linestring![(0.0, 0.0), (2.0, 0.0), (3.0, 0.0)];
736 let b: LS = linestring![(0.0, 0.0), (1.0, 1.0), (2.0, 2.0)];
737 assert!(CartesianIntersects.intersects(&a, &b));
738 }
739
740 #[test]
742 fn ls_overlap_collinear() {
743 let a: LS = linestring![(0.0, 0.0), (2.0, 0.0), (3.0, 0.0)];
744 let b: LS = linestring![(1.0, 0.0), (4.0, 0.0), (5.0, 0.0)];
745 assert!(CartesianIntersects.intersects(&a, &b));
746 }
747
748 #[test]
750 fn ls_inside_polygon() {
751 let ls: LS = linestring![(1.0, 1.0), (2.0, 2.0)];
752 let p: Polygon<P> = polygon![[
753 (0.0, 0.0),
754 (10.0, 0.0),
755 (10.0, 10.0),
756 (0.0, 10.0),
757 (0.0, 0.0)
758 ]];
759 assert!(CartesianIntersects.intersects(&ls, &p));
760 }
761
762 #[test]
764 fn ls_outside_polygon() {
765 let ls: LS = linestring![(11.0, 0.0), (12.0, 12.0)];
766 let p: Polygon<P> = polygon![[
767 (0.0, 0.0),
768 (10.0, 0.0),
769 (10.0, 10.0),
770 (0.0, 10.0),
771 (0.0, 0.0)
772 ]];
773 assert!(!CartesianIntersects.intersects(&ls, &p));
774 }
775
776 #[test]
780 fn empty_linestring_has_no_ring_crossing() {
781 let ls = Linestring::<P>::new();
782 let ring: Ring<P> = Ring::from_vec(vec![
783 pt(0.0, 0.0),
784 pt(0.0, 1.0),
785 pt(1.0, 1.0),
786 pt(1.0, 0.0),
787 pt(0.0, 0.0),
788 ]);
789
790 assert!(!linestring_crosses_ring(&ls, &ring));
791 }
792
793 #[test]
795 fn reversed_pair_compiles_and_agrees() {
796 let ls: LS = linestring![(1.0, 1.0), (2.0, 2.0)];
797 let p: Polygon<P> = polygon![[
798 (0.0, 0.0),
799 (10.0, 0.0),
800 (10.0, 10.0),
801 (0.0, 10.0),
802 (0.0, 0.0)
803 ]];
804 let forward = CartesianIntersects.intersects(&ls, &p);
805 let reversed = Reversed(CartesianIntersects).intersects(&p, &ls);
806 assert_eq!(forward, reversed);
807 }
808
809 fn _accepts_readonly_point<A, B, S>(s: &S, a: &A, b: &B) -> bool
813 where
814 A: geometry_trait::Point,
815 B: geometry_trait::Point,
816 S: IntersectsStrategy<A, B>,
817 {
818 s.intersects(a, b)
819 }
820
821 use super::IxPointPoint;
822
823 #[test]
826 #[allow(
827 clippy::used_underscore_items,
828 reason = "the test exists to run the compile-time witness's body"
829 )]
830 fn readonly_point_witness_computes_equality() {
831 assert!(_accepts_readonly_point(
832 &IxPointPoint,
833 &pt(1.0, 1.0),
834 &pt(1.0, 1.0)
835 ));
836 assert!(!_accepts_readonly_point(
837 &IxPointPoint,
838 &pt(1.0, 1.0),
839 &pt(2.0, 2.0)
840 ));
841 }
842}