Source:
Local role:
Pure-Python, frozen-dataclass implementation of the Geometry protocol. The only concrete geometry at the foundations layer — makes the spatial module fully usable without any adapter.
Big-picture role:
Used as the geometry type G for GeometricSpace[BoundingBox], SpatialExtent[BoundingBox], and SpatialMap[BoundingBox] when no adapter is installed. Adapter-backed geometry types must expose a .bounds property that returns a BoundingBox, enabling fallback comparisons.
Fields
min_x: float— left boundarymin_y: float— bottom boundarymax_x: float— right boundarymax_y: float— top boundary
Raises ValueError on construction if min_x > max_x or min_y > max_y.
Geometry protocol — properties
area -> float— zero for degenerate (point or line) boxescentroid -> Coordinate2D— centre of the boxbounds -> BoundingBox— returnsself; a BoundingBox is its own AABB
Geometry protocol — predicate methods
contains(other: Geometry) -> bool— True if every point ofotherlies within or on the boundary of this box. Falls back toother.boundsfor non-BoundingBox operands.intersects(other: Geometry) -> bool— True if the boxes share any common point (including boundary). Falls back toother.bounds.touches(other: Geometry) -> bool— DE-9IM semantics: at least one common point and open interiors are disjoint. Implemented asnot_disjoint AND NOT interior_overlapusing strict inequalities for interior overlap detection. Falls back toother.bounds.distance(other: Geometry) -> float— minimum Euclidean distance; 0.0 if they share any point. Falls back toother.bounds.
Convenience methods (not on Geometry protocol)
contains_point(point: Coordinate2D) -> bool— True ifpointlies inside or on the boundaryexpand(margin: float) -> BoundingBox— new box expanded bymarginon every sideunion(other: BoundingBox) -> BoundingBox— smallest box that contains both
Constructors
BoundingBox(min_x, min_y, max_x, max_y)— standard constructor@classmethod from_center(center: Coordinate2D, half_w, half_h) -> BoundingBox@classmethod from_point(point: Coordinate2D) -> BoundingBox— degenerate zero-area box at a point
Serialisation
to_dict() -> JsonMap—{"type": "bounding_box", "min_x": …, "min_y": …, "max_x": …, "max_y": …}@classmethod from_dict(data) -> BoundingBox— raisesValueErrorif"type"discriminator does not match or required keys are missing
Example:
from ometeotl_foundations.spatial.bounding_box import BoundingBox
from ometeotl_foundations.spatial.coordinates import Coordinate2D
city = BoundingBox(0, 0, 100, 80)
district = BoundingBox(10, 10, 40, 40)
assert city.contains(district)
assert city.area == 8000.0
assert city.centroid == Coordinate2D(50.0, 40.0)
# Touching but not overlapping
east = BoundingBox(100, 0, 200, 80)
assert city.touches(east)
# Round-trip serialisation
restored = BoundingBox.from_dict(city.to_dict())
assert restored == city
# From centre
box = BoundingBox.from_center(Coordinate2D(50, 40), half_w=10, half_h=5)
Notes:
- All predicate methods fall back to bounding-box comparison (
other.bounds) when the operand is not aBoundingBox. Results are then approximations. - Conformance to the
Geometryprotocol is asserted at import time:assert isinstance(BoundingBox(0,0,1,1), Geometry).
See also:
