use boost_geometry::cs::Cartesian;
use boost_geometry::model::{
Box as ModelBox, DynGeometry, DynGeometryCollection, Linestring, MultiLinestring, MultiPoint,
MultiPolygon, Point2D, Polygon, Ring, Segment, polygon,
};
use boost_geometry::overlay::{
De9im, Dimension, OverlayError, RelateError, crosses, overlaps, relate, relation, touches,
};
use boost_geometry::trait_::Polygon as _;
type P = Point2D<f64, Cartesian>;
fn square() -> Polygon<P> {
polygon![[(0.0, 0.0), (4.0, 0.0), (4.0, 4.0), (0.0, 4.0), (0.0, 0.0)]]
}
#[test]
fn point_point_relations_use_point_dimension() {
let equal = relation(&P::new(1.0, 1.0), &P::new(1.0, 1.0)).unwrap();
assert_eq!(equal.interior_interior(), Dimension::Point);
let disjoint = relation(&P::new(1.0, 1.0), &P::new(2.0, 2.0)).unwrap();
assert_eq!(disjoint.interior_interior(), Dimension::Empty);
assert_eq!(disjoint.interior_exterior(), Dimension::Point);
assert_eq!(disjoint.exterior_interior(), Dimension::Point);
}
#[test]
fn point_polygon_distinguishes_interior_and_boundary() {
let polygon = square();
let inside = relation(&P::new(2.0, 2.0), &polygon).unwrap();
assert_eq!(inside.interior_interior(), Dimension::Point);
let boundary = relation(&P::new(0.0, 2.0), &polygon).unwrap();
assert_eq!(boundary.interior_interior(), Dimension::Empty);
assert_eq!(boundary.m[0][1], Dimension::Point);
assert!(touches(&P::new(0.0, 2.0), &polygon).unwrap());
assert!(relate(&P::new(2.0, 2.0), &polygon, "T********").unwrap());
let reversed = relation(&polygon, &P::new(0.0, 2.0)).unwrap();
for row in 0..3 {
for column in 0..3 {
assert_eq!(boundary.m[row][column], reversed.m[column][row]);
}
}
}
#[test]
fn crossing_linestrings_have_point_interior_intersection() {
let first = Linestring::from_vec(vec![P::new(0.0, 0.0), P::new(4.0, 4.0)]);
let second = Linestring::from_vec(vec![P::new(0.0, 4.0), P::new(4.0, 0.0)]);
let matrix = relation(&first, &second).unwrap();
assert_eq!(matrix.interior_interior(), Dimension::Point);
assert_eq!(matrix.interior_exterior(), Dimension::Curve);
assert_eq!(matrix.exterior_interior(), Dimension::Curve);
}
#[test]
fn linestring_polygon_reports_curve_and_boundary_crossings() {
let line = Linestring::from_vec(vec![P::new(-1.0, 2.0), P::new(5.0, 2.0)]);
let matrix = relation(&line, &square()).unwrap();
assert_eq!(matrix.interior_interior(), Dimension::Curve);
assert_eq!(matrix.m[0][1], Dimension::Point);
assert_eq!(matrix.interior_exterior(), Dimension::Curve);
assert!(crosses(&line, &square()).unwrap());
}
fn box_at(x0: f64, y0: f64, x1: f64, y1: f64) -> Polygon<P> {
polygon![[(x0, y0), (x1, y0), (x1, y1), (x0, y1), (x0, y0)]]
}
#[test]
fn areal_touches_distinguish_shared_edges_and_vertices() {
let first = box_at(0.0, 0.0, 4.0, 4.0);
let edge_touch = box_at(4.0, 0.0, 8.0, 4.0);
let edge_matrix = relation(&first, &edge_touch).unwrap();
assert_eq!(edge_matrix.interior_interior(), Dimension::Empty);
assert_eq!(edge_matrix.boundary_boundary(), Dimension::Curve);
assert!(touches(&first, &edge_touch).unwrap());
assert!(!overlaps(&first, &edge_touch).unwrap());
let vertex_touch = box_at(4.0, 4.0, 8.0, 8.0);
let vertex_matrix = relation(&first, &vertex_touch).unwrap();
assert_eq!(vertex_matrix.interior_interior(), Dimension::Empty);
assert_eq!(vertex_matrix.boundary_boundary(), Dimension::Point);
assert!(touches(&first, &vertex_touch).unwrap());
}
#[test]
fn collinear_boundaries_can_still_overlap_in_area() {
let first = box_at(0.0, 0.0, 3.0, 1.0);
let second = box_at(2.0, 0.0, 5.0, 1.0);
let matrix = relation(&first, &second).unwrap();
assert_eq!(matrix.interior_interior(), Dimension::Area);
assert_eq!(matrix.boundary_boundary(), Dimension::Curve);
assert!(overlaps(&first, &second).unwrap());
assert!(!touches(&first, &second).unwrap());
}
#[test]
fn areal_relation_respects_polygon_holes() {
let donut: Polygon<P> = polygon![
[
(0.0, 0.0),
(10.0, 0.0),
(10.0, 10.0),
(0.0, 10.0),
(0.0, 0.0)
],
[(3.0, 3.0), (7.0, 3.0), (7.0, 7.0), (3.0, 7.0), (3.0, 3.0)]
];
let island = box_at(4.0, 4.0, 6.0, 6.0);
let matrix = relation(&donut, &island).unwrap();
assert_eq!(matrix.interior_interior(), Dimension::Empty);
assert_eq!(matrix.boundary_boundary(), Dimension::Empty);
assert!(!touches(&donut, &island).unwrap());
assert!(!overlaps(&donut, &island).unwrap());
let point_in_hole = relation(&P::new(5.0, 5.0), &donut).unwrap();
assert_eq!(point_in_hole.interior_interior(), Dimension::Empty);
assert_eq!(point_in_hole.interior_exterior(), Dimension::Point);
}
#[test]
fn point_linestring_relations_cover_all_linear_locations() {
let open = Linestring::from_vec(vec![P::new(0.0, 0.0), P::new(4.0, 0.0)]);
let endpoint = relation(&P::new(0.0, 0.0), &open).unwrap();
assert_eq!(endpoint.m[0][1], Dimension::Point);
assert_eq!(endpoint.m[2][1], Dimension::Point);
let interior = relation(&P::new(2.0, 0.0), &open).unwrap();
assert_eq!(interior.interior_interior(), Dimension::Point);
let exterior = relation(&P::new(2.0, 1.0), &open).unwrap();
assert_eq!(exterior.interior_exterior(), Dimension::Point);
let reversed = relation(&open, &P::new(2.0, 0.0)).unwrap();
assert_eq!(reversed, interior.transposed());
let closed = Linestring::from_vec(vec![P::new(0.0, 0.0), P::new(2.0, 0.0), P::new(0.0, 0.0)]);
assert_eq!(
relation(&P::new(0.0, 0.0), &closed)
.unwrap()
.interior_interior(),
Dimension::Point
);
let empty = Linestring::<P>::new();
assert_eq!(
relation(&P::new(0.0, 0.0), &empty)
.unwrap()
.interior_exterior(),
Dimension::Point
);
let degenerate = Linestring::from_vec(vec![P::new(1.0, 1.0), P::new(1.0, 1.0)]);
assert_eq!(
relation(&P::new(1.0, 1.0), °enerate)
.unwrap()
.interior_interior(),
Dimension::Point
);
}
#[test]
fn linear_relations_cover_disjoint_touch_and_overlap_kernels() {
let horizontal = Linestring::from_vec(vec![P::new(0.0, 0.0), P::new(4.0, 0.0)]);
let disjoint = Linestring::from_vec(vec![P::new(0.0, 2.0), P::new(4.0, 2.0)]);
assert_eq!(
relation(&horizontal, &disjoint)
.unwrap()
.interior_interior(),
Dimension::Empty
);
let touching = Linestring::from_vec(vec![P::new(4.0, 0.0), P::new(5.0, 1.0)]);
assert_eq!(
relation(&horizontal, &touching)
.unwrap()
.boundary_boundary(),
Dimension::Point
);
let overlapping = Linestring::from_vec(vec![P::new(2.0, 0.0), P::new(6.0, 0.0)]);
assert_eq!(
relation(&horizontal, &overlapping)
.unwrap()
.interior_interior(),
Dimension::Curve
);
let diagonal = Linestring::from_vec(vec![P::new(0.0, -1.0), P::new(4.0, 1.0)]);
assert!(crosses(&horizontal, &diagonal).unwrap());
}
#[test]
fn duplicated_and_closed_linestrings_match_boost_matrices() {
let reference =
Linestring::from_vec(vec![P::new(0.0, 0.0), P::new(2.0, 2.0), P::new(4.0, 2.0)]);
for duplicated in [
Linestring::from_vec(vec![P::new(1.0, 1.0), P::new(2.0, 2.0), P::new(2.0, 2.0)]),
Linestring::from_vec(vec![P::new(1.0, 1.0), P::new(1.0, 1.0), P::new(2.0, 2.0)]),
] {
assert!(
relation(&duplicated, &reference)
.unwrap()
.matches("1FF0FF102")
.unwrap()
);
}
let open = Linestring::from_vec(vec![P::new(0.0, 0.0), P::new(10.0, 0.0)]);
let closed = Linestring::from_vec(vec![
P::new(5.0, 0.0),
P::new(9.0, 0.0),
P::new(5.0, 5.0),
P::new(1.0, 0.0),
P::new(5.0, 0.0),
]);
let observed = relation(&open, &closed).unwrap();
assert!(observed.matches("1F1FF01F2").unwrap());
let point_size = Linestring::from_vec(vec![P::new(1.0, 0.0), P::new(1.0, 0.0)]);
let horizontal = Linestring::from_vec(vec![P::new(0.0, 0.0), P::new(5.0, 0.0)]);
let point_line = relation(&point_size, &horizontal).unwrap();
assert!(point_line.matches("0FFFFF102").unwrap());
assert_eq!(
relation(&horizontal, &point_size).unwrap(),
point_line.transposed()
);
}
#[test]
fn line_on_polygon_boundary_and_reversed_pair_are_related() {
let polygon = square();
let boundary = Linestring::from_vec(vec![P::new(0.0, 0.0), P::new(4.0, 0.0)]);
let matrix = relation(&boundary, &polygon).unwrap();
assert_eq!(matrix.m[0][1], Dimension::Point);
assert_eq!(relation(&polygon, &boundary).unwrap(), matrix.transposed());
let empty_polygon = Polygon::new(Ring::<P>::new());
assert_eq!(
relation(&P::new(1.0, 1.0), &empty_polygon)
.unwrap()
.interior_exterior(),
Dimension::Point
);
let open_polygon: Polygon<P> = Polygon::new(Ring::from_vec(vec![
P::new(0.0, 0.0),
P::new(4.0, 0.0),
P::new(4.0, 4.0),
P::new(0.0, 4.0),
]));
assert_eq!(
relation(&P::new(0.0, 2.0), &open_polygon).unwrap().m[0][1],
Dimension::Point
);
}
#[test]
fn areal_containment_covers_both_ordered_matrix_arms() {
let outer = box_at(0.0, 0.0, 10.0, 10.0);
let inner = box_at(2.0, 2.0, 4.0, 4.0);
let outer_inner = relation(&outer, &inner).unwrap();
let inner_outer = relation(&inner, &outer).unwrap();
assert_eq!(outer_inner, inner_outer.transposed());
assert_eq!(outer_inner.interior_interior(), Dimension::Area);
assert_eq!(outer_inner.interior_exterior(), Dimension::Area);
assert_eq!(inner_outer.exterior_interior(), Dimension::Area);
let identical = relation(&outer, &outer).unwrap();
assert_eq!(identical.interior_interior(), Dimension::Area);
let open_outer: Polygon<P> = Polygon::new(Ring::<P>::from_vec(vec![
P::new(0.0, 0.0),
P::new(0.0, 10.0),
P::new(10.0, 10.0),
P::new(10.0, 0.0),
]));
assert_eq!(
relation(&open_outer, &open_outer)
.unwrap()
.boundary_boundary(),
Dimension::Curve
);
}
#[test]
fn public_matrix_masks_cover_every_symbol_and_overlay_errors() {
let matrix = De9im {
m: [
[Dimension::Empty, Dimension::Point, Dimension::Curve],
[Dimension::Area, Dimension::Empty, Dimension::Point],
[Dimension::Curve, Dimension::Area, Dimension::Empty],
],
};
assert!(matrix.matches("F012F012F").unwrap());
assert!(matrix.matches("*T*******").unwrap());
assert_eq!(matrix.matches("********"), Err(RelateError::InvalidMask));
assert_eq!(matrix.matches("F012X012F"), Err(RelateError::InvalidMask));
let huge = box_at(0.0, 0.0, 200_000_000.0, 200_000_000.0);
assert_eq!(
relate(&huge, &square(), "*********"),
Err(RelateError::Overlay(OverlayError::Unsupported))
);
}
#[test]
fn boxes_and_rings_use_public_areal_relate_dispatch() {
let first = ModelBox::from_corners(P::new(1.0, 1.0), P::new(3.0, 3.0));
let second = ModelBox::from_corners(P::new(0.0, 0.0), P::new(2.0, 2.0));
assert!(
relation(&first, &second)
.unwrap()
.matches("212101212")
.unwrap()
);
assert!(overlaps(&first, &second).unwrap());
let ring = square().exterior().clone();
assert!(
relation(&P::new(2.0, 2.0), &ring)
.unwrap()
.matches("0FFFFF212")
.unwrap()
);
assert!(touches(&P::new(0.0, 2.0), &ring).unwrap());
}
#[test]
fn pointlike_and_linear_multis_preserve_union_boundary_rules() {
let first = MultiPoint::from_vec(vec![P::new(0.0, 0.0), P::new(1.0, 1.0)]);
let second = MultiPoint::from_vec(vec![P::new(0.0, 0.0), P::new(1.0, 0.0)]);
assert!(
relation(&first, &second)
.unwrap()
.matches("0F0FFF0F2")
.unwrap()
);
let lines = MultiLinestring::from_vec(vec![
Linestring::from_vec(vec![P::new(0.0, 0.0), P::new(2.0, 0.0), P::new(2.0, 2.0)]),
Linestring::from_vec(vec![P::new(0.0, 0.0), P::new(0.0, 2.0)]),
]);
assert!(
relation(&P::new(0.0, 0.0), &lines)
.unwrap()
.matches("0FFFFF102")
.unwrap()
);
}
#[test]
fn multipolygon_relate_uses_the_public_union_topology() {
let polygons =
MultiPolygon::from_vec(vec![box_at(0.0, 0.0, 5.0, 5.0), box_at(5.0, 5.0, 9.0, 9.0)]);
assert!(
relation(&P::new(5.0, 5.0), &polygons)
.unwrap()
.matches("F0FFFF212")
.unwrap()
);
assert!(
relation(&P::new(6.0, 6.0), &polygons)
.unwrap()
.matches("0FFFFF212")
.unwrap()
);
}
#[test]
fn geometry_collections_relate_through_runtime_public_dispatch() {
let joined_lines = DynGeometryCollection(vec![
DynGeometry::LineString(Linestring::from_vec(vec![
P::new(0.0, 0.0),
P::new(1.0, 1.0),
])),
DynGeometry::LineString(Linestring::from_vec(vec![
P::new(1.0, 1.0),
P::new(2.0, 2.0),
])),
]);
let point = DynGeometryCollection(vec![DynGeometry::Point(P::new(1.0, 1.0))]);
assert!(
relation(&point, &joined_lines)
.unwrap()
.matches("0FFFFF102")
.unwrap()
);
let closed_line =
DynGeometryCollection(vec![DynGeometry::LineString(Linestring::from_vec(vec![
P::new(0.0, 0.0),
P::new(2.0, 0.0),
P::new(0.0, 0.0),
]))]);
let closed_endpoint = DynGeometryCollection(vec![DynGeometry::Point(P::new(0.0, 0.0))]);
assert!(
relation(&closed_endpoint, &closed_line)
.unwrap()
.matches("0FFFFF1F2")
.unwrap()
);
let first = DynGeometryCollection(vec![
DynGeometry::Polygon(box_at(0.0, 0.0, 5.0, 5.0)),
DynGeometry::LineString(Linestring::from_vec(vec![
P::new(1.0, 1.0),
P::new(6.0, 6.0),
])),
]);
let second = DynGeometryCollection(vec![
DynGeometry::Polygon(box_at(0.0, 0.0, 5.0, 5.0)),
DynGeometry::LineString(Linestring::from_vec(vec![
P::new(5.0, 5.0),
P::new(6.0, 6.0),
])),
]);
let matrix = relation(&first, &second).unwrap();
assert!(matrix.matches("2FFF1FFF2").unwrap());
}
#[test]
fn segment_dynamic_and_collection_reverse_pairs_are_public() {
let segment = Segment::new(P::new(-1.0, 2.0), P::new(5.0, 2.0));
let bounds = ModelBox::from_corners(P::new(0.0, 0.0), P::new(4.0, 4.0));
let segment_box = relation(&segment, &bounds).unwrap();
assert!(segment_box.matches("101FF0212").unwrap());
assert_eq!(
relation(&bounds, &segment).unwrap(),
segment_box.transposed()
);
assert!(crosses(&segment, &bounds).unwrap());
let dynamic_point = DynGeometry::Point(P::new(2.0, 2.0));
let dynamic_polygon = DynGeometry::Polygon(square());
let dynamic_matrix = relation(&dynamic_point, &dynamic_polygon).unwrap();
assert!(dynamic_matrix.matches("0FFFFF212").unwrap());
assert_eq!(
relation(&dynamic_polygon, &dynamic_point).unwrap(),
dynamic_matrix.transposed()
);
let adjacent = DynGeometryCollection(vec![
DynGeometry::Polygon(box_at(10.0, 0.0, 20.0, 10.0)),
DynGeometry::Point(P::new(15.0, 5.0)),
]);
assert!(
relation(&box_at(0.0, 0.0, 10.0, 10.0), &adjacent)
.unwrap()
.matches("FF2F11212")
.unwrap()
);
}
#[test]
fn generic_topology_dispatch_covers_degenerate_and_dynamic_variants() {
let segment = Segment::new(P::new(0.0, 0.0), P::new(4.0, 0.0));
let line = Linestring::from_vec(vec![P::new(2.0, -1.0), P::new(2.0, 1.0)]);
assert_eq!(
relation(&line, &segment).unwrap().interior_interior(),
Dimension::Point
);
let degenerate_line = Linestring::from_vec(vec![P::new(1.0, 0.0), P::new(1.0, 0.0)]);
assert_eq!(
relation(°enerate_line, &segment)
.unwrap()
.interior_interior(),
Dimension::Point
);
assert_eq!(
relation(&segment, °enerate_line).unwrap(),
relation(°enerate_line, &segment).unwrap().transposed()
);
let degenerate_polygon: Polygon<P> =
Polygon::new(Ring::from_vec(vec![P::new(1.0, 0.0), P::new(3.0, 0.0)]));
assert_eq!(
relation(°enerate_polygon, &segment)
.unwrap()
.interior_interior(),
Dimension::Curve
);
let dynamic_multi_point = DynGeometry::MultiPoint(MultiPoint::from_vec(vec![
P::new(0.0, 0.0),
P::new(2.0, 2.0),
]));
assert_eq!(
relation(&dynamic_multi_point, &P::new(2.0, 2.0))
.unwrap()
.interior_interior(),
Dimension::Point
);
let dynamic_multi_line = DynGeometry::MultiLineString(MultiLinestring::from_vec(vec![
Linestring::from_vec(vec![P::new(0.0, 0.0), P::new(2.0, 2.0)]),
]));
assert_eq!(
relation(&dynamic_multi_line, &P::new(1.0, 1.0))
.unwrap()
.interior_interior(),
Dimension::Point
);
let dynamic_multi_polygon = DynGeometry::MultiPolygon(MultiPolygon::from_vec(vec![square()]));
assert_eq!(
relation(&dynamic_multi_polygon, &P::new(2.0, 2.0))
.unwrap()
.interior_interior(),
Dimension::Point
);
let dynamic_empty_line = DynGeometry::<f64, Cartesian>::LineString(Linestring::new());
assert_eq!(
relation(&dynamic_empty_line, &P::new(2.0, 2.0))
.unwrap()
.exterior_interior(),
Dimension::Point
);
let redundant_polygon: Polygon<P> = Polygon::new(Ring::from_vec(vec![
P::new(0.0, 0.0),
P::new(0.0, 4.0),
P::new(0.0, 4.0),
P::new(4.0, 4.0),
P::new(4.0, 0.0),
P::new(0.0, 0.0),
]));
assert_eq!(
relation(&redundant_polygon, &P::new(2.0, 2.0))
.unwrap()
.interior_interior(),
Dimension::Point
);
let nested = DynGeometry::GeometryCollection(vec![DynGeometry::GeometryCollection(vec![
dynamic_multi_point,
dynamic_multi_line,
dynamic_multi_polygon,
])]);
assert!(
relation(&nested, &P::new(1.0, 1.0))
.unwrap()
.interior_interior()
.is_set()
);
}
#[test]
fn generic_topology_rejects_out_of_range_members_and_crosses_both_orders() {
let segment = Segment::new(P::new(-1.0, 2.0), P::new(5.0, 2.0));
assert!(matches!(
relation(&P::new(f64::MAX, 0.0), &segment),
Err(OverlayError::Unsupported)
));
let polygon_with_bad_hole = Polygon::with_inners(
square().outer,
vec![Ring::from_vec(vec![
P::new(1.0, 1.0),
P::new(f64::MAX, 1.0),
P::new(2.0, 2.0),
P::new(1.0, 1.0),
])],
);
assert!(matches!(
relation(&polygon_with_bad_hole, &segment),
Err(OverlayError::Unsupported)
));
let donut = Polygon::with_inners(
square().outer,
vec![Ring::from_vec(vec![
P::new(1.0, 1.0),
P::new(3.0, 1.0),
P::new(3.0, 3.0),
P::new(1.0, 3.0),
P::new(1.0, 1.0),
])],
);
assert!(relation(&donut, &segment).is_ok());
let bounds = ModelBox::from_corners(P::new(0.0, 0.0), P::new(4.0, 4.0));
assert!(crosses(&segment, &bounds).unwrap());
assert!(crosses(&bounds, &segment).unwrap());
}