Skip to content

Add a native Box3D type for 3D bounding boxes (PostGIS-compatible) #2973

@jiayuasu

Description

@jiayuasu

Follow-up to the Box2D epic (#2877). Adds the planar 3D bounding box type — PostGIS's box3d — as a first-class Sedona SQL type.

Scope (Phase 1)

Mirrors Box2D's Phase 1 shape, scoped down to JVM + SQL surface only. Cross-language bindings, GeoParquet covering interop, casts, join-planner integration, and the ST_3DDWithin distance predicate are all explicit follow-ups.

Foundation

  • Box3D value class in common/.../geometryObjects/Box3D.java (6 doubles: xmin, ymin, zmin, xmax, ymax, zmax, mirroring PostGIS's storage order).
  • Box3DUDT (struct of 6 non-nullable doubles) + UDT registration.

SQL surface

  • ST_Box3D(geom) — planar 3D bounding box of a Geometry. Geometries without Z get zmin = zmax = 0 (matches PostGIS).
  • ST_3DMakeBox(p1: PointZ, p2: PointZ) — two-corner constructor.
  • ST_3DExtent(geom) aggregate — Box3D over a column.
  • ST_ZMin(box3d) / ST_ZMax(box3d) overloads (the existing Geometry-input forms stay).
  • ST_XMin/YMin/XMax/YMax(box3d) overloads.
  • ST_3DBoxIntersects(box3d, box3d) — closed-interval intersection on all three axes. Matches PostGIS &&& (the 3D overlap operator).
  • ST_3DBoxContains(box3d, box3d) — closed-interval containment on all three axes. Matches PostGIS ~~ on box3d.
  • ST_AsText(box3d)BOX3D(xmin ymin zmin, xmax ymax zmax) text form.

Semantics

  • Closed-interval (edge/face/corner-touching counts as intersecting and contained), matching Box2D's contract.
  • NULL for absent (no in-band empty marker).
  • xmin > xmax / ymin > ymax / zmin > zmax are explicitly rejected by predicates with IllegalArgumentException — same contract as Box2D. (We're not carrying forward Box2D's "reserved for future antimeridian wraparound" exception for Z; Z doesn't wraparound.)
  • XY-only geometries → zmin = zmax = 0 (PostGIS-compatible default).

Out of scope (separate follow-ups)

  • Python Box3DType bindings and Scala DataFrame helpers.
  • Flink bindings (scalar + aggregate + Box3DTypeSerializer).
  • R bindings.
  • Documentation (analogous to the Box2D docs PR [DOCS] Add Box2D SQL documentation #2966).
  • GeoParquet covering-bbox recognition for Box3D columns (the spec allows optional zmin/zmax).
  • Casts between Box2D and Box3D (lossy in either direction).
  • Spatial join planner integration for ST_3DBoxIntersects / ST_3DBoxContains.
  • Filter pushdown for Box3D predicates against Box3D columns.
  • ST_3DDWithin distance predicate (needs squared-Euclidean over 3D).

Why Phase 1 is narrow

Box2D's Phase 1 was wide because GeoParquet covering bbox interop drove concrete demand. Box3D today has no equivalent pull (see the original epic's "when a concrete user appears" note). This issue ships the foundation and the SQL surface so it's available, and defers everything that benefits from a real workload to inform design.

Coordination

Match Box2D's design choices wherever possible — the type-system contract (separate UDT, six-double struct), the closed-interval semantics, and the inverted-bounds throw should all feel parallel.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions