1use nodedb_types::geometry::{Geometry, point_in_polygon};
10use nodedb_types::geometry_bbox;
11
12use super::edge::{point_on_ring_boundary, ring_edges};
13
14pub fn st_contains(a: &Geometry, b: &Geometry) -> bool {
19 let a_bb = geometry_bbox(a);
21 let b_bb = geometry_bbox(b);
22 if !a_bb.contains_bbox(&b_bb) {
23 return false;
24 }
25
26 match (a, b) {
27 (Geometry::Point { coordinates: ca }, Geometry::Point { coordinates: cb }) => {
29 (ca[0] - cb[0]).abs() < 1e-12 && (ca[1] - cb[1]).abs() < 1e-12
30 }
31
32 (Geometry::Polygon { coordinates: rings }, Geometry::Point { coordinates: pt }) => {
34 polygon_contains_point(rings, *pt)
35 }
36
37 (Geometry::Polygon { coordinates: rings }, Geometry::LineString { coordinates: line }) => {
40 polygon_contains_linestring(rings, line)
41 }
42
43 (
45 Geometry::Polygon {
46 coordinates: rings_a,
47 },
48 Geometry::Polygon {
49 coordinates: rings_b,
50 },
51 ) => polygon_contains_polygon(rings_a, rings_b),
52
53 (Geometry::Polygon { .. }, Geometry::MultiPoint { coordinates }) => coordinates
55 .iter()
56 .all(|pt| st_contains(a, &Geometry::Point { coordinates: *pt })),
57 (Geometry::Polygon { .. }, Geometry::MultiLineString { coordinates }) => {
58 coordinates.iter().all(|ls| {
59 st_contains(
60 a,
61 &Geometry::LineString {
62 coordinates: ls.clone(),
63 },
64 )
65 })
66 }
67 (Geometry::Polygon { .. }, Geometry::MultiPolygon { coordinates }) => {
68 coordinates.iter().all(|poly| {
69 st_contains(
70 a,
71 &Geometry::Polygon {
72 coordinates: poly.clone(),
73 },
74 )
75 })
76 }
77
78 (Geometry::MultiPolygon { coordinates: polys }, _) => polys.iter().any(|poly| {
80 st_contains(
81 &Geometry::Polygon {
82 coordinates: poly.clone(),
83 },
84 b,
85 )
86 }),
87
88 (Geometry::LineString { coordinates: line }, Geometry::Point { coordinates: pt }) => {
92 super::edge::point_on_ring_boundary(*pt, line)
93 }
94
95 (Geometry::GeometryCollection { geometries }, _) => {
97 geometries.iter().any(|g| st_contains(g, b))
98 }
99
100 _ => false,
102 }
103}
104
105fn polygon_contains_point(rings: &[Vec<[f64; 2]>], pt: [f64; 2]) -> bool {
107 let Some(exterior) = rings.first() else {
108 return false;
109 };
110
111 if point_on_ring_boundary(pt, exterior) {
113 return false;
114 }
115
116 if !point_in_polygon(pt[0], pt[1], exterior) {
118 return false;
119 }
120
121 for hole in &rings[1..] {
123 if point_in_polygon(pt[0], pt[1], hole) {
124 return false;
125 }
126 if point_on_ring_boundary(pt, hole) {
128 return false;
129 }
130 }
131
132 true
133}
134
135fn polygon_contains_linestring(rings: &[Vec<[f64; 2]>], line: &[[f64; 2]]) -> bool {
137 if line.is_empty() {
138 return true;
139 }
140
141 let Some(exterior) = rings.first() else {
142 return false;
143 };
144
145 for pt in line {
147 if !polygon_contains_point(rings, *pt) {
148 if !point_on_ring_boundary(*pt, exterior) {
153 return false;
154 }
155 }
156 }
157
158 let poly_edges = ring_edges(exterior);
160 for i in 0..line.len() - 1 {
161 for &(pe_a, pe_b) in &poly_edges {
162 if edges_properly_cross(line[i], line[i + 1], pe_a, pe_b) {
163 return false;
164 }
165 }
166 }
167
168 for hole in &rings[1..] {
170 let hole_edges = ring_edges(hole);
171 for i in 0..line.len() - 1 {
172 for &(he_a, he_b) in &hole_edges {
173 if edges_properly_cross(line[i], line[i + 1], he_a, he_b) {
174 return false;
175 }
176 }
177 }
178 for pt in line {
180 if point_in_polygon(pt[0], pt[1], hole) {
181 return false;
182 }
183 }
184 }
185
186 line.iter().any(|pt| polygon_contains_point(rings, *pt))
188}
189
190fn polygon_contains_polygon(rings_a: &[Vec<[f64; 2]>], rings_b: &[Vec<[f64; 2]>]) -> bool {
192 let Some(ext_b) = rings_b.first() else {
193 return true;
194 };
195
196 let Some(ext_a) = rings_a.first() else {
199 return false;
200 };
201
202 for pt in ext_b {
203 if !point_in_polygon(pt[0], pt[1], ext_a) && !point_on_ring_boundary(*pt, ext_a) {
204 return false;
205 }
206 }
207
208 for hole in &rings_a[1..] {
210 for pt in ext_b {
211 if point_in_polygon(pt[0], pt[1], hole) {
212 return false;
213 }
214 }
215 }
216
217 let a_edges = ring_edges(ext_a);
219 let b_edges = ring_edges(ext_b);
220 for &(a1, a2) in &a_edges {
221 for &(b1, b2) in &b_edges {
222 if edges_properly_cross(a1, a2, b1, b2) {
223 return false;
224 }
225 }
226 }
227
228 ext_b.iter().any(|pt| polygon_contains_point(rings_a, *pt))
230}
231
232fn edges_properly_cross(a1: [f64; 2], a2: [f64; 2], b1: [f64; 2], b2: [f64; 2]) -> bool {
234 use super::edge::Orientation;
235 use super::edge::orientation;
236
237 let o1 = orientation(a1, a2, b1);
238 let o2 = orientation(a1, a2, b2);
239 let o3 = orientation(b1, b2, a1);
240 let o4 = orientation(b1, b2, a2);
241
242 if o1 != o2
244 && o3 != o4
245 && o1 != Orientation::Collinear
246 && o2 != Orientation::Collinear
247 && o3 != Orientation::Collinear
248 && o4 != Orientation::Collinear
249 {
250 return true;
251 }
252 false
253}
254
255#[cfg(test)]
256mod tests {
257 use super::*;
258
259 fn square_poly() -> Geometry {
260 Geometry::polygon(vec![vec![
261 [0.0, 0.0],
262 [10.0, 0.0],
263 [10.0, 10.0],
264 [0.0, 10.0],
265 [0.0, 0.0],
266 ]])
267 }
268
269 fn poly_with_hole() -> Geometry {
270 Geometry::polygon(vec![
271 vec![
272 [0.0, 0.0],
273 [20.0, 0.0],
274 [20.0, 20.0],
275 [0.0, 20.0],
276 [0.0, 0.0],
277 ],
278 vec![
279 [5.0, 5.0],
280 [15.0, 5.0],
281 [15.0, 15.0],
282 [5.0, 15.0],
283 [5.0, 5.0],
284 ],
285 ])
286 }
287
288 #[test]
289 fn point_inside_polygon() {
290 assert!(st_contains(&square_poly(), &Geometry::point(5.0, 5.0)));
291 }
292
293 #[test]
294 fn point_outside_polygon() {
295 assert!(!st_contains(&square_poly(), &Geometry::point(15.0, 5.0)));
296 }
297
298 #[test]
299 fn point_on_edge_not_contained() {
300 assert!(!st_contains(&square_poly(), &Geometry::point(5.0, 0.0)));
302 }
303
304 #[test]
305 fn point_on_vertex_not_contained() {
306 assert!(!st_contains(&square_poly(), &Geometry::point(0.0, 0.0)));
307 }
308
309 #[test]
310 fn point_in_hole_not_contained() {
311 assert!(!st_contains(
313 &poly_with_hole(),
314 &Geometry::point(10.0, 10.0)
315 ));
316 }
317
318 #[test]
319 fn point_between_exterior_and_hole() {
320 assert!(st_contains(&poly_with_hole(), &Geometry::point(2.0, 2.0)));
322 }
323
324 #[test]
325 fn linestring_inside_polygon() {
326 let line = Geometry::line_string(vec![[2.0, 2.0], [5.0, 5.0], [8.0, 3.0]]);
327 assert!(st_contains(&square_poly(), &line));
328 }
329
330 #[test]
331 fn linestring_crossing_boundary() {
332 let line = Geometry::line_string(vec![[5.0, 5.0], [15.0, 5.0]]);
333 assert!(!st_contains(&square_poly(), &line));
334 }
335
336 #[test]
337 fn polygon_contains_smaller_polygon() {
338 let inner = Geometry::polygon(vec![vec![
339 [2.0, 2.0],
340 [8.0, 2.0],
341 [8.0, 8.0],
342 [2.0, 8.0],
343 [2.0, 2.0],
344 ]]);
345 assert!(st_contains(&square_poly(), &inner));
346 }
347
348 #[test]
349 fn polygon_does_not_contain_overlapping() {
350 let overlapping = Geometry::polygon(vec![vec![
351 [5.0, 5.0],
352 [15.0, 5.0],
353 [15.0, 15.0],
354 [5.0, 15.0],
355 [5.0, 5.0],
356 ]]);
357 assert!(!st_contains(&square_poly(), &overlapping));
358 }
359
360 #[test]
361 fn point_contains_same_point() {
362 let p = Geometry::point(5.0, 5.0);
363 assert!(st_contains(&p, &p));
364 }
365
366 #[test]
367 fn multipoint_contained() {
368 let mp = Geometry::MultiPoint {
369 coordinates: vec![[2.0, 2.0], [5.0, 5.0], [8.0, 8.0]],
370 };
371 assert!(st_contains(&square_poly(), &mp));
372 }
373
374 #[test]
375 fn multipoint_not_all_contained() {
376 let mp = Geometry::MultiPoint {
377 coordinates: vec![[2.0, 2.0], [15.0, 15.0]],
378 };
379 assert!(!st_contains(&square_poly(), &mp));
380 }
381}