use crate::error::Result;
use oxigdal_core::vector::{Coordinate, Point, Polygon};
pub fn contains<T: ContainsPredicate>(a: &T, b: &T) -> Result<bool> {
a.contains(b)
}
pub fn within<T: ContainsPredicate>(a: &T, b: &T) -> Result<bool> {
b.contains(a)
}
pub fn intersects<T: IntersectsPredicate>(a: &T, b: &T) -> Result<bool> {
a.intersects(b)
}
pub fn disjoint<T: IntersectsPredicate>(a: &T, b: &T) -> Result<bool> {
Ok(!a.intersects(b)?)
}
pub fn touches<T: TouchesPredicate>(a: &T, b: &T) -> Result<bool> {
a.touches(b)
}
pub fn overlaps<T: OverlapsPredicate>(a: &T, b: &T) -> Result<bool> {
a.overlaps(b)
}
pub fn crosses<T: CrossesPredicate>(a: &T, b: &T) -> Result<bool> {
a.crosses(b)
}
pub trait ContainsPredicate {
fn contains(&self, other: &Self) -> Result<bool>;
}
pub trait IntersectsPredicate {
fn intersects(&self, other: &Self) -> Result<bool>;
}
pub trait TouchesPredicate {
fn touches(&self, other: &Self) -> Result<bool>;
}
pub trait OverlapsPredicate {
fn overlaps(&self, other: &Self) -> Result<bool>;
}
pub trait CrossesPredicate {
fn crosses(&self, other: &Self) -> Result<bool>;
}
impl ContainsPredicate for Point {
fn contains(&self, other: &Self) -> Result<bool> {
Ok((self.coord.x - other.coord.x).abs() < f64::EPSILON
&& (self.coord.y - other.coord.y).abs() < f64::EPSILON)
}
}
impl ContainsPredicate for Polygon {
fn contains(&self, other: &Self) -> Result<bool> {
for coord in &other.exterior.coords {
if !point_in_polygon_or_boundary(coord, self) {
return Ok(false);
}
}
let mut has_interior_point = false;
for coord in &other.exterior.coords {
if point_strictly_inside_polygon(coord, self) {
has_interior_point = true;
break;
}
}
Ok(has_interior_point)
}
}
impl IntersectsPredicate for Point {
fn intersects(&self, other: &Self) -> Result<bool> {
self.contains(other)
}
}
impl IntersectsPredicate for Polygon {
fn intersects(&self, other: &Self) -> Result<bool> {
for coord in &other.exterior.coords {
if point_in_polygon_or_boundary(coord, self) {
return Ok(true);
}
}
for coord in &self.exterior.coords {
if point_in_polygon_or_boundary(coord, other) {
return Ok(true);
}
}
Ok(rings_intersect(
&self.exterior.coords,
&other.exterior.coords,
))
}
}
impl TouchesPredicate for Polygon {
fn touches(&self, other: &Self) -> Result<bool> {
let mut has_boundary_contact = false;
let mut has_interior_contact = false;
for coord in &other.exterior.coords {
if point_on_polygon_boundary(coord, self) {
has_boundary_contact = true;
} else if point_strictly_inside_polygon(coord, self) {
has_interior_contact = true;
}
}
for coord in &self.exterior.coords {
if point_strictly_inside_polygon(coord, other) {
has_interior_contact = true;
}
}
Ok(has_boundary_contact && !has_interior_contact)
}
}
impl OverlapsPredicate for Point {
fn overlaps(&self, _other: &Self) -> Result<bool> {
Ok(false)
}
}
impl OverlapsPredicate for Polygon {
fn overlaps(&self, other: &Self) -> Result<bool> {
if !self.intersects(other)? {
return Ok(false);
}
if self.contains(other)? || other.contains(self)? {
return Ok(false);
}
Ok(true)
}
}
impl CrossesPredicate for Point {
fn crosses(&self, _other: &Self) -> Result<bool> {
Ok(false)
}
}
impl CrossesPredicate for Polygon {
fn crosses(&self, _other: &Self) -> Result<bool> {
Ok(false)
}
}
pub fn point_in_polygon_or_boundary(point: &Coordinate, polygon: &Polygon) -> bool {
point_in_polygon_boundary(point, polygon) || point_on_polygon_boundary(point, polygon)
}
pub fn point_strictly_inside_polygon(point: &Coordinate, polygon: &Polygon) -> bool {
point_in_polygon_boundary(point, polygon) && !point_on_polygon_boundary(point, polygon)
}
pub fn point_on_polygon_boundary(point: &Coordinate, polygon: &Polygon) -> bool {
point_on_ring(&polygon.exterior.coords, point)
|| polygon
.interiors
.iter()
.any(|hole| point_on_ring(&hole.coords, point))
}
fn point_on_ring(ring: &[Coordinate], point: &Coordinate) -> bool {
for i in 0..ring.len().saturating_sub(1) {
if point_on_segment(point, &ring[i], &ring[i + 1]) {
return true;
}
}
false
}
fn point_on_segment(point: &Coordinate, seg_start: &Coordinate, seg_end: &Coordinate) -> bool {
let cross = (seg_end.y - seg_start.y) * (point.x - seg_start.x)
- (seg_end.x - seg_start.x) * (point.y - seg_start.y);
if cross.abs() > f64::EPSILON {
return false;
}
let dot = (point.x - seg_start.x) * (seg_end.x - seg_start.x)
+ (point.y - seg_start.y) * (seg_end.y - seg_start.y);
let len_sq = (seg_end.x - seg_start.x).powi(2) + (seg_end.y - seg_start.y).powi(2);
if dot < -f64::EPSILON || dot > len_sq + f64::EPSILON {
return false;
}
true
}
fn point_in_polygon_boundary(point: &Coordinate, polygon: &Polygon) -> bool {
let mut inside = ray_casting_test(point, &polygon.exterior.coords);
for hole in &polygon.interiors {
if ray_casting_test(point, &hole.coords) {
inside = !inside;
}
}
inside
}
fn ray_casting_test(point: &Coordinate, ring: &[Coordinate]) -> bool {
let mut inside = false;
let n = ring.len();
let mut j = n - 1;
for i in 0..n {
let xi = ring[i].x;
let yi = ring[i].y;
let xj = ring[j].x;
let yj = ring[j].y;
let intersect = ((yi > point.y) != (yj > point.y))
&& (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi);
if intersect {
inside = !inside;
}
j = i;
}
inside
}
#[allow(dead_code)]
fn winding_number_test(point: &Coordinate, ring: &[Coordinate]) -> bool {
let mut winding_number = 0;
let n = ring.len();
for i in 0..n - 1 {
let p1 = &ring[i];
let p2 = &ring[i + 1];
if p1.y <= point.y {
if p2.y > point.y {
if is_left(p1, p2, point) > 0.0 {
winding_number += 1;
}
}
} else if p2.y <= point.y {
if is_left(p1, p2, point) < 0.0 {
winding_number -= 1;
}
}
}
winding_number != 0
}
fn is_left(p1: &Coordinate, p2: &Coordinate, point: &Coordinate) -> f64 {
(p2.x - p1.x) * (point.y - p1.y) - (point.x - p1.x) * (p2.y - p1.y)
}
fn rings_intersect(ring1: &[Coordinate], ring2: &[Coordinate]) -> bool {
for i in 0..ring1.len().saturating_sub(1) {
for j in 0..ring2.len().saturating_sub(1) {
if segments_intersect(&ring1[i], &ring1[i + 1], &ring2[j], &ring2[j + 1]) {
return true;
}
}
}
false
}
fn segments_intersect(p1: &Coordinate, p2: &Coordinate, p3: &Coordinate, p4: &Coordinate) -> bool {
let d1 = direction(p3, p4, p1);
let d2 = direction(p3, p4, p2);
let d3 = direction(p1, p2, p3);
let d4 = direction(p1, p2, p4);
if ((d1 > 0.0 && d2 < 0.0) || (d1 < 0.0 && d2 > 0.0))
&& ((d3 > 0.0 && d4 < 0.0) || (d3 < 0.0 && d4 > 0.0))
{
return true;
}
if d1.abs() < f64::EPSILON && on_segment(p3, p1, p4) {
return true;
}
if d2.abs() < f64::EPSILON && on_segment(p3, p2, p4) {
return true;
}
if d3.abs() < f64::EPSILON && on_segment(p1, p3, p2) {
return true;
}
if d4.abs() < f64::EPSILON && on_segment(p1, p4, p2) {
return true;
}
false
}
fn direction(a: &Coordinate, b: &Coordinate, p: &Coordinate) -> f64 {
(b.x - a.x) * (p.y - a.y) - (p.x - a.x) * (b.y - a.y)
}
fn on_segment(p: &Coordinate, q: &Coordinate, r: &Coordinate) -> bool {
q.x <= p.x.max(r.x) && q.x >= p.x.min(r.x) && q.y <= p.y.max(r.y) && q.y >= p.y.min(r.y)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::AlgorithmError;
use oxigdal_core::vector::LineString;
fn create_square() -> Result<Polygon> {
let coords = vec![
Coordinate::new_2d(0.0, 0.0),
Coordinate::new_2d(4.0, 0.0),
Coordinate::new_2d(4.0, 4.0),
Coordinate::new_2d(0.0, 4.0),
Coordinate::new_2d(0.0, 0.0),
];
let exterior = LineString::new(coords).map_err(|e| AlgorithmError::Core(e))?;
Polygon::new(exterior, vec![]).map_err(|e| AlgorithmError::Core(e))
}
#[test]
fn test_point_contains_point() {
let p1 = Point::new(1.0, 2.0);
let p2 = Point::new(1.0, 2.0);
let p3 = Point::new(3.0, 4.0);
let result1 = p1.contains(&p2);
assert!(result1.is_ok());
if let Ok(contains) = result1 {
assert!(contains);
}
let result2 = p1.contains(&p3);
assert!(result2.is_ok());
if let Ok(contains) = result2 {
assert!(!contains);
}
}
#[test]
fn test_polygon_contains_point() {
let poly = create_square();
assert!(poly.is_ok());
if let Ok(p) = poly {
let inside = Coordinate::new_2d(2.0, 2.0);
assert!(point_strictly_inside_polygon(&inside, &p));
let outside = Coordinate::new_2d(5.0, 5.0);
assert!(!point_in_polygon_or_boundary(&outside, &p));
let boundary = Coordinate::new_2d(0.0, 2.0);
assert!(point_on_polygon_boundary(&boundary, &p));
}
}
#[test]
fn test_point_in_polygon_boundary() {
let poly = create_square();
assert!(poly.is_ok());
if let Ok(p) = poly {
let inside = Coordinate::new_2d(2.0, 2.0);
assert!(point_in_polygon_boundary(&inside, &p));
let outside = Coordinate::new_2d(5.0, 5.0);
assert!(!point_in_polygon_boundary(&outside, &p));
}
}
#[test]
fn test_point_on_segment() {
let seg_start = Coordinate::new_2d(0.0, 0.0);
let seg_end = Coordinate::new_2d(4.0, 0.0);
let on = Coordinate::new_2d(2.0, 0.0);
assert!(point_on_segment(&on, &seg_start, &seg_end));
let off = Coordinate::new_2d(2.0, 1.0);
assert!(!point_on_segment(&off, &seg_start, &seg_end));
}
#[test]
fn test_polygon_intersects_polygon() {
let poly1 = create_square();
assert!(poly1.is_ok());
let coords2 = vec![
Coordinate::new_2d(2.0, 2.0),
Coordinate::new_2d(6.0, 2.0),
Coordinate::new_2d(6.0, 6.0),
Coordinate::new_2d(2.0, 6.0),
Coordinate::new_2d(2.0, 2.0),
];
let exterior2 = LineString::new(coords2);
assert!(exterior2.is_ok());
if let (Ok(p1), Ok(ext2)) = (poly1, exterior2) {
let poly2 = Polygon::new(ext2, vec![]);
assert!(poly2.is_ok());
if let Ok(p2) = poly2 {
let result: crate::error::Result<bool> = intersects(&p1, &p2);
assert!(result.is_ok());
if let Ok(do_intersect) = result {
assert!(do_intersect);
}
}
}
}
#[test]
fn test_disjoint_polygons() {
let poly1 = create_square();
let coords2 = vec![
Coordinate::new_2d(10.0, 10.0),
Coordinate::new_2d(14.0, 10.0),
Coordinate::new_2d(14.0, 14.0),
Coordinate::new_2d(10.0, 14.0),
Coordinate::new_2d(10.0, 10.0),
];
let exterior2 = LineString::new(coords2);
assert!(poly1.is_ok() && exterior2.is_ok());
if let (Ok(p1), Ok(ext2)) = (poly1, exterior2) {
let poly2 = Polygon::new(ext2, vec![]);
assert!(poly2.is_ok());
if let Ok(p2) = poly2 {
let result: crate::error::Result<bool> = intersects(&p1, &p2);
assert!(result.is_ok());
if let Ok(do_intersect) = result {
assert!(!do_intersect);
}
}
}
}
#[test]
fn test_segments_intersect() {
let p1 = Coordinate::new_2d(0.0, 0.0);
let p2 = Coordinate::new_2d(2.0, 2.0);
let p3 = Coordinate::new_2d(0.0, 2.0);
let p4 = Coordinate::new_2d(2.0, 0.0);
assert!(segments_intersect(&p1, &p2, &p3, &p4));
}
#[test]
fn test_segments_no_intersect() {
let p1 = Coordinate::new_2d(0.0, 0.0);
let p2 = Coordinate::new_2d(2.0, 0.0);
let p3 = Coordinate::new_2d(0.0, 1.0);
let p4 = Coordinate::new_2d(2.0, 1.0);
assert!(!segments_intersect(&p1, &p2, &p3, &p4));
}
#[test]
fn test_ray_casting() {
let ring = vec![
Coordinate::new_2d(0.0, 0.0),
Coordinate::new_2d(4.0, 0.0),
Coordinate::new_2d(4.0, 4.0),
Coordinate::new_2d(0.0, 4.0),
Coordinate::new_2d(0.0, 0.0),
];
let inside = Coordinate::new_2d(2.0, 2.0);
assert!(ray_casting_test(&inside, &ring));
let outside = Coordinate::new_2d(5.0, 5.0);
assert!(!ray_casting_test(&outside, &ring));
}
#[test]
fn test_winding_number() {
let ring = vec![
Coordinate::new_2d(0.0, 0.0),
Coordinate::new_2d(4.0, 0.0),
Coordinate::new_2d(4.0, 4.0),
Coordinate::new_2d(0.0, 4.0),
Coordinate::new_2d(0.0, 0.0),
];
let inside = Coordinate::new_2d(2.0, 2.0);
assert!(winding_number_test(&inside, &ring));
let outside = Coordinate::new_2d(5.0, 5.0);
assert!(!winding_number_test(&outside, &ring));
}
#[test]
fn test_overlaps_polygons_partial() {
let poly1 = create_square();
assert!(poly1.is_ok());
let coords2 = vec![
Coordinate::new_2d(2.0, 2.0),
Coordinate::new_2d(6.0, 2.0),
Coordinate::new_2d(6.0, 6.0),
Coordinate::new_2d(2.0, 6.0),
Coordinate::new_2d(2.0, 2.0),
];
let exterior2 = LineString::new(coords2);
assert!(exterior2.is_ok());
if let (Ok(p1), Ok(ext2)) = (poly1, exterior2) {
let poly2 = Polygon::new(ext2, vec![]);
assert!(poly2.is_ok());
if let Ok(p2) = poly2 {
let result = overlaps(&p1, &p2);
assert!(result.is_ok());
if let Ok(do_overlap) = result {
assert!(do_overlap, "Partially overlapping polygons should overlap");
}
}
}
}
#[test]
fn test_overlaps_polygons_disjoint() {
let poly1 = create_square();
assert!(poly1.is_ok());
let coords2 = vec![
Coordinate::new_2d(10.0, 10.0),
Coordinate::new_2d(14.0, 10.0),
Coordinate::new_2d(14.0, 14.0),
Coordinate::new_2d(10.0, 14.0),
Coordinate::new_2d(10.0, 10.0),
];
let exterior2 = LineString::new(coords2);
assert!(exterior2.is_ok());
if let (Ok(p1), Ok(ext2)) = (poly1, exterior2) {
let poly2 = Polygon::new(ext2, vec![]);
assert!(poly2.is_ok());
if let Ok(p2) = poly2 {
let result = overlaps(&p1, &p2);
assert!(result.is_ok());
if let Ok(do_overlap) = result {
assert!(!do_overlap, "Disjoint polygons should not overlap");
}
}
}
}
#[test]
fn test_overlaps_polygons_contained() {
let poly1 = create_square();
assert!(poly1.is_ok());
let coords2 = vec![
Coordinate::new_2d(1.0, 1.0),
Coordinate::new_2d(3.0, 1.0),
Coordinate::new_2d(3.0, 3.0),
Coordinate::new_2d(1.0, 3.0),
Coordinate::new_2d(1.0, 1.0),
];
let exterior2 = LineString::new(coords2);
assert!(exterior2.is_ok());
if let (Ok(p1), Ok(ext2)) = (poly1, exterior2) {
let poly2 = Polygon::new(ext2, vec![]);
assert!(poly2.is_ok());
if let Ok(p2) = poly2 {
let result = overlaps(&p1, &p2);
assert!(result.is_ok());
if let Ok(do_overlap) = result {
assert!(!do_overlap, "Contained polygons should not overlap");
}
}
}
}
#[test]
fn test_overlaps_points() {
let p1 = Point::new(1.0, 2.0);
let p2 = Point::new(1.0, 2.0);
let result = overlaps(&p1, &p2);
assert!(result.is_ok());
if let Ok(do_overlap) = result {
assert!(!do_overlap, "Points should not overlap");
}
}
#[test]
fn test_crosses_polygons() {
let poly1 = create_square();
assert!(poly1.is_ok());
let coords2 = vec![
Coordinate::new_2d(-1.0, 2.0),
Coordinate::new_2d(5.0, 2.0),
Coordinate::new_2d(5.0, 3.0),
Coordinate::new_2d(-1.0, 3.0),
Coordinate::new_2d(-1.0, 2.0),
];
let exterior2 = LineString::new(coords2);
assert!(exterior2.is_ok());
if let (Ok(p1), Ok(ext2)) = (poly1, exterior2) {
let poly2 = Polygon::new(ext2, vec![]);
assert!(poly2.is_ok());
if let Ok(p2) = poly2 {
let result = crosses(&p1, &p2);
assert!(result.is_ok());
if let Ok(do_cross) = result {
assert!(
!do_cross,
"Polygon/Polygon crosses is undefined per OGC, must return false"
);
}
}
}
}
#[test]
fn test_crosses_polygons_disjoint() {
let poly1 = create_square();
assert!(poly1.is_ok());
let coords2 = vec![
Coordinate::new_2d(10.0, 10.0),
Coordinate::new_2d(14.0, 10.0),
Coordinate::new_2d(14.0, 14.0),
Coordinate::new_2d(10.0, 14.0),
Coordinate::new_2d(10.0, 10.0),
];
let exterior2 = LineString::new(coords2);
assert!(exterior2.is_ok());
if let (Ok(p1), Ok(ext2)) = (poly1, exterior2) {
let poly2 = Polygon::new(ext2, vec![]);
assert!(poly2.is_ok());
if let Ok(p2) = poly2 {
let result = crosses(&p1, &p2);
assert!(result.is_ok());
if let Ok(do_cross) = result {
assert!(!do_cross, "Disjoint polygons should not cross");
}
}
}
}
#[test]
fn test_crosses_points() {
let p1 = Point::new(1.0, 2.0);
let p2 = Point::new(3.0, 4.0);
let result = crosses(&p1, &p2);
assert!(result.is_ok());
if let Ok(do_cross) = result {
assert!(!do_cross, "Points should not cross");
}
}
#[test]
fn test_touches_adjacent_polygons() {
let poly1 = create_square();
assert!(poly1.is_ok());
let coords2 = vec![
Coordinate::new_2d(4.0, 0.0),
Coordinate::new_2d(8.0, 0.0),
Coordinate::new_2d(8.0, 4.0),
Coordinate::new_2d(4.0, 4.0),
Coordinate::new_2d(4.0, 0.0),
];
let exterior2 = LineString::new(coords2);
assert!(exterior2.is_ok());
if let (Ok(p1), Ok(ext2)) = (poly1, exterior2) {
let poly2 = Polygon::new(ext2, vec![]);
assert!(poly2.is_ok());
if let Ok(p2) = poly2 {
let result = touches(&p1, &p2);
assert!(result.is_ok());
if let Ok(do_touch) = result {
assert!(do_touch, "Adjacent polygons should touch");
}
}
}
}
#[test]
fn test_within_polygon() {
let poly1 = create_square();
assert!(poly1.is_ok());
let coords2 = vec![
Coordinate::new_2d(1.0, 1.0),
Coordinate::new_2d(3.0, 1.0),
Coordinate::new_2d(3.0, 3.0),
Coordinate::new_2d(1.0, 3.0),
Coordinate::new_2d(1.0, 1.0),
];
let exterior2 = LineString::new(coords2);
assert!(exterior2.is_ok());
if let (Ok(p1), Ok(ext2)) = (poly1, exterior2) {
let poly2 = Polygon::new(ext2, vec![]);
assert!(poly2.is_ok());
if let Ok(p2) = poly2 {
let result = within(&p2, &p1);
assert!(result.is_ok());
if let Ok(is_within) = result {
assert!(is_within, "Small polygon should be within larger polygon");
}
}
}
}
#[test]
fn test_contains_polygon() {
let poly1 = create_square();
assert!(poly1.is_ok());
let coords2 = vec![
Coordinate::new_2d(1.0, 1.0),
Coordinate::new_2d(3.0, 1.0),
Coordinate::new_2d(3.0, 3.0),
Coordinate::new_2d(1.0, 3.0),
Coordinate::new_2d(1.0, 1.0),
];
let exterior2 = LineString::new(coords2);
assert!(exterior2.is_ok());
if let (Ok(p1), Ok(ext2)) = (poly1, exterior2) {
let poly2 = Polygon::new(ext2, vec![]);
assert!(poly2.is_ok());
if let Ok(p2) = poly2 {
let result = contains(&p1, &p2);
assert!(result.is_ok());
if let Ok(does_contain) = result {
assert!(does_contain, "Large polygon should contain smaller polygon");
}
}
}
}
#[test]
fn test_disjoint_polygons_separated() {
let poly1 = create_square();
assert!(poly1.is_ok());
let coords2 = vec![
Coordinate::new_2d(10.0, 10.0),
Coordinate::new_2d(14.0, 10.0),
Coordinate::new_2d(14.0, 14.0),
Coordinate::new_2d(10.0, 14.0),
Coordinate::new_2d(10.0, 10.0),
];
let exterior2 = LineString::new(coords2);
assert!(exterior2.is_ok());
if let (Ok(p1), Ok(ext2)) = (poly1, exterior2) {
let poly2 = Polygon::new(ext2, vec![]);
assert!(poly2.is_ok());
if let Ok(p2) = poly2 {
let result = disjoint(&p1, &p2);
assert!(result.is_ok());
if let Ok(are_disjoint) = result {
assert!(are_disjoint, "Separated polygons should be disjoint");
}
}
}
}
}