///|
/// Named spatial predicates for Dimensionally Extended 9-Intersection Model (DE-9IM).
pub(all) enum SpatialPredicate {
Intersects
Contains
Within
Touches
Overlaps
Disjoint
Equals
Covers
CoveredBy
} derive(Eq, Debug)
///|
/// Parses a string representation into a SpatialPredicate.
pub fn SpatialPredicate::from_string(name : String) -> SpatialPredicate? {
match name {
"intersects" => Some(Intersects)
"contains" => Some(Contains)
"within" => Some(Within)
"touches" => Some(Touches)
"overlaps" => Some(Overlaps)
"disjoint" => Some(Disjoint)
"equals" => Some(Equals)
"covers" => Some(Covers)
"covered_by" => Some(CoveredBy)
_ => None
}
}
///|
/// Converts a SpatialPredicate into its standard name string.
pub fn SpatialPredicate::to_string(self : SpatialPredicate) -> String {
match self {
Intersects => "intersects"
Contains => "contains"
Within => "within"
Touches => "touches"
Overlaps => "overlaps"
Disjoint => "disjoint"
Equals => "equals"
Covers => "covers"
CoveredBy => "covered_by"
}
}
///|
/// Returns true if this rectangle touches another rectangle (boundaries intersect, interiors disjoint).
pub fn Rect::touches(self : Rect, other : Rect) -> Bool {
if !self.intersects(other) {
return false
}
let overlap_x = self.min_x < other.max_x && self.max_x > other.min_x
let overlap_y = self.min_y < other.max_y && self.max_y > other.min_y
!(overlap_x && overlap_y)
}
///|
/// Returns true if this rectangle topologically covers another rectangle.
pub fn Rect::covers(self : Rect, other : Rect) -> Bool {
self.contains_rect(other)
}
///|
/// Returns true if this rectangle is topologically covered by another rectangle.
pub fn Rect::covered_by(self : Rect, other : Rect) -> Bool {
other.contains_rect(self)
}
///|
/// Returns true if this rectangle contains another rectangle with non-empty interior intersection.
pub fn Rect::contains(self : Rect, other : Rect) -> Bool {
if !self.covers(other) {
return false
}
let overlap_x = self.min_x < other.max_x && self.max_x > other.min_x
let overlap_y = self.min_y < other.max_y && self.max_y > other.min_y
overlap_x && overlap_y
}
///|
/// Returns true if this rectangle is within another rectangle.
pub fn Rect::within(self : Rect, other : Rect) -> Bool {
other.contains(self)
}
///|
/// Returns true if two rectangles have identical boundaries.
pub fn Rect::equals_exact(self : Rect, other : Rect) -> Bool {
self.min_x == other.min_x &&
self.min_y == other.min_y &&
self.max_x == other.max_x &&
self.max_y == other.max_y
}
///|
/// Returns true if two rectangles are completely disjoint (no common points).
pub fn Rect::disjoint(self : Rect, other : Rect) -> Bool {
!self.intersects(other)
}
///|
/// Returns true if two rectangles overlap (interiors intersect, but neither covers the other).
pub fn Rect::overlaps(self : Rect, other : Rect) -> Bool {
let overlap_x = self.min_x < other.max_x && self.max_x > other.min_x
let overlap_y = self.min_y < other.max_y && self.max_y > other.min_y
overlap_x && overlap_y && !self.covers(other) && !other.covers(self)
}
///|
/// Evaluates a named spatial predicate between two rectangles.
pub fn Rect::evaluate_predicate(
self : Rect,
other : Rect,
predicate : SpatialPredicate,
) -> Bool {
match predicate {
Intersects => self.intersects(other)
Contains => self.contains(other)
Within => self.within(other)
Touches => self.touches(other)
Overlaps => self.overlaps(other)
Disjoint => self.disjoint(other)
Equals => self.equals_exact(other)
Covers => self.covers(other)
CoveredBy => self.covered_by(other)
}
}
///|
/// Computes the Dimensionally Extended 9-Intersection Model (DE-9IM) matrix string (9 characters).
pub fn Rect::relate(self : Rect, other : Rect) -> String {
let overlap_x = self.min_x < other.max_x && self.max_x > other.min_x
let overlap_y = self.min_y < other.max_y && self.max_y > other.min_y
let c0 = if overlap_x && overlap_y { '2' } else { 'F' }
let b_in_ia = (
other.min_x > self.min_x && other.min_x < self.max_x && overlap_y
) ||
(other.max_x > self.min_x && other.max_x < self.max_x && overlap_y) ||
(other.min_y > self.min_y && other.min_y < self.max_y && overlap_x) ||
(other.max_y > self.min_y && other.max_y < self.max_y && overlap_x)
let c1 = if b_in_ia { '1' } else { 'F' }
let c2 = if other.covers(self) { 'F' } else { '2' }
let a_in_ib = (
self.min_x > other.min_x && self.min_x < other.max_x && overlap_y
) ||
(self.max_x > other.min_x && self.max_x < other.max_x && overlap_y) ||
(self.min_y > other.min_y && self.min_y < other.max_y && overlap_x) ||
(self.max_y > other.min_y && self.max_y < other.max_y && overlap_x)
let c3 = if a_in_ib { '1' } else { 'F' }
let share_1d = (
(
self.min_x == other.min_x ||
self.min_x == other.max_x ||
self.max_x == other.min_x ||
self.max_x == other.max_x
) &&
overlap_y
) ||
(
(
self.min_y == other.min_y ||
self.min_y == other.max_y ||
self.max_y == other.min_y ||
self.max_y == other.max_y
) &&
overlap_x
)
let c4 = if share_1d {
'1'
} else if self.intersects(other) {
'0'
} else {
'F'
}
let a_strictly_in_ib = other.min_x < self.min_x &&
other.max_x > self.max_x &&
other.min_y < self.min_y &&
other.max_y > self.max_y
let c5 = if self.equals_exact(other) || a_strictly_in_ib { 'F' } else { '1' }
let c6 = if self.covers(other) { 'F' } else { '2' }
let b_strictly_in_ia = self.min_x < other.min_x &&
self.max_x > other.max_x &&
self.min_y < other.min_y &&
self.max_y > other.max_y
let c7 = if self.equals_exact(other) || b_strictly_in_ia { 'F' } else { '1' }
let c8 = '2'
let buf = StringBuilder()
buf.write_char(c0)
buf.write_char(c1)
buf.write_char(c2)
buf.write_char(c3)
buf.write_char(c4)
buf.write_char(c5)
buf.write_char(c6)
buf.write_char(c7)
buf.write_char(c8)
buf.to_string()
}
///|
/// Checks whether a 9-character DE-9IM intersection matrix matches an intersection pattern.
pub fn matches_de9im_pattern(matrix : String, pattern : String) -> Bool {
if matrix.length() != 9 || pattern.length() != 9 {
return false
}
for i = 0; i < 9; i = i + 1 {
let p = pattern[i]
let m = matrix[i]
if p == '*' {
continue
}
if p == 'T' {
if m != '0' && m != '1' && m != '2' {
return false
}
continue
}
if p == 'F' {
if m != 'F' {
return false
}
continue
}
if p != m {
return false
}
}
true
}