geoarrow_expr_geo/
intersects.rs

1use arrow_array::BooleanArray;
2use geo::intersects::Intersects;
3use geoarrow_array::{GeoArrowArray, GeoArrowArrayAccessor};
4use geoarrow_schema::error::{GeoArrowError, GeoArrowResult};
5
6use crate::util::downcast::downcast_geoarrow_array_two_args;
7use crate::util::to_geo::geometry_to_geo;
8
9pub fn intersects(
10    left_array: &dyn GeoArrowArray,
11    right_array: &dyn GeoArrowArray,
12) -> GeoArrowResult<BooleanArray> {
13    if left_array.len() != right_array.len() {
14        Err(GeoArrowError::InvalidGeoArrow(
15            "Input arrays must have the same length".to_string(),
16        ))
17    } else {
18        downcast_geoarrow_array_two_args!(left_array, right_array, _intersects_impl)
19    }
20}
21
22fn _intersects_impl<'a>(
23    left_array: &'a impl GeoArrowArrayAccessor<'a>,
24    right_array: &'a impl GeoArrowArrayAccessor<'a>,
25) -> GeoArrowResult<BooleanArray> {
26    let mut builder = BooleanArray::builder(left_array.len());
27
28    for (maybe_left, maybe_right) in left_array.iter().zip(right_array.iter()) {
29        match (maybe_left, maybe_right) {
30            (Some(left), Some(right)) => {
31                let left_geom = geometry_to_geo(&left?)?;
32                let right_geom = geometry_to_geo(&right?)?;
33                let intersects = left_geom.intersects(&right_geom);
34                builder.append_value(intersects);
35            }
36            _ => {
37                // If either is null, the result is null
38                builder.append_null();
39            }
40        }
41    }
42
43    Ok(builder.finish())
44}
45
46#[cfg(test)]
47mod tests {
48    use geo::{Geometry, line_string, polygon};
49    use geoarrow_array::builder::GeometryBuilder;
50    use geoarrow_schema::{CoordType, GeometryType};
51
52    use super::*;
53
54    #[test]
55    fn test_intersects() {
56        // Group matching pairs for better visibility
57        let test_pairs = [
58            // Pair 1: Should intersect, overlapping unit squares
59            vec![
60                Some(Geometry::from(polygon![
61                    (x: 1.0, y: 1.0),
62                    (x: 2.0, y: 1.0),
63                    (x: 2.0, y: 2.0),
64                    (x: 1.0, y: 2.0)
65                ])),
66                Some(Geometry::from(polygon![
67                    (x: 1.5, y: 1.5),
68                    (x: 2.5, y: 1.5),
69                    (x: 2.5, y: 2.5),
70                    (x: 1.5, y: 2.5)
71                ])),
72            ],
73            // Pair 2: Should not intersect, separated squares
74            vec![
75                Some(Geometry::from(polygon![
76                    (x: 1.0, y: 1.0),
77                    (x: 2.0, y: 1.0),
78                    (x: 2.0, y: 2.0),
79                    (x: 1.0, y: 2.0)
80                ])),
81                Some(Geometry::from(polygon![
82                    (x: 3.0, y: 3.0),
83                    (x: 4.0, y: 3.0),
84                    (x: 4.0, y: 4.0),
85                    (x: 3.0, y: 4.0)
86                ])),
87            ],
88            // Pair 3: Should intersect, touching at corner
89            vec![
90                Some(Geometry::from(polygon![
91                    (x: 2.0, y: 2.0),
92                    (x: 3.0, y: 2.0),
93                    (x: 3.0, y: 3.0),
94                    (x: 2.0, y: 3.0)
95                ])),
96                Some(Geometry::from(polygon![
97                    (x: 3.0, y: 3.0),
98                    (x: 4.0, y: 3.0),
99                    (x: 4.0, y: 4.0),
100                    (x: 3.0, y: 4.0)
101                ])),
102            ],
103            // Pair 4: Mixed geometry types, should intersect
104            vec![
105                Some(Geometry::from(line_string! [
106                    (x: 1.0, y: 1.0),
107                    (x: 2.0, y: 2.0)
108                ])),
109                Some(Geometry::from(polygon![
110                    (x: 1.5, y: 1.5),
111                    (x: 2.5, y: 1.5),
112                    (x: 2.5, y: 2.5),
113                    (x: 1.5, y: 2.5)
114                ])),
115            ],
116            // Pair 5: Null geometries, should return null
117            vec![None, None],
118        ];
119
120        let geoms_left: Vec<_> = test_pairs.iter().map(|pair| pair[0].clone()).collect();
121        let geoms_right: Vec<_> = test_pairs.iter().map(|pair| pair[1].clone()).collect();
122
123        let typ = GeometryType::new(Default::default()).with_coord_type(CoordType::Interleaved);
124        let left_array = GeometryBuilder::from_nullable_geometries(&geoms_left, typ.clone())
125            .unwrap()
126            .finish();
127        let right_array = GeometryBuilder::from_nullable_geometries(&geoms_right, typ)
128            .unwrap()
129            .finish();
130
131        let result = intersects(&left_array, &right_array).unwrap();
132
133        let expected =
134            BooleanArray::from(vec![Some(true), Some(false), Some(true), Some(true), None]);
135
136        assert_eq!(result, expected);
137    }
138
139    #[test]
140    #[should_panic(expected = "Input arrays must have the same length")]
141    fn test_intersects_length_mismatch() {
142        let left_geom = vec![Some(Geometry::from(
143            polygon![(x: 0.0, y: 0.0), (x: 1.0, y: 0.0), (x: 1.0, y: 1.0), (x: 0.0, y: 1.0)],
144        ))];
145        let right_geom: Vec<Option<Geometry>> = vec![];
146
147        let typ = GeometryType::new(Default::default()).with_coord_type(CoordType::Interleaved);
148        let left_array = GeometryBuilder::from_nullable_geometries(&left_geom, typ.clone())
149            .unwrap()
150            .finish();
151        let right_array = GeometryBuilder::from_nullable_geometries(&right_geom, typ)
152            .unwrap()
153            .finish();
154
155        intersects(&left_array, &right_array).unwrap();
156    }
157
158    #[test]
159    fn test_intersects_empty_arrays() {
160        let typ = GeometryType::new(Default::default()).with_coord_type(CoordType::Interleaved);
161        let left_array =
162            GeometryBuilder::from_nullable_geometries(&Vec::<Option<Geometry>>::new(), typ.clone())
163                .unwrap()
164                .finish();
165        let right_array =
166            GeometryBuilder::from_nullable_geometries(&Vec::<Option<Geometry>>::new(), typ)
167                .unwrap()
168                .finish();
169
170        let result = intersects(&left_array, &right_array).unwrap();
171        assert_eq!(result.len(), 0);
172    }
173}