From bab3432d3805f02995038f1706e72f5f0ea22842 Mon Sep 17 00:00:00 2001 From: Jia Yu Date: Thu, 11 Jun 2026 22:35:35 -0700 Subject: [PATCH] [GH-3048] Docs: Box3D SQL reference pages with isometric diagrams MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Box3D SQL surface is fully shipped across Spark and Flink but had no reference docs. Adds them, mirroring the Box2D doc structure, plus a set of isometric SVG diagrams (3D is harder to grasp from prose than 2D). Pages: - box3d/Box3D-Functions.md landing page (added to SQL nav + Chinese nav_translations). - Constructors: ST_Box3D, ST_3DMakeBox. - Accessors: ST_XMin / ST_YMin / ST_ZMin / ST_XMax / ST_YMax / ST_ZMax. - Function: ST_AsText(box3d). - Aggregate: Aggregate-Functions/ST_3DExtent.md. - Predicate: Predicates/ST_3DDWithin.md; Box3D true/false figures also added to the shared ST_Intersects / ST_Contains pages. - Index rows for ST_3DExtent / ST_3DDWithin in Geometry-Functions.md. Diagrams (docs/image/box3d/, isometric axonometric projection): - box3d_anatomy, st_box3d, st_3dmakebox, st_3dextent. - st_intersects_box3d_{true,false}, st_contains_box3d_{true,false}, st_3ddwithin_{true,false}. The false cases deliberately show the XY-overlap-but-Z-disjoint scenario — the key 3D distinction that a 2D bbox diagram cannot convey. Notes that Box3D filter pushdown and the inverse CAST(box3d AS geometry) are not yet implemented. --- .../sql/Aggregate-Functions/ST_3DExtent.md | 50 ++++++++++ docs/api/sql/Geometry-Functions.md | 2 + docs/api/sql/Predicates/ST_3DDWithin.md | 69 ++++++++++++++ docs/api/sql/Predicates/ST_Contains.md | 5 + docs/api/sql/Predicates/ST_Intersects.md | 5 + docs/api/sql/box3d/Box3D-Accessors/ST_XMax.md | 42 +++++++++ docs/api/sql/box3d/Box3D-Accessors/ST_XMin.md | 42 +++++++++ docs/api/sql/box3d/Box3D-Accessors/ST_YMax.md | 42 +++++++++ docs/api/sql/box3d/Box3D-Accessors/ST_YMin.md | 42 +++++++++ docs/api/sql/box3d/Box3D-Accessors/ST_ZMax.md | 42 +++++++++ docs/api/sql/box3d/Box3D-Accessors/ST_ZMin.md | 42 +++++++++ .../box3d/Box3D-Constructors/ST_3DMakeBox.md | 44 +++++++++ .../sql/box3d/Box3D-Constructors/ST_Box3D.md | 60 ++++++++++++ docs/api/sql/box3d/Box3D-Functions.md | 92 +++++++++++++++++++ .../sql/box3d/Box3D-Functions/ST_AsText.md | 44 +++++++++ docs/image/box3d/box3d_anatomy.svg | 1 + docs/image/box3d/st_3ddwithin_false.svg | 1 + docs/image/box3d/st_3ddwithin_true.svg | 1 + docs/image/box3d/st_3dextent.svg | 1 + docs/image/box3d/st_3dmakebox.svg | 1 + docs/image/box3d/st_box3d.svg | 1 + docs/image/box3d/st_contains_box3d_false.svg | 1 + docs/image/box3d/st_contains_box3d_true.svg | 1 + .../image/box3d/st_intersects_box3d_false.svg | 1 + docs/image/box3d/st_intersects_box3d_true.svg | 1 + mkdocs.yml | 2 + 26 files changed, 635 insertions(+) create mode 100644 docs/api/sql/Aggregate-Functions/ST_3DExtent.md create mode 100644 docs/api/sql/Predicates/ST_3DDWithin.md create mode 100644 docs/api/sql/box3d/Box3D-Accessors/ST_XMax.md create mode 100644 docs/api/sql/box3d/Box3D-Accessors/ST_XMin.md create mode 100644 docs/api/sql/box3d/Box3D-Accessors/ST_YMax.md create mode 100644 docs/api/sql/box3d/Box3D-Accessors/ST_YMin.md create mode 100644 docs/api/sql/box3d/Box3D-Accessors/ST_ZMax.md create mode 100644 docs/api/sql/box3d/Box3D-Accessors/ST_ZMin.md create mode 100644 docs/api/sql/box3d/Box3D-Constructors/ST_3DMakeBox.md create mode 100644 docs/api/sql/box3d/Box3D-Constructors/ST_Box3D.md create mode 100644 docs/api/sql/box3d/Box3D-Functions.md create mode 100644 docs/api/sql/box3d/Box3D-Functions/ST_AsText.md create mode 100644 docs/image/box3d/box3d_anatomy.svg create mode 100644 docs/image/box3d/st_3ddwithin_false.svg create mode 100644 docs/image/box3d/st_3ddwithin_true.svg create mode 100644 docs/image/box3d/st_3dextent.svg create mode 100644 docs/image/box3d/st_3dmakebox.svg create mode 100644 docs/image/box3d/st_box3d.svg create mode 100644 docs/image/box3d/st_contains_box3d_false.svg create mode 100644 docs/image/box3d/st_contains_box3d_true.svg create mode 100644 docs/image/box3d/st_intersects_box3d_false.svg create mode 100644 docs/image/box3d/st_intersects_box3d_true.svg diff --git a/docs/api/sql/Aggregate-Functions/ST_3DExtent.md b/docs/api/sql/Aggregate-Functions/ST_3DExtent.md new file mode 100644 index 00000000000..28940842804 --- /dev/null +++ b/docs/api/sql/Aggregate-Functions/ST_3DExtent.md @@ -0,0 +1,50 @@ + + +# ST_3DExtent + +Introduction: Return the 3D bounding box of all geometries in `A` as a typed [Box3D](../box3d/Box3D-Functions.md). Empty geometries and null values are skipped. If all inputs are empty or null, the result is null. Geometries without a Z dimension fold into `z = 0`. Mirrors PostGIS `ST_3DExtent`. + +`ST_3DExtent` is the 3D counterpart to [ST_Extent](ST_Extent.md), which returns a `Box2D`. + +![ST_3DExtent aggregates a column of geometries into the enclosing cuboid](../../../image/box3d/st_3dextent.svg "ST_3DExtent: enclosing cuboid over a column") + +Format: `ST_3DExtent(A: geometryColumn)` + +Return type: `Box3D` + +Since: `v1.9.1` + +SQL Example + +```sql +SELECT ST_AsText(ST_3DExtent(geom)) +FROM VALUES + (ST_GeomFromText('POINT Z (1 2 3)')), + (ST_GeomFromText('POINT Z (4 5 -1)')), + (ST_GeomFromText('LINESTRING (-3 0, 0 0)')) AS t(geom) +``` + +Output: + +``` +BOX3D(-3.0 0.0 -1.0, 4.0 5.0 3.0) +``` + +The XY-only linestring contributes `z = 0`, which sits between the `-1` and `3` of the two POINT Z rows, so it does not move either Z bound. diff --git a/docs/api/sql/Geometry-Functions.md b/docs/api/sql/Geometry-Functions.md index 40b94981975..9e02d382d40 100644 --- a/docs/api/sql/Geometry-Functions.md +++ b/docs/api/sql/Geometry-Functions.md @@ -166,6 +166,7 @@ These functions test spatial relationships between geometries, returning boolean | [ST_Crosses](Predicates/ST_Crosses.md) | Boolean | Return true if A crosses B | v1.0.0 | | [ST_Disjoint](Predicates/ST_Disjoint.md) | Boolean | Return true if A and B are disjoint | v1.2.1 | | [ST_DWithin](Predicates/ST_DWithin.md) | Boolean | Returns true if 'leftGeometry' and 'rightGeometry' are within a specified 'distance'. | v1.5.1 | +| [ST_3DDWithin](Predicates/ST_3DDWithin.md) | Boolean | Returns true if A and B are within a specified 3D Euclidean 'distance'. Accepts Geometry or Box3D inputs. | v1.9.1 | | [ST_Equals](Predicates/ST_Equals.md) | Boolean | Return true if A equals to B | v1.0.0 | | [ST_Intersects](Predicates/ST_Intersects.md) | Boolean | Return true if A intersects B | v1.0.0 | | [ST_OrderingEquals](Predicates/ST_OrderingEquals.md) | Boolean | Returns true if the geometries are equal and the coordinates are in the same order | v1.2.1 | @@ -275,6 +276,7 @@ These functions perform aggregate operations on groups of geometries. | [ST_Collect_Agg](Aggregate-Functions/ST_Collect_Agg.md) | Geometry | Collects all geometries in a geometry column into a single multi-geometry (MultiPoint, MultiLineString, MultiPolygon, or GeometryCollection). Unlike `ST_Union_Agg`, this function does not dissolve ... | v1.8.1 | | [ST_Envelope_Agg](Aggregate-Functions/ST_Envelope_Agg.md) | Geometry | Return the entire envelope boundary of all geometries in A. Empty geometries and null values are skipped. If all inputs are empty or null, the result is null. This behavior is consistent with PostG... | v1.0.0 | | [ST_Extent](Aggregate-Functions/ST_Extent.md) | Box2D | Return the planar bounding box of all geometries in A as a typed Box2D. Empty geometries and NULL values are skipped. Mirrors PostGIS `ST_Extent`. | v1.9.1 | +| [ST_3DExtent](Aggregate-Functions/ST_3DExtent.md) | Box3D | Return the 3D bounding box of all geometries in A as a typed Box3D. Empty/NULL skipped; geometries without Z fold to z=0. Mirrors PostGIS `ST_3DExtent`. | v1.9.1 | | [ST_Intersection_Agg](Aggregate-Functions/ST_Intersection_Agg.md) | Geometry | Return the polygon intersection of all polygons in A | v1.0.0 | | [ST_Union_Agg](Aggregate-Functions/ST_Union_Agg.md) | Geometry | Return the polygon union of all polygons in A | v1.0.0 | diff --git a/docs/api/sql/Predicates/ST_3DDWithin.md b/docs/api/sql/Predicates/ST_3DDWithin.md new file mode 100644 index 00000000000..f34ce925ec7 --- /dev/null +++ b/docs/api/sql/Predicates/ST_3DDWithin.md @@ -0,0 +1,69 @@ + + +# ST_3DDWithin + +Introduction: Return true if the 3D Euclidean distance between `A` and `B` is less than or equal to `distance`. Mirrors PostGIS `ST_3DDWithin`. Polymorphic over input type: + +- `(Geometry, Geometry, Double)` — 3D distance via JTS; coordinates without a Z dimension are treated as `z = 0`. +- `(Box3D, Box3D, Double)` — closed-interval 3D distance between two axis-aligned cuboids. Throws `IllegalArgumentException` on inverted bounds (`xmin > xmax`, `ymin > ymax`, or `zmin > zmax`). + +This is distinct from [ST_DWithin](ST_DWithin.md), which always measures planar (2D) distance regardless of input dimensionality — matching the PostGIS distinction between `ST_DWithin` and `ST_3DDWithin`. + +![ST_3DDWithin returning true: the 3D gap is within the distance](../../../image/box3d/st_3ddwithin_true.svg "ST_3DDWithin returning true") +![ST_3DDWithin returning false: the 3D gap exceeds the distance](../../../image/box3d/st_3ddwithin_false.svg "ST_3DDWithin returning false") + +Format: + +- `ST_3DDWithin(A: Geometry, B: Geometry, distance: Double)` +- `ST_3DDWithin(A: Box3D, B: Box3D, distance: Double)` + +Return type: `Boolean` + +Since: `v1.9.1` + +SQL Example + +```sql +SELECT ST_3DDWithin(ST_PointZ(0, 0, 0), ST_PointZ(0, 0, 3), 3.0) +``` + +Output: + +``` +true +``` + +The same points are not within 2.9 units in 3D: + +```sql +SELECT ST_3DDWithin(ST_PointZ(0, 0, 0), ST_PointZ(0, 0, 3), 2.9) +``` + +Output: + +``` +false +``` + +Returns `NULL` if any argument is `NULL`. + +## Optimization + +`ST_3DDWithin(a, b, distance)` between two `Box3D` columns (or two geometry columns) is planned as a distance join. The planner expands each shape's XY footprint by `distance` for the R-tree pass — a valid superset filter, since the XY distance between two shapes is no greater than their 3D distance — and re-checks the full 3D distance per candidate pair. See [Query optimization](../Optimizer.md). diff --git a/docs/api/sql/Predicates/ST_Contains.md b/docs/api/sql/Predicates/ST_Contains.md index c32ff2fb6bc..76fb64b7663 100644 --- a/docs/api/sql/Predicates/ST_Contains.md +++ b/docs/api/sql/Predicates/ST_Contains.md @@ -66,6 +66,11 @@ Output: true ``` +For `Box3D` inputs containment must hold on all three axes — a box contained within another's XY footprint but extending past it in Z is **not** contained: + +![Box3D ST_Contains returning true: B lies fully inside A](../../../image/box3d/st_contains_box3d_true.svg "Box3D ST_Contains returning true") +![Box3D ST_Contains returning false: B is inside in XY but extends past A in Z](../../../image/box3d/st_contains_box3d_false.svg "Box3D ST_Contains returning false") + ## Box2D optimization `ST_Contains(box_col, lit_box)` over a `Box2D` column and a literal `Box2D` (and the reversed form) is recognised by Sedona's spatial optimizer: diff --git a/docs/api/sql/Predicates/ST_Intersects.md b/docs/api/sql/Predicates/ST_Intersects.md index 8775779bf36..8e478b28e4d 100644 --- a/docs/api/sql/Predicates/ST_Intersects.md +++ b/docs/api/sql/Predicates/ST_Intersects.md @@ -66,6 +66,11 @@ Output: true ``` +For `Box3D` inputs the test covers all three axes — two boxes whose XY footprints overlap but whose Z ranges are disjoint do **not** intersect: + +![Box3D ST_Intersects returning true: boxes overlap on all three axes](../../../image/box3d/st_intersects_box3d_true.svg "Box3D ST_Intersects returning true") +![Box3D ST_Intersects returning false: XY overlaps but Z is disjoint](../../../image/box3d/st_intersects_box3d_false.svg "Box3D ST_Intersects returning false") + ## Box2D optimization `ST_Intersects(box_col, lit_box)` over a `Box2D` column and a literal `Box2D` is recognised by Sedona's spatial optimizer: diff --git a/docs/api/sql/box3d/Box3D-Accessors/ST_XMax.md b/docs/api/sql/box3d/Box3D-Accessors/ST_XMax.md new file mode 100644 index 00000000000..14ee4d0187f --- /dev/null +++ b/docs/api/sql/box3d/Box3D-Accessors/ST_XMax.md @@ -0,0 +1,42 @@ + + +# ST_XMax + +Introduction: Return the maximum X coordinate of a `Box3D` value. This is the same function as the [Geometry-input form](../../Bounding-Box-Functions/ST_XMax.md), overloaded to accept a `Box3D`. + +Format: `ST_XMax(box: Box3D)` + +Return type: `Double` + +Since: `v1.9.1` + +SQL Example + +```sql +SELECT ST_XMax(ST_3DMakeBox(ST_PointZ(0.0, 0.0, -3.0), ST_PointZ(5.0, 10.0, 7.0))) +``` + +Output: + +``` +5.0 +``` + +Returns `NULL` on `NULL` input. diff --git a/docs/api/sql/box3d/Box3D-Accessors/ST_XMin.md b/docs/api/sql/box3d/Box3D-Accessors/ST_XMin.md new file mode 100644 index 00000000000..9fb9b51a185 --- /dev/null +++ b/docs/api/sql/box3d/Box3D-Accessors/ST_XMin.md @@ -0,0 +1,42 @@ + + +# ST_XMin + +Introduction: Return the minimum X coordinate of a `Box3D` value. This is the same function as the [Geometry-input form](../../Bounding-Box-Functions/ST_XMin.md), overloaded to accept a `Box3D`. + +Format: `ST_XMin(box: Box3D)` + +Return type: `Double` + +Since: `v1.9.1` + +SQL Example + +```sql +SELECT ST_XMin(ST_3DMakeBox(ST_PointZ(0.0, 0.0, -3.0), ST_PointZ(5.0, 10.0, 7.0))) +``` + +Output: + +``` +0.0 +``` + +Returns `NULL` on `NULL` input. diff --git a/docs/api/sql/box3d/Box3D-Accessors/ST_YMax.md b/docs/api/sql/box3d/Box3D-Accessors/ST_YMax.md new file mode 100644 index 00000000000..a7fbf5acd60 --- /dev/null +++ b/docs/api/sql/box3d/Box3D-Accessors/ST_YMax.md @@ -0,0 +1,42 @@ + + +# ST_YMax + +Introduction: Return the maximum Y coordinate of a `Box3D` value. This is the same function as the [Geometry-input form](../../Bounding-Box-Functions/ST_YMax.md), overloaded to accept a `Box3D`. + +Format: `ST_YMax(box: Box3D)` + +Return type: `Double` + +Since: `v1.9.1` + +SQL Example + +```sql +SELECT ST_YMax(ST_3DMakeBox(ST_PointZ(0.0, 0.0, -3.0), ST_PointZ(5.0, 10.0, 7.0))) +``` + +Output: + +``` +10.0 +``` + +Returns `NULL` on `NULL` input. diff --git a/docs/api/sql/box3d/Box3D-Accessors/ST_YMin.md b/docs/api/sql/box3d/Box3D-Accessors/ST_YMin.md new file mode 100644 index 00000000000..69d5c98bb5e --- /dev/null +++ b/docs/api/sql/box3d/Box3D-Accessors/ST_YMin.md @@ -0,0 +1,42 @@ + + +# ST_YMin + +Introduction: Return the minimum Y coordinate of a `Box3D` value. This is the same function as the [Geometry-input form](../../Bounding-Box-Functions/ST_YMin.md), overloaded to accept a `Box3D`. + +Format: `ST_YMin(box: Box3D)` + +Return type: `Double` + +Since: `v1.9.1` + +SQL Example + +```sql +SELECT ST_YMin(ST_3DMakeBox(ST_PointZ(0.0, 0.0, -3.0), ST_PointZ(5.0, 10.0, 7.0))) +``` + +Output: + +``` +0.0 +``` + +Returns `NULL` on `NULL` input. diff --git a/docs/api/sql/box3d/Box3D-Accessors/ST_ZMax.md b/docs/api/sql/box3d/Box3D-Accessors/ST_ZMax.md new file mode 100644 index 00000000000..0bdd6f9b6ba --- /dev/null +++ b/docs/api/sql/box3d/Box3D-Accessors/ST_ZMax.md @@ -0,0 +1,42 @@ + + +# ST_ZMax + +Introduction: Return the maximum Z coordinate of a `Box3D` value. This is the same function as the [Geometry-input form](../../Bounding-Box-Functions/ST_ZMax.md), overloaded to accept a `Box3D`. + +Format: `ST_ZMax(box: Box3D)` + +Return type: `Double` + +Since: `v1.9.1` + +SQL Example + +```sql +SELECT ST_ZMax(ST_3DMakeBox(ST_PointZ(0.0, 0.0, -3.0), ST_PointZ(5.0, 10.0, 7.0))) +``` + +Output: + +``` +7.0 +``` + +Returns `NULL` on `NULL` input. diff --git a/docs/api/sql/box3d/Box3D-Accessors/ST_ZMin.md b/docs/api/sql/box3d/Box3D-Accessors/ST_ZMin.md new file mode 100644 index 00000000000..248daa2d975 --- /dev/null +++ b/docs/api/sql/box3d/Box3D-Accessors/ST_ZMin.md @@ -0,0 +1,42 @@ + + +# ST_ZMin + +Introduction: Return the minimum Z coordinate of a `Box3D` value. This is the same function as the [Geometry-input form](../../Bounding-Box-Functions/ST_ZMin.md), overloaded to accept a `Box3D`. + +Format: `ST_ZMin(box: Box3D)` + +Return type: `Double` + +Since: `v1.9.1` + +SQL Example + +```sql +SELECT ST_ZMin(ST_3DMakeBox(ST_PointZ(0.0, 0.0, -3.0), ST_PointZ(5.0, 10.0, 7.0))) +``` + +Output: + +``` +-3.0 +``` + +Returns `NULL` on `NULL` input. diff --git a/docs/api/sql/box3d/Box3D-Constructors/ST_3DMakeBox.md b/docs/api/sql/box3d/Box3D-Constructors/ST_3DMakeBox.md new file mode 100644 index 00000000000..7a82c15ad9e --- /dev/null +++ b/docs/api/sql/box3d/Box3D-Constructors/ST_3DMakeBox.md @@ -0,0 +1,44 @@ + + +# ST_3DMakeBox + +Introduction: Build a `Box3D` from two corner POINT Z geometries. Mirrors PostGIS's `ST_3DMakeBox`. The corners are taken verbatim — no swapping or validation of ordering — so inverted bounds are preserved as supplied. POINT inputs without a Z dimension contribute `z = 0`, matching PostGIS. + +![ST_3DMakeBox builds a cuboid from two corner POINT Z geometries](../../../../image/box3d/st_3dmakebox.svg "ST_3DMakeBox: cuboid from two corner points") + +Format: `ST_3DMakeBox(lowerLeft: Point, upperRight: Point)` + +Return type: `Box3D` + +Since: `v1.9.1` + +SQL Example + +```sql +SELECT ST_AsText(ST_3DMakeBox(ST_PointZ(1.0, 2.0, 3.0), ST_PointZ(4.0, 5.0, 6.0))) +``` + +Output: + +``` +BOX3D(1.0 2.0 3.0, 4.0 5.0 6.0) +``` + +Throws `IllegalArgumentException` if either argument is not a POINT. Returns `NULL` if either point is `NULL` or empty. diff --git a/docs/api/sql/box3d/Box3D-Constructors/ST_Box3D.md b/docs/api/sql/box3d/Box3D-Constructors/ST_Box3D.md new file mode 100644 index 00000000000..4bede28188c --- /dev/null +++ b/docs/api/sql/box3d/Box3D-Constructors/ST_Box3D.md @@ -0,0 +1,60 @@ + + +# ST_Box3D + +Introduction: Return the planar 3D bounding box of a Geometry as a typed `Box3D` value (six doubles: `xmin`, `ymin`, `zmin`, `xmax`, `ymax`, `zmax`). + +Geometries without a Z dimension fold into `zmin = zmax = 0`, matching PostGIS. `ST_Box3D` is the 3D counterpart to [ST_Box2D](../../box2d/Box2D-Constructors/ST_Box2D.md); it always returns a `Box3D` value that serialises to a struct of six non-nullable doubles and round-trips through Parquet without WKB overhead. + +![ST_Box3D returns the tightest cuboid enclosing a geometry](../../../../image/box3d/st_box3d.svg "ST_Box3D: bounding cuboid of a geometry") + +Format: `ST_Box3D(geom: Geometry)` + +Return type: `Box3D` + +Since: `v1.9.1` + +SQL Example + +```sql +SELECT ST_AsText(ST_Box3D(ST_GeomFromWKT('LINESTRING Z(0 0 -3, 5 10 7)'))) +``` + +Output: + +``` +BOX3D(0.0 0.0 -3.0, 5.0 10.0 7.0) +``` + +A 2D geometry folds its Z extent to 0: + +```sql +SELECT ST_AsText(ST_Box3D(ST_GeomFromWKT('LINESTRING (0 0, 5 10)'))) +``` + +Output: + +``` +BOX3D(0.0 0.0 0.0, 5.0 10.0 0.0) +``` + +`ST_Box3D` is also produced by the SQL cast `CAST(geom AS box3d)` — see [Type conversion](../Box3D-Functions.md#type-conversion). + +Returns `NULL` for `NULL` or empty geometry input. diff --git a/docs/api/sql/box3d/Box3D-Functions.md b/docs/api/sql/box3d/Box3D-Functions.md new file mode 100644 index 00000000000..0a6ae8aa2fd --- /dev/null +++ b/docs/api/sql/box3d/Box3D-Functions.md @@ -0,0 +1,92 @@ + + +# Box3D Functions + +The `Box3D` type in Sedona represents a planar axis-aligned 3D bounding box — a rectangular cuboid described by six `Double` values: `xmin`, `ymin`, `zmin`, `xmax`, `ymax`, `zmax` (PostGIS `box3d` storage order). It is a first-class SQL type backed by a Spark UDT and serialises to a struct of six non-nullable doubles, so columns of `Box3D` round-trip natively through Parquet. It is also available as a Flink type. + +`Box3D` is the 3D counterpart to [Box2D](../box2d/Box2D-Functions.md) and complements the [Geometry](../Geometry-Functions.md) type. Use it when you need a compact, comparable bounding box that retains the Z extent — for example, as the join key in a spatial join that should match on all three axes. + +![Box3D anatomy: a cuboid spanning the (xmin, ymin, zmin) and (xmax, ymax, zmax) corners](../../../image/box3d/box3d_anatomy.svg "A Box3D is the cuboid between its two opposite corners") + +## Semantic notes + +- `Box3D` values use closed-interval semantics: edge-, face-, and corner-touching boxes are considered intersecting and contained. +- Absence is represented by SQL `NULL` rather than an in-band sentinel. +- Geometries without a Z dimension fold into `zmin = zmax = 0`, matching PostGIS. So `ST_Box3D` of a purely 2D geometry yields a box flush against the `z = 0` plane. +- Bounds are required to be ordered (`xmin <= xmax`, `ymin <= ymax`, `zmin <= zmax`) on all three axes. Unlike Box2D — where inverted X is reserved for a future antimeridian-wraparound semantics — Z has no wraparound convention, so all three axes are strictly ordered; predicates and join planning throw `IllegalArgumentException` on inverted input. + +## Box3D Constructors + +| Function | Return type | Description | Since | +| :--- | :--- | :--- | :--- | +| [ST_Box3D](Box3D-Constructors/ST_Box3D.md) | Box3D | Return the planar 3D bounding box of a Geometry as a Box3D (`z = 0` for geometries without a Z dimension). | v1.9.1 | +| [ST_3DMakeBox](Box3D-Constructors/ST_3DMakeBox.md) | Box3D | Build a Box3D from two corner POINT Z geometries. | v1.9.1 | + +## Box3D Accessors + +| Function | Return type | Description | Since | +| :--- | :--- | :--- | :--- | +| [ST_XMin](Box3D-Accessors/ST_XMin.md) | Double | Return the minimum X coordinate of a Box3D. | v1.9.1 | +| [ST_YMin](Box3D-Accessors/ST_YMin.md) | Double | Return the minimum Y coordinate of a Box3D. | v1.9.1 | +| [ST_ZMin](Box3D-Accessors/ST_ZMin.md) | Double | Return the minimum Z coordinate of a Box3D. | v1.9.1 | +| [ST_XMax](Box3D-Accessors/ST_XMax.md) | Double | Return the maximum X coordinate of a Box3D. | v1.9.1 | +| [ST_YMax](Box3D-Accessors/ST_YMax.md) | Double | Return the maximum Y coordinate of a Box3D. | v1.9.1 | +| [ST_ZMax](Box3D-Accessors/ST_ZMax.md) | Double | Return the maximum Z coordinate of a Box3D. | v1.9.1 | + +The same `ST_XMin` … `ST_ZMax` functions also accept `Geometry` inputs — see [Bounding Box Functions](../Geometry-Functions.md#bounding-box-functions). + +## Box3D Predicates + +`Box3D` inputs are accepted by the existing `ST_Intersects` / `ST_Contains` predicates as type-dispatched overloads, and by the dedicated `ST_3DDWithin` distance predicate — there are no separate `ST_3DBox*` functions. + +| Function | Return type | Description | Since | +| :--- | :--- | :--- | :--- | +| [ST_Intersects](../Predicates/ST_Intersects.md) | Boolean | Closed-interval bbox intersection on all three axes when both arguments are `Box3D`. Matches PostGIS `&&&` on `box3d`. | v1.9.1 | +| [ST_Contains](../Predicates/ST_Contains.md) | Boolean | Closed-interval bbox containment on all three axes when both arguments are `Box3D`. | v1.9.1 | +| [ST_3DDWithin](../Predicates/ST_3DDWithin.md) | Boolean | 3D Euclidean distance-within test, over Geometry or Box3D inputs. Mirrors PostGIS `ST_3DDWithin`. | v1.9.1 | + +## Box3D Functions + +| Function | Return type | Description | Since | +| :--- | :--- | :--- | :--- | +| [ST_AsText](Box3D-Functions/ST_AsText.md) | String | Return the `BOX3D(xmin ymin zmin, xmax ymax zmax)` text representation of a Box3D. | v1.9.1 | + +## Box3D Aggregates + +| Function | Return type | Description | Since | +| :--- | :--- | :--- | :--- | +| [ST_3DExtent](../Aggregate-Functions/ST_3DExtent.md) | Box3D | Return the 3D bounding box of all geometries in a column as a Box3D. Empty and NULL inputs are skipped; geometries without Z fold to `z = 0`. Mirrors PostGIS `ST_3DExtent`. | v1.9.1 | + +## Type conversion + +Catalyst recognises the SQL `CAST` from `Geometry` to `Box3D`: + +| Cast | Equivalent function | Notes | +| :--- | :--- | :--- | +| `CAST(geom AS box3d)` | [ST_Box3D(geom)](Box3D-Constructors/ST_Box3D.md) | Planar 3D bounding box of the geometry (`z = 0` when the geometry has no Z). | + +The cast form requires the Sedona SQL parser extension (`spark.sql.extensions=org.apache.sedona.sql.SedonaSqlExtensions`); the function form works in any Sedona-enabled session. The inverse cast (`CAST(box3d AS geometry)`) is not yet supported — there is no `ST_GeomFromBox3D` counterpart. + +## Query optimization + +`Box3D`-typed columns participate in Sedona's spatial join planner: + +- **Spatial joins.** `ST_Intersects` and `ST_Contains` between two `Box3D` columns, and `ST_3DDWithin` distance joins, route through the same physical operators (`RangeJoinExec`, `BroadcastIndexJoinExec`, `DistanceJoinExec`) used for the Geometry-typed forms. The planner projects each `Box3D` to its XY footprint for the R-tree pass and re-checks the Z axis per candidate via the original predicate, so a Box3D join is correct on all three axes while still benefiting from the 2D index. See [Query optimization → Range join](../Optimizer.md#range-join) and [Broadcast index join](../Optimizer.md#broadcast-index-join). +- **Filter pushdown** to Parquet row-group statistics is not yet implemented for `Box3D` columns (it exists for [Box2D](../box2d/Box2D-Functions.md#query-optimization)). diff --git a/docs/api/sql/box3d/Box3D-Functions/ST_AsText.md b/docs/api/sql/box3d/Box3D-Functions/ST_AsText.md new file mode 100644 index 00000000000..f1e0819f8d7 --- /dev/null +++ b/docs/api/sql/box3d/Box3D-Functions/ST_AsText.md @@ -0,0 +1,44 @@ + + +# ST_AsText + +Introduction: Return the PostGIS-style text representation of a `Box3D`: `BOX3D(xmin ymin zmin, xmax ymax zmax)`. This is the `Box3D` overload of `ST_AsText`; the [Geometry variant](../../Geometry-Output/ST_AsText.md) emits standard WKT. + +The bounds are emitted exactly as stored — `ST_AsText` does not normalize or reorder the corners. The text is not WKT (WKT has no `BOX3D` type), so it lives outside the `asWKT` family. + +Format: `ST_AsText(box: Box3D)` + +Return type: `String` + +Since: `v1.9.1` + +SQL Example + +```sql +SELECT ST_AsText(ST_3DMakeBox(ST_PointZ(0.0, 0.0, -3.0), ST_PointZ(5.0, 10.0, 7.0))) +``` + +Output: + +``` +BOX3D(0.0 0.0 -3.0, 5.0 10.0 7.0) +``` + +Returns `NULL` on `NULL` input. diff --git a/docs/image/box3d/box3d_anatomy.svg b/docs/image/box3d/box3d_anatomy.svg new file mode 100644 index 00000000000..831a5311f1f --- /dev/null +++ b/docs/image/box3d/box3d_anatomy.svg @@ -0,0 +1 @@ +xyz(xmin, ymin, zmin)(xmax, ymax, zmax)Box3D \ No newline at end of file diff --git a/docs/image/box3d/st_3ddwithin_false.svg b/docs/image/box3d/st_3ddwithin_false.svg new file mode 100644 index 00000000000..ff98381d933 --- /dev/null +++ b/docs/image/box3d/st_3ddwithin_false.svg @@ -0,0 +1 @@ +xyzST_3DDWithin (Box3D)3D gap exceeds the distancefalse \ No newline at end of file diff --git a/docs/image/box3d/st_3ddwithin_true.svg b/docs/image/box3d/st_3ddwithin_true.svg new file mode 100644 index 00000000000..44614a2a38f --- /dev/null +++ b/docs/image/box3d/st_3ddwithin_true.svg @@ -0,0 +1 @@ +xyzST_3DDWithin (Box3D)3D gap between boxes is within the distancetrue \ No newline at end of file diff --git a/docs/image/box3d/st_3dextent.svg b/docs/image/box3d/st_3dextent.svg new file mode 100644 index 00000000000..f2ea21ada28 --- /dev/null +++ b/docs/image/box3d/st_3dextent.svg @@ -0,0 +1 @@ +xyzST_3DExtent(geom)enclosing cuboid over a column of geometries \ No newline at end of file diff --git a/docs/image/box3d/st_3dmakebox.svg b/docs/image/box3d/st_3dmakebox.svg new file mode 100644 index 00000000000..37c73200c11 --- /dev/null +++ b/docs/image/box3d/st_3dmakebox.svg @@ -0,0 +1 @@ +xyzPOINT Z (lower)POINT Z (upper)ST_3DMakeBox(p1, p2) \ No newline at end of file diff --git a/docs/image/box3d/st_box3d.svg b/docs/image/box3d/st_box3d.svg new file mode 100644 index 00000000000..2d6617ff158 --- /dev/null +++ b/docs/image/box3d/st_box3d.svg @@ -0,0 +1 @@ +xyzST_Box3D(geometry)tightest cuboid enclosing the geometry \ No newline at end of file diff --git a/docs/image/box3d/st_contains_box3d_false.svg b/docs/image/box3d/st_contains_box3d_false.svg new file mode 100644 index 00000000000..2f9e1be1d19 --- /dev/null +++ b/docs/image/box3d/st_contains_box3d_false.svg @@ -0,0 +1 @@ +xyzST_Contains (Box3D)B is inside in XY but extends past A in Zfalse \ No newline at end of file diff --git a/docs/image/box3d/st_contains_box3d_true.svg b/docs/image/box3d/st_contains_box3d_true.svg new file mode 100644 index 00000000000..e2acf4ed7fb --- /dev/null +++ b/docs/image/box3d/st_contains_box3d_true.svg @@ -0,0 +1 @@ +xyzST_Contains (Box3D)B lies fully inside A on all three axestrue \ No newline at end of file diff --git a/docs/image/box3d/st_intersects_box3d_false.svg b/docs/image/box3d/st_intersects_box3d_false.svg new file mode 100644 index 00000000000..2ee08948409 --- /dev/null +++ b/docs/image/box3d/st_intersects_box3d_false.svg @@ -0,0 +1 @@ +xyzST_Intersects (Box3D)XY footprints overlap, but Z ranges are disjointfalse \ No newline at end of file diff --git a/docs/image/box3d/st_intersects_box3d_true.svg b/docs/image/box3d/st_intersects_box3d_true.svg new file mode 100644 index 00000000000..5f5eb98154a --- /dev/null +++ b/docs/image/box3d/st_intersects_box3d_true.svg @@ -0,0 +1 @@ +xyzST_Intersects (Box3D)boxes overlap on all three axestrue \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 6115bebf712..3652503dff7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -76,6 +76,7 @@ nav: - Vector data: - Geometry Functions: api/sql/Geometry-Functions.md - Box2D Functions: api/sql/box2d/Box2D-Functions.md + - Box3D Functions: api/sql/box3d/Box3D-Functions.md - Geography Functions: api/sql/geography/Geography-Functions.md - DataFrame Style functions: api/sql/DataFrameAPI.md - Query optimization: api/sql/Optimizer.md @@ -316,6 +317,7 @@ plugins: Vector data: 矢量数据 Geometry Functions: 几何函数 Box2D Functions: Box2D 函数 + Box3D Functions: Box3D 函数 Geography Functions: 地理函数 DataFrame Style functions: DataFrame 风格函数 Query optimization: 查询优化