use nodedb_types::geometry::{Geometry, point_in_polygon};
use nodedb_types::geometry_bbox;
use super::edge::{point_on_ring_boundary, ring_edges};
pub fn st_contains(a: &Geometry, b: &Geometry) -> bool {
let a_bb = geometry_bbox(a);
let b_bb = geometry_bbox(b);
if !a_bb.contains_bbox(&b_bb) {
return false;
}
match (a, b) {
(Geometry::Point { coordinates: ca }, Geometry::Point { coordinates: cb }) => {
(ca[0] - cb[0]).abs() < 1e-12 && (ca[1] - cb[1]).abs() < 1e-12
}
(Geometry::Polygon { coordinates: rings }, Geometry::Point { coordinates: pt }) => {
polygon_contains_point(rings, *pt)
}
(Geometry::Polygon { coordinates: rings }, Geometry::LineString { coordinates: line }) => {
polygon_contains_linestring(rings, line)
}
(
Geometry::Polygon {
coordinates: rings_a,
},
Geometry::Polygon {
coordinates: rings_b,
},
) => polygon_contains_polygon(rings_a, rings_b),
(Geometry::Polygon { .. }, Geometry::MultiPoint { coordinates }) => coordinates
.iter()
.all(|pt| st_contains(a, &Geometry::Point { coordinates: *pt })),
(Geometry::Polygon { .. }, Geometry::MultiLineString { coordinates }) => {
coordinates.iter().all(|ls| {
st_contains(
a,
&Geometry::LineString {
coordinates: ls.clone(),
},
)
})
}
(Geometry::Polygon { .. }, Geometry::MultiPolygon { coordinates }) => {
coordinates.iter().all(|poly| {
st_contains(
a,
&Geometry::Polygon {
coordinates: poly.clone(),
},
)
})
}
(Geometry::MultiPolygon { coordinates: polys }, _) => polys.iter().any(|poly| {
st_contains(
&Geometry::Polygon {
coordinates: poly.clone(),
},
b,
)
}),
(Geometry::LineString { coordinates: line }, Geometry::Point { coordinates: pt }) => {
super::edge::point_on_ring_boundary(*pt, line)
}
(Geometry::GeometryCollection { geometries }, _) => {
geometries.iter().any(|g| st_contains(g, b))
}
_ => false,
}
}
fn polygon_contains_point(rings: &[Vec<[f64; 2]>], pt: [f64; 2]) -> bool {
let Some(exterior) = rings.first() else {
return false;
};
if point_on_ring_boundary(pt, exterior) {
return false;
}
if !point_in_polygon(pt[0], pt[1], exterior) {
return false;
}
for hole in &rings[1..] {
if point_in_polygon(pt[0], pt[1], hole) {
return false;
}
if point_on_ring_boundary(pt, hole) {
return false;
}
}
true
}
fn polygon_contains_linestring(rings: &[Vec<[f64; 2]>], line: &[[f64; 2]]) -> bool {
if line.is_empty() {
return true;
}
let Some(exterior) = rings.first() else {
return false;
};
for pt in line {
if !polygon_contains_point(rings, *pt) {
if !point_on_ring_boundary(*pt, exterior) {
return false;
}
}
}
let poly_edges = ring_edges(exterior);
for i in 0..line.len() - 1 {
for &(pe_a, pe_b) in &poly_edges {
if edges_properly_cross(line[i], line[i + 1], pe_a, pe_b) {
return false;
}
}
}
for hole in &rings[1..] {
let hole_edges = ring_edges(hole);
for i in 0..line.len() - 1 {
for &(he_a, he_b) in &hole_edges {
if edges_properly_cross(line[i], line[i + 1], he_a, he_b) {
return false;
}
}
}
for pt in line {
if point_in_polygon(pt[0], pt[1], hole) {
return false;
}
}
}
line.iter().any(|pt| polygon_contains_point(rings, *pt))
}
fn polygon_contains_polygon(rings_a: &[Vec<[f64; 2]>], rings_b: &[Vec<[f64; 2]>]) -> bool {
let Some(ext_b) = rings_b.first() else {
return true;
};
let Some(ext_a) = rings_a.first() else {
return false;
};
for pt in ext_b {
if !point_in_polygon(pt[0], pt[1], ext_a) && !point_on_ring_boundary(*pt, ext_a) {
return false;
}
}
for hole in &rings_a[1..] {
for pt in ext_b {
if point_in_polygon(pt[0], pt[1], hole) {
return false;
}
}
}
let a_edges = ring_edges(ext_a);
let b_edges = ring_edges(ext_b);
for &(a1, a2) in &a_edges {
for &(b1, b2) in &b_edges {
if edges_properly_cross(a1, a2, b1, b2) {
return false;
}
}
}
ext_b.iter().any(|pt| polygon_contains_point(rings_a, *pt))
}
fn edges_properly_cross(a1: [f64; 2], a2: [f64; 2], b1: [f64; 2], b2: [f64; 2]) -> bool {
use super::edge::Orientation;
use super::edge::orientation;
let o1 = orientation(a1, a2, b1);
let o2 = orientation(a1, a2, b2);
let o3 = orientation(b1, b2, a1);
let o4 = orientation(b1, b2, a2);
if o1 != o2
&& o3 != o4
&& o1 != Orientation::Collinear
&& o2 != Orientation::Collinear
&& o3 != Orientation::Collinear
&& o4 != Orientation::Collinear
{
return true;
}
false
}
#[cfg(test)]
mod tests {
use super::*;
fn square_poly() -> Geometry {
Geometry::polygon(vec![vec![
[0.0, 0.0],
[10.0, 0.0],
[10.0, 10.0],
[0.0, 10.0],
[0.0, 0.0],
]])
}
fn poly_with_hole() -> Geometry {
Geometry::polygon(vec![
vec![
[0.0, 0.0],
[20.0, 0.0],
[20.0, 20.0],
[0.0, 20.0],
[0.0, 0.0],
],
vec![
[5.0, 5.0],
[15.0, 5.0],
[15.0, 15.0],
[5.0, 15.0],
[5.0, 5.0],
],
])
}
#[test]
fn point_inside_polygon() {
assert!(st_contains(&square_poly(), &Geometry::point(5.0, 5.0)));
}
#[test]
fn point_outside_polygon() {
assert!(!st_contains(&square_poly(), &Geometry::point(15.0, 5.0)));
}
#[test]
fn point_on_edge_not_contained() {
assert!(!st_contains(&square_poly(), &Geometry::point(5.0, 0.0)));
}
#[test]
fn point_on_vertex_not_contained() {
assert!(!st_contains(&square_poly(), &Geometry::point(0.0, 0.0)));
}
#[test]
fn point_in_hole_not_contained() {
assert!(!st_contains(
&poly_with_hole(),
&Geometry::point(10.0, 10.0)
));
}
#[test]
fn point_between_exterior_and_hole() {
assert!(st_contains(&poly_with_hole(), &Geometry::point(2.0, 2.0)));
}
#[test]
fn linestring_inside_polygon() {
let line = Geometry::line_string(vec![[2.0, 2.0], [5.0, 5.0], [8.0, 3.0]]);
assert!(st_contains(&square_poly(), &line));
}
#[test]
fn linestring_crossing_boundary() {
let line = Geometry::line_string(vec![[5.0, 5.0], [15.0, 5.0]]);
assert!(!st_contains(&square_poly(), &line));
}
#[test]
fn polygon_contains_smaller_polygon() {
let inner = Geometry::polygon(vec![vec![
[2.0, 2.0],
[8.0, 2.0],
[8.0, 8.0],
[2.0, 8.0],
[2.0, 2.0],
]]);
assert!(st_contains(&square_poly(), &inner));
}
#[test]
fn polygon_does_not_contain_overlapping() {
let overlapping = Geometry::polygon(vec![vec![
[5.0, 5.0],
[15.0, 5.0],
[15.0, 15.0],
[5.0, 15.0],
[5.0, 5.0],
]]);
assert!(!st_contains(&square_poly(), &overlapping));
}
#[test]
fn point_contains_same_point() {
let p = Geometry::point(5.0, 5.0);
assert!(st_contains(&p, &p));
}
#[test]
fn multipoint_contained() {
let mp = Geometry::MultiPoint {
coordinates: vec![[2.0, 2.0], [5.0, 5.0], [8.0, 8.0]],
};
assert!(st_contains(&square_poly(), &mp));
}
#[test]
fn multipoint_not_all_contained() {
let mp = Geometry::MultiPoint {
coordinates: vec![[2.0, 2.0], [15.0, 15.0]],
};
assert!(!st_contains(&square_poly(), &mp));
}
}