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