use crate::error::{AlgorithmError, Result};
use crate::vector::contains::{
point_in_polygon_or_boundary, point_on_polygon_boundary, point_strictly_inside_polygon,
};
use crate::vector::intersection::SegmentIntersection;
use crate::vector::intersection::intersect_segment_segment;
use oxigdal_core::vector::{Coordinate, LineString, Point, Polygon};
use core::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Dimension {
Empty,
Point,
Line,
Area,
DontCare,
}
impl Dimension {
#[must_use]
pub fn to_char(self) -> char {
match self {
Self::Empty => 'F',
Self::Point => '0',
Self::Line => '1',
Self::Area => '2',
Self::DontCare => '*',
}
}
pub fn from_char(c: char) -> Result<Self> {
match c {
'F' | 'f' => Ok(Self::Empty),
'0' => Ok(Self::Point),
'1' => Ok(Self::Line),
'2' => Ok(Self::Area),
'*' => Ok(Self::DontCare),
'T' | 't' => Ok(Self::DontCare), _ => Err(AlgorithmError::InvalidInput(format!(
"invalid DE-9IM character: '{c}'"
))),
}
}
fn matches_pattern(self, pat: char) -> bool {
match pat {
'*' => true,
'T' | 't' => self != Self::Empty,
'F' | 'f' => self == Self::Empty,
'0' => self == Self::Point,
'1' => self == Self::Line,
'2' => self == Self::Area,
_ => false,
}
}
}
impl fmt::Display for Dimension {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_char())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct De9im {
cells: [Dimension; 9],
}
impl De9im {
pub const II: usize = 0;
pub const IB: usize = 1;
pub const IE: usize = 2;
pub const BI: usize = 3;
pub const BB: usize = 4;
pub const BE: usize = 5;
pub const EI: usize = 6;
pub const EB: usize = 7;
pub const EE: usize = 8;
#[must_use]
pub const fn new(cells: [Dimension; 9]) -> Self {
Self { cells }
}
pub fn from_str(s: &str) -> Result<Self> {
let chars: Vec<char> = s.chars().collect();
if chars.len() != 9 {
return Err(AlgorithmError::InvalidInput(format!(
"DE-9IM string must be exactly 9 characters, got {}",
chars.len()
)));
}
let mut cells = [Dimension::Empty; 9];
for (i, &ch) in chars.iter().enumerate() {
cells[i] = Dimension::from_char(ch)?;
}
Ok(Self { cells })
}
#[must_use]
pub fn get(&self, index: usize) -> Dimension {
if index < 9 {
self.cells[index]
} else {
Dimension::Empty
}
}
pub fn set(&mut self, index: usize, dim: Dimension) {
if index < 9 {
self.cells[index] = dim;
}
}
#[must_use]
pub fn to_string_repr(&self) -> String {
self.cells.iter().map(|d| d.to_char()).collect()
}
#[must_use]
pub fn transpose(&self) -> Self {
Self::new([
self.cells[Self::II],
self.cells[Self::BI],
self.cells[Self::EI],
self.cells[Self::IB],
self.cells[Self::BB],
self.cells[Self::EB],
self.cells[Self::IE],
self.cells[Self::BE],
self.cells[Self::EE],
])
}
#[must_use]
pub fn matches(&self, pattern: &str) -> bool {
let chars: Vec<char> = pattern.chars().collect();
if chars.len() != 9 {
return false;
}
self.cells
.iter()
.zip(chars.iter())
.all(|(dim, &pat)| dim.matches_pattern(pat))
}
#[must_use]
pub fn is_equals(&self) -> bool {
self.matches("T*F**FFF*")
}
#[must_use]
pub fn is_disjoint(&self) -> bool {
self.matches("FF*FF****")
}
#[must_use]
pub fn is_intersects(&self) -> bool {
!self.is_disjoint()
}
#[must_use]
pub fn is_touches(&self) -> bool {
self.matches("FT*******") || self.matches("F**T*****") || self.matches("F***T****")
}
#[must_use]
pub fn is_crosses(&self, dim_a: u8, dim_b: u8) -> bool {
if dim_a < dim_b {
self.matches("T*T******")
} else if dim_a > dim_b {
self.transpose().matches("T*T******")
} else if dim_a == 1 && dim_b == 1 {
self.matches("0********")
} else {
false
}
}
#[must_use]
pub fn is_within(&self) -> bool {
self.matches("T*F**F***")
}
#[must_use]
pub fn is_contains(&self) -> bool {
self.matches("T*****FF*")
}
#[must_use]
pub fn is_overlaps(&self, dim_a: u8, dim_b: u8) -> bool {
if dim_a != dim_b {
return false;
}
if dim_a == 1 {
self.matches("1*T***T**")
} else {
self.matches("T*T***T**")
}
}
#[must_use]
pub fn is_covers(&self) -> bool {
self.matches("T*****FF*")
|| self.matches("*T****FF*")
|| self.matches("***T**FF*")
|| self.matches("****T*FF*")
}
#[must_use]
pub fn is_covered_by(&self) -> bool {
self.matches("T*F**F***")
|| self.matches("*TF**F***")
|| self.matches("**FT*F***")
|| self.matches("**F*TF***")
}
}
impl fmt::Display for De9im {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_string_repr())
}
}
pub fn relate_polygons(a: &Polygon, b: &Polygon) -> Result<De9im> {
validate_polygon(a, "relate_polygons", "polygon a")?;
validate_polygon(b, "relate_polygons", "polygon b")?;
let mut matrix = [Dimension::Empty; 9];
matrix[De9im::EE] = Dimension::Area;
let mut a_bdry_in_b_interior = false;
let mut a_bdry_on_b_boundary = false;
let mut a_bdry_in_b_exterior = false;
classify_boundary_against_polygon(
a,
b,
&mut a_bdry_in_b_interior,
&mut a_bdry_on_b_boundary,
&mut a_bdry_in_b_exterior,
);
let mut b_bdry_in_a_interior = false;
let mut b_bdry_on_a_boundary = false;
let mut b_bdry_in_a_exterior = false;
classify_boundary_against_polygon(
b,
a,
&mut b_bdry_in_a_interior,
&mut b_bdry_on_a_boundary,
&mut b_bdry_in_a_exterior,
);
let mut a_int_in_b_interior = false;
let mut a_int_on_b_boundary = false;
let mut a_int_in_b_exterior = false;
let mut b_int_in_a_interior = false;
let mut b_int_on_a_boundary = false;
let mut b_int_in_a_exterior = false;
let a_samples = interior_sample_points(a);
for pt in &a_samples {
classify_point_against_polygon(
pt,
b,
&mut a_int_in_b_interior,
&mut a_int_on_b_boundary,
&mut a_int_in_b_exterior,
);
}
let b_samples = interior_sample_points(b);
for pt in &b_samples {
classify_point_against_polygon(
pt,
a,
&mut b_int_in_a_interior,
&mut b_int_on_a_boundary,
&mut b_int_in_a_exterior,
);
}
let cross_samples = generate_crossing_interior_samples(a, b);
for pt in &cross_samples {
if point_strictly_inside_polygon(pt, a) && point_strictly_inside_polygon(pt, b) {
a_int_in_b_interior = true;
b_int_in_a_interior = true;
}
}
let bb_dim = compute_boundary_boundary_dim(a, b);
if a_int_in_b_interior || b_int_in_a_interior {
matrix[De9im::II] = Dimension::Area;
}
if b_bdry_in_a_interior || a_int_on_b_boundary {
matrix[De9im::IB] = Dimension::Line;
}
if a_int_in_b_exterior {
matrix[De9im::IE] = Dimension::Area;
}
if a_bdry_in_b_interior {
matrix[De9im::BI] = Dimension::Line;
}
if a_bdry_on_b_boundary || b_bdry_on_a_boundary || bb_dim != Dimension::Empty {
matrix[De9im::BB] = bb_dim;
}
if a_bdry_in_b_exterior {
matrix[De9im::BE] = Dimension::Line;
}
if b_int_in_a_exterior {
matrix[De9im::EI] = Dimension::Area;
}
if b_bdry_in_a_exterior {
matrix[De9im::EB] = Dimension::Line;
}
Ok(De9im::new(matrix))
}
pub fn relate_point_polygon(pt: &Point, poly: &Polygon) -> Result<De9im> {
validate_polygon(poly, "relate_point_polygon", "polygon")?;
let coord = &pt.coord;
let mut matrix = [Dimension::Empty; 9];
matrix[De9im::EE] = Dimension::Area;
if point_on_polygon_boundary(coord, poly) {
matrix[De9im::II] = Dimension::Empty; matrix[De9im::IB] = Dimension::Point; matrix[De9im::IE] = Dimension::Empty; matrix[De9im::EI] = Dimension::Area; matrix[De9im::EB] = Dimension::Line; } else if point_strictly_inside_polygon(coord, poly) {
matrix[De9im::II] = Dimension::Point; matrix[De9im::IB] = Dimension::Empty;
matrix[De9im::IE] = Dimension::Empty;
matrix[De9im::EI] = Dimension::Area;
matrix[De9im::EB] = Dimension::Line;
} else {
matrix[De9im::II] = Dimension::Empty;
matrix[De9im::IB] = Dimension::Empty;
matrix[De9im::IE] = Dimension::Point;
matrix[De9im::EI] = Dimension::Area;
matrix[De9im::EB] = Dimension::Line;
}
Ok(De9im::new(matrix))
}
pub fn relate_line_polygon(line: &LineString, poly: &Polygon) -> Result<De9im> {
validate_polygon(poly, "relate_line_polygon", "polygon")?;
if line.coords.len() < 2 {
return Err(AlgorithmError::InsufficientData {
operation: "relate_line_polygon",
message: "line must have at least 2 coordinates".to_string(),
});
}
let mut matrix = [Dimension::Empty; 9];
matrix[De9im::EE] = Dimension::Area;
let mut line_has_interior_in_poly_interior = false;
let mut line_has_interior_on_poly_boundary = false;
let mut line_has_interior_in_poly_exterior = false;
for coord in &line.coords {
if point_on_polygon_boundary(coord, poly) {
line_has_interior_on_poly_boundary = true;
} else if point_strictly_inside_polygon(coord, poly) {
line_has_interior_in_poly_interior = true;
} else {
line_has_interior_in_poly_exterior = true;
}
}
for i in 0..line.coords.len().saturating_sub(1) {
let mid = midpoint(&line.coords[i], &line.coords[i + 1]);
if point_on_polygon_boundary(&mid, poly) {
line_has_interior_on_poly_boundary = true;
} else if point_strictly_inside_polygon(&mid, poly) {
line_has_interior_in_poly_interior = true;
} else {
line_has_interior_in_poly_exterior = true;
}
}
let line_is_closed = line.coords.len() >= 3
&& coords_equal(&line.coords[0], &line.coords[line.coords.len() - 1]);
let mut line_boundary_in_poly_interior = false;
let mut line_boundary_on_poly_boundary = false;
let mut line_boundary_in_poly_exterior = false;
if !line_is_closed && line.coords.len() >= 2 {
let endpoints = [&line.coords[0], &line.coords[line.coords.len() - 1]];
for ep in &endpoints {
if point_on_polygon_boundary(ep, poly) {
line_boundary_on_poly_boundary = true;
} else if point_strictly_inside_polygon(ep, poly) {
line_boundary_in_poly_interior = true;
} else {
line_boundary_in_poly_exterior = true;
}
}
}
let boundary_intersections = count_boundary_line_intersections(line, poly);
if line_has_interior_in_poly_interior {
matrix[De9im::II] = Dimension::Line;
}
if line_has_interior_on_poly_boundary || boundary_intersections > 0 {
if has_segment_on_polygon_boundary(line, poly) {
matrix[De9im::IB] = Dimension::Line;
} else {
matrix[De9im::IB] = Dimension::Point;
}
}
if line_has_interior_in_poly_exterior {
matrix[De9im::IE] = Dimension::Line;
}
if line_boundary_in_poly_interior {
matrix[De9im::BI] = Dimension::Point;
}
if line_boundary_on_poly_boundary {
matrix[De9im::BB] = Dimension::Point;
}
if line_boundary_in_poly_exterior {
matrix[De9im::BE] = Dimension::Point;
}
matrix[De9im::EI] = Dimension::Area;
matrix[De9im::EB] = Dimension::Line;
Ok(De9im::new(matrix))
}
pub fn relate(a: &Polygon, b: &Polygon) -> Result<De9im> {
relate_polygons(a, b)
}
pub trait EqualsPredicate {
fn equals_topo(&self, other: &Self) -> Result<bool>;
}
pub trait CoversPredicate {
fn covers(&self, other: &Self) -> Result<bool>;
}
pub trait CoveredByPredicate {
fn covered_by(&self, other: &Self) -> Result<bool>;
}
impl EqualsPredicate for Polygon {
fn equals_topo(&self, other: &Self) -> Result<bool> {
let m = relate_polygons(self, other)?;
Ok(m.is_equals())
}
}
impl CoversPredicate for Polygon {
fn covers(&self, other: &Self) -> Result<bool> {
let m = relate_polygons(self, other)?;
Ok(m.is_covers())
}
}
impl CoveredByPredicate for Polygon {
fn covered_by(&self, other: &Self) -> Result<bool> {
let m = relate_polygons(self, other)?;
Ok(m.is_covered_by())
}
}
impl EqualsPredicate for Point {
fn equals_topo(&self, other: &Self) -> Result<bool> {
Ok(coords_equal(&self.coord, &other.coord))
}
}
impl CoversPredicate for Point {
fn covers(&self, other: &Self) -> Result<bool> {
Ok(coords_equal(&self.coord, &other.coord))
}
}
impl CoveredByPredicate for Point {
fn covered_by(&self, other: &Self) -> Result<bool> {
Ok(coords_equal(&self.coord, &other.coord))
}
}
fn validate_polygon(poly: &Polygon, operation: &'static str, name: &str) -> Result<()> {
if poly.exterior.coords.len() < 4 {
return Err(AlgorithmError::InsufficientData {
operation,
message: format!("{name} exterior must have at least 4 coordinates"),
});
}
Ok(())
}
fn coords_equal(a: &Coordinate, b: &Coordinate) -> bool {
(a.x - b.x).abs() < f64::EPSILON && (a.y - b.y).abs() < f64::EPSILON
}
fn midpoint(a: &Coordinate, b: &Coordinate) -> Coordinate {
Coordinate::new_2d((a.x + b.x) * 0.5, (a.y + b.y) * 0.5)
}
fn classify_point_against_polygon(
pt: &Coordinate,
poly: &Polygon,
in_interior: &mut bool,
on_boundary: &mut bool,
in_exterior: &mut bool,
) {
if point_on_polygon_boundary(pt, poly) {
*on_boundary = true;
} else if point_strictly_inside_polygon(pt, poly) {
*in_interior = true;
} else {
*in_exterior = true;
}
}
fn classify_boundary_against_polygon(
source: &Polygon,
target: &Polygon,
in_interior: &mut bool,
on_boundary: &mut bool,
in_exterior: &mut bool,
) {
for coord in &source.exterior.coords {
classify_point_against_polygon(coord, target, in_interior, on_boundary, in_exterior);
}
for i in 0..source.exterior.coords.len().saturating_sub(1) {
let mid = midpoint(&source.exterior.coords[i], &source.exterior.coords[i + 1]);
classify_point_against_polygon(&mid, target, in_interior, on_boundary, in_exterior);
}
}
fn generate_crossing_interior_samples(a: &Polygon, b: &Polygon) -> Vec<Coordinate> {
let mut samples = Vec::new();
let a_coords = &a.exterior.coords;
let b_coords = &b.exterior.coords;
let eps = 1e-6;
for i in 0..a_coords.len().saturating_sub(1) {
for j in 0..b_coords.len().saturating_sub(1) {
if let SegmentIntersection::Point(pt) = intersect_segment_segment(
&a_coords[i],
&a_coords[i + 1],
&b_coords[j],
&b_coords[j + 1],
) {
let a_dx = a_coords[i + 1].x - a_coords[i].x;
let a_dy = a_coords[i + 1].y - a_coords[i].y;
let b_dx = b_coords[j + 1].x - b_coords[j].x;
let b_dy = b_coords[j + 1].y - b_coords[j].y;
let normals = [(a_dy, -a_dx), (-a_dy, a_dx), (b_dy, -b_dx), (-b_dy, b_dx)];
for &(nx_a, ny_a) in &normals[..2] {
for &(nx_b, ny_b) in &normals[2..] {
let nx = nx_a + nx_b;
let ny = ny_a + ny_b;
let len = (nx * nx + ny * ny).sqrt();
if len < f64::EPSILON {
continue;
}
let candidate =
Coordinate::new_2d(pt.x + eps * nx / len, pt.y + eps * ny / len);
if point_strictly_inside_polygon(&candidate, a)
&& point_strictly_inside_polygon(&candidate, b)
{
samples.push(candidate);
}
}
}
}
}
if samples.len() >= 4 {
break;
}
}
samples
}
fn interior_sample_points(poly: &Polygon) -> Vec<Coordinate> {
let mut samples = Vec::new();
let n = poly.exterior.coords.len();
if n < 4 {
return samples;
}
let (mut cx, mut cy) = (0.0_f64, 0.0_f64);
let vertex_count = n - 1;
for coord in &poly.exterior.coords[..vertex_count] {
cx += coord.x;
cy += coord.y;
}
if vertex_count > 0 {
cx /= vertex_count as f64;
cy /= vertex_count as f64;
}
let centroid = Coordinate::new_2d(cx, cy);
if point_strictly_inside_polygon(¢roid, poly) {
samples.push(centroid);
}
for i in 0..vertex_count {
let mid = midpoint(&poly.exterior.coords[i], &poly.exterior.coords[i + 1]);
let pulled = Coordinate::new_2d(mid.x + (cx - mid.x) * 0.1, mid.y + (cy - mid.y) * 0.1);
if point_strictly_inside_polygon(&pulled, poly) {
samples.push(pulled);
if samples.len() >= 5 {
break;
}
}
}
if samples.is_empty() {
if let Some((min_x, min_y, max_x, max_y)) = poly.bounds() {
let step_x = (max_x - min_x) / 5.0;
let step_y = (max_y - min_y) / 5.0;
'outer: for ix in 1..5 {
for iy in 1..5 {
let pt = Coordinate::new_2d(
min_x + step_x * (ix as f64),
min_y + step_y * (iy as f64),
);
if point_strictly_inside_polygon(&pt, poly) {
samples.push(pt);
if samples.len() >= 3 {
break 'outer;
}
}
}
}
}
}
samples
}
fn compute_boundary_boundary_dim(a: &Polygon, b: &Polygon) -> Dimension {
let a_coords = &a.exterior.coords;
let b_coords = &b.exterior.coords;
let mut has_point_intersection = false;
let mut has_line_intersection = false;
for i in 0..a_coords.len().saturating_sub(1) {
for j in 0..b_coords.len().saturating_sub(1) {
match intersect_segment_segment(
&a_coords[i],
&a_coords[i + 1],
&b_coords[j],
&b_coords[j + 1],
) {
SegmentIntersection::Point(_) => {
has_point_intersection = true;
}
SegmentIntersection::Overlap(_, _) => {
has_line_intersection = true;
}
SegmentIntersection::None => {}
}
}
}
if has_line_intersection {
Dimension::Line
} else if has_point_intersection {
Dimension::Point
} else {
Dimension::Empty
}
}
fn count_boundary_line_intersections(line: &LineString, poly: &Polygon) -> usize {
let mut count = 0;
let ring = &poly.exterior.coords;
for i in 0..line.coords.len().saturating_sub(1) {
for j in 0..ring.len().saturating_sub(1) {
match intersect_segment_segment(
&line.coords[i],
&line.coords[i + 1],
&ring[j],
&ring[j + 1],
) {
SegmentIntersection::Point(_) | SegmentIntersection::Overlap(_, _) => {
count += 1;
}
SegmentIntersection::None => {}
}
}
}
count
}
fn has_segment_on_polygon_boundary(line: &LineString, poly: &Polygon) -> bool {
let ring = &poly.exterior.coords;
for i in 0..line.coords.len().saturating_sub(1) {
for j in 0..ring.len().saturating_sub(1) {
if let SegmentIntersection::Overlap(_, _) = intersect_segment_segment(
&line.coords[i],
&line.coords[i + 1],
&ring[j],
&ring[j + 1],
) {
return true;
}
}
}
false
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::AlgorithmError;
type TestResult = core::result::Result<(), Box<dyn std::error::Error>>;
fn make_rect(x0: f64, y0: f64, x1: f64, y1: f64) -> Result<Polygon> {
let coords = vec![
Coordinate::new_2d(x0, y0),
Coordinate::new_2d(x1, y0),
Coordinate::new_2d(x1, y1),
Coordinate::new_2d(x0, y1),
Coordinate::new_2d(x0, y0),
];
let ext = LineString::new(coords).map_err(AlgorithmError::Core)?;
Polygon::new(ext, vec![]).map_err(AlgorithmError::Core)
}
#[test]
fn test_dimension_to_char() {
assert_eq!(Dimension::Empty.to_char(), 'F');
assert_eq!(Dimension::Point.to_char(), '0');
assert_eq!(Dimension::Line.to_char(), '1');
assert_eq!(Dimension::Area.to_char(), '2');
assert_eq!(Dimension::DontCare.to_char(), '*');
}
#[test]
fn test_dimension_from_char() -> TestResult {
assert_eq!(Dimension::from_char('F')?, Dimension::Empty);
assert_eq!(Dimension::from_char('0')?, Dimension::Point);
assert_eq!(Dimension::from_char('1')?, Dimension::Line);
assert_eq!(Dimension::from_char('2')?, Dimension::Area);
assert_eq!(Dimension::from_char('*')?, Dimension::DontCare);
assert!(Dimension::from_char('X').is_err());
Ok(())
}
#[test]
fn test_de9im_from_str() -> TestResult {
let m = De9im::from_str("212101212")?;
assert_eq!(m.get(De9im::II), Dimension::Area);
assert_eq!(m.get(De9im::IB), Dimension::Line);
assert_eq!(m.get(De9im::IE), Dimension::Area);
assert_eq!(m.get(De9im::BI), Dimension::Line);
assert_eq!(m.get(De9im::BB), Dimension::Point);
assert_eq!(m.get(De9im::BE), Dimension::Line);
assert_eq!(m.get(De9im::EI), Dimension::Area);
assert_eq!(m.get(De9im::EB), Dimension::Line);
assert_eq!(m.get(De9im::EE), Dimension::Area);
Ok(())
}
#[test]
fn test_de9im_display() -> TestResult {
let m = De9im::from_str("212101212")?;
assert_eq!(format!("{m}"), "212101212");
Ok(())
}
#[test]
fn test_de9im_matches_basic() -> TestResult {
let m = De9im::from_str("212101212")?;
assert!(m.matches("2*2***2*2")); assert!(m.matches("T*T***T**")); assert!(!m.matches("FF*FF****")); Ok(())
}
#[test]
fn test_de9im_transpose() -> TestResult {
let m = De9im::from_str("212101212")?;
let t = m.transpose();
assert_eq!(t.get(De9im::II), Dimension::Area); assert_eq!(t.get(De9im::IB), Dimension::Line); assert_eq!(t.get(De9im::IE), Dimension::Area); assert_eq!(t.get(De9im::BI), Dimension::Line); assert_eq!(t.get(De9im::BB), Dimension::Point); assert_eq!(t.get(De9im::BE), Dimension::Line); assert_eq!(t.get(De9im::EI), Dimension::Area); assert_eq!(t.get(De9im::EB), Dimension::Line); assert_eq!(t.get(De9im::EE), Dimension::Area); Ok(())
}
#[test]
fn test_de9im_from_str_invalid_length() {
assert!(De9im::from_str("212").is_err());
assert!(De9im::from_str("2121012121").is_err());
}
#[test]
fn test_de9im_get_out_of_bounds() {
let m = De9im::new([Dimension::Empty; 9]);
assert_eq!(m.get(99), Dimension::Empty);
}
#[test]
fn test_is_equals_synthetic() -> TestResult {
let m = De9im::from_str("2FFF1FFF2")?;
assert!(m.is_equals());
let m2 = De9im::from_str("212101212")?;
assert!(!m2.is_equals());
Ok(())
}
#[test]
fn test_is_disjoint_synthetic() -> TestResult {
let m = De9im::from_str("FF2FF1212")?;
assert!(m.is_disjoint());
assert!(!m.is_intersects());
let m2 = De9im::from_str("212101212")?;
assert!(!m2.is_disjoint());
assert!(m2.is_intersects());
Ok(())
}
#[test]
fn test_is_touches_synthetic() -> TestResult {
let m = De9im::from_str("F11FF0212")?;
assert!(m.is_touches());
let m2 = De9im::from_str("212101212")?;
assert!(!m2.is_touches());
Ok(())
}
#[test]
fn test_is_crosses_synthetic() -> TestResult {
let m = De9im::from_str("1020F1102")?;
assert!(m.is_crosses(1, 2));
assert!(!m.is_crosses(2, 2));
let m2 = De9im::from_str("0FFFFFFFF")?;
assert!(m2.is_crosses(1, 1));
Ok(())
}
#[test]
fn test_is_within_synthetic() -> TestResult {
let m = De9im::from_str("2FF1FF212")?;
assert!(m.is_within());
assert!(!m.is_contains()); Ok(())
}
#[test]
fn test_is_contains_synthetic() -> TestResult {
let m = De9im::from_str("212101FF2")?;
assert!(m.is_contains());
Ok(())
}
#[test]
fn test_is_overlaps_synthetic() -> TestResult {
let m = De9im::from_str("212101212")?;
assert!(m.is_overlaps(2, 2));
assert!(!m.is_overlaps(1, 2));
let m2 = De9im::from_str("1FT1FFT1F")?;
assert!(m2.is_overlaps(1, 1));
Ok(())
}
#[test]
fn test_is_covers_synthetic() -> TestResult {
let m = De9im::from_str("2FF1FFFF2")?;
assert!(m.is_covers());
Ok(())
}
#[test]
fn test_is_covered_by_synthetic() -> TestResult {
let m = De9im::from_str("2FF0FF212")?;
assert!(m.is_covered_by());
Ok(())
}
#[test]
fn test_relate_disjoint_squares() -> TestResult {
let a = make_rect(0.0, 0.0, 4.0, 4.0)?;
let b = make_rect(10.0, 10.0, 14.0, 14.0)?;
let m = relate_polygons(&a, &b)?;
assert!(m.is_disjoint(), "disjoint squares: matrix = {m}");
assert!(!m.is_intersects());
assert_eq!(m.get(De9im::II), Dimension::Empty);
assert_eq!(m.get(De9im::EE), Dimension::Area);
Ok(())
}
#[test]
fn test_relate_overlapping_squares() -> TestResult {
let a = make_rect(0.0, 0.0, 4.0, 4.0)?;
let b = make_rect(2.0, 2.0, 6.0, 6.0)?;
let m = relate_polygons(&a, &b)?;
assert!(m.is_intersects(), "overlapping squares: matrix = {m}");
assert!(!m.is_disjoint());
assert!(m.is_overlaps(2, 2), "overlapping squares: matrix = {m}");
assert!(!m.is_contains());
assert!(!m.is_within());
assert_eq!(
m.get(De9im::II),
Dimension::Area,
"II = {}",
m.get(De9im::II)
);
assert_eq!(
m.get(De9im::IE),
Dimension::Area,
"IE = {}",
m.get(De9im::IE)
);
assert_eq!(
m.get(De9im::EI),
Dimension::Area,
"EI = {}",
m.get(De9im::EI)
);
Ok(())
}
#[test]
fn test_relate_contained_square() -> TestResult {
let a = make_rect(0.0, 0.0, 10.0, 10.0)?;
let b = make_rect(2.0, 2.0, 8.0, 8.0)?;
let m = relate_polygons(&a, &b)?;
assert!(m.is_contains(), "a contains b: matrix = {m}");
let mt = m.transpose();
assert!(mt.is_within(), "b within a: transposed matrix = {mt}");
Ok(())
}
#[test]
fn test_relate_touching_squares() -> TestResult {
let a = make_rect(0.0, 0.0, 4.0, 4.0)?;
let b = make_rect(4.0, 0.0, 8.0, 4.0)?;
let m = relate_polygons(&a, &b)?;
assert!(m.is_touches(), "touching squares: matrix = {m}");
assert!(!m.is_disjoint());
assert!(!m.is_overlaps(2, 2));
Ok(())
}
#[test]
fn test_relate_identical_polygons() -> TestResult {
let a = make_rect(0.0, 0.0, 4.0, 4.0)?;
let b = make_rect(0.0, 0.0, 4.0, 4.0)?;
let m = relate_polygons(&a, &b)?;
assert!(m.is_equals(), "identical polygons: matrix = {m}");
assert!(!m.is_disjoint());
Ok(())
}
#[test]
fn test_relate_point_inside_polygon() -> TestResult {
let poly = make_rect(0.0, 0.0, 10.0, 10.0)?;
let pt = Point::new(5.0, 5.0);
let m = relate_point_polygon(&pt, &poly)?;
assert!(m.is_within(), "point inside polygon: matrix = {m}");
Ok(())
}
#[test]
fn test_relate_point_on_boundary() -> TestResult {
let poly = make_rect(0.0, 0.0, 10.0, 10.0)?;
let pt = Point::new(0.0, 5.0);
let m = relate_point_polygon(&pt, &poly)?;
assert!(m.is_touches(), "point on boundary: matrix = {m}");
Ok(())
}
#[test]
fn test_relate_point_outside_polygon() -> TestResult {
let poly = make_rect(0.0, 0.0, 10.0, 10.0)?;
let pt = Point::new(20.0, 20.0);
let m = relate_point_polygon(&pt, &poly)?;
assert!(m.is_disjoint(), "point outside polygon: matrix = {m}");
Ok(())
}
#[test]
fn test_relate_line_crossing_polygon() -> TestResult {
let poly = make_rect(0.0, 0.0, 10.0, 10.0)?;
let line_coords = vec![Coordinate::new_2d(-5.0, 5.0), Coordinate::new_2d(15.0, 5.0)];
let line = LineString::new(line_coords).map_err(AlgorithmError::Core)?;
let m = relate_line_polygon(&line, &poly)?;
assert!(m.is_crosses(1, 2), "line crossing polygon: matrix = {m}");
Ok(())
}
#[test]
fn test_relate_line_inside_polygon() -> TestResult {
let poly = make_rect(0.0, 0.0, 10.0, 10.0)?;
let line_coords = vec![Coordinate::new_2d(2.0, 5.0), Coordinate::new_2d(8.0, 5.0)];
let line = LineString::new(line_coords).map_err(AlgorithmError::Core)?;
let m = relate_line_polygon(&line, &poly)?;
assert!(m.is_within(), "line inside polygon: matrix = {m}");
Ok(())
}
#[test]
fn test_relate_line_outside_polygon() -> TestResult {
let poly = make_rect(0.0, 0.0, 10.0, 10.0)?;
let line_coords = vec![
Coordinate::new_2d(20.0, 20.0),
Coordinate::new_2d(30.0, 30.0),
];
let line = LineString::new(line_coords).map_err(AlgorithmError::Core)?;
let m = relate_line_polygon(&line, &poly)?;
assert!(m.is_disjoint(), "line outside polygon: matrix = {m}");
Ok(())
}
#[test]
fn test_equals_predicate_polygon() -> TestResult {
let a = make_rect(0.0, 0.0, 4.0, 4.0)?;
let b = make_rect(0.0, 0.0, 4.0, 4.0)?;
assert!(a.equals_topo(&b)?);
let c = make_rect(1.0, 1.0, 5.0, 5.0)?;
assert!(!a.equals_topo(&c)?);
Ok(())
}
#[test]
fn test_covers_predicate_polygon() -> TestResult {
let a = make_rect(0.0, 0.0, 10.0, 10.0)?;
let b = make_rect(2.0, 2.0, 8.0, 8.0)?;
assert!(a.covers(&b)?);
assert!(!b.covers(&a)?);
Ok(())
}
#[test]
fn test_covered_by_predicate_polygon() -> TestResult {
let a = make_rect(2.0, 2.0, 8.0, 8.0)?;
let b = make_rect(0.0, 0.0, 10.0, 10.0)?;
assert!(a.covered_by(&b)?);
assert!(!b.covered_by(&a)?);
Ok(())
}
#[test]
fn test_equals_predicate_point() -> TestResult {
let a = Point::new(1.0, 2.0);
let b = Point::new(1.0, 2.0);
let c = Point::new(3.0, 4.0);
assert!(a.equals_topo(&b)?);
assert!(!a.equals_topo(&c)?);
Ok(())
}
#[test]
fn test_pattern_matching_roundtrip() -> TestResult {
let patterns = [
"T*F**FFF*", "FF*FF****", "FT*******", "T*T******", "T*F**F***", "T*****FF*", "T*T***T**", ];
for pat in &patterns {
let m = De9im::from_str(pat)?;
assert!(
m.matches(pat),
"pattern {pat} should match itself, matrix = {m}"
);
}
Ok(())
}
#[test]
fn test_polygon_polygon_crosses_always_false_via_de9im() -> TestResult {
let a = make_rect(0.0, 0.0, 4.0, 4.0)?;
let b = make_rect(2.0, 2.0, 6.0, 6.0)?;
let m = relate_polygons(&a, &b)?;
assert!(
!m.is_crosses(2, 2),
"polygon/polygon crosses must always be false per OGC"
);
Ok(())
}
#[test]
fn test_relate_invalid_polygon() {
let coords = vec![
Coordinate::new_2d(0.0, 0.0),
Coordinate::new_2d(1.0, 0.0),
Coordinate::new_2d(0.0, 0.0),
];
let ext = LineString::new(coords);
if let Ok(e) = ext {
if let Ok(bad_poly) = Polygon::new(e, vec![]) {
let good = make_rect(0.0, 0.0, 10.0, 10.0);
if let Ok(g) = good {
let result = relate_polygons(&bad_poly, &g);
assert!(result.is_err());
}
}
}
}
#[test]
fn test_relate_line_polygon_invalid_line() {
let poly_result = make_rect(0.0, 0.0, 10.0, 10.0);
if let Ok(poly) = poly_result {
let coords = vec![Coordinate::new_2d(5.0, 5.0)];
let line_result = LineString::new(coords);
assert!(line_result.is_err());
}
}
#[test]
fn test_dimension_display() {
assert_eq!(format!("{}", Dimension::Empty), "F");
assert_eq!(format!("{}", Dimension::Point), "0");
assert_eq!(format!("{}", Dimension::Line), "1");
assert_eq!(format!("{}", Dimension::Area), "2");
}
}