You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sedona.apache.org by ji...@apache.org on 2026/06/11 07:44:08 UTC
(sedona) branch master updated: [GH-3039] Flink Box3D accessors, ST_AsText, and ST_3DExtent (#3040)
This is an automated email from the ASF dual-hosted git repository.
jiayuasu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sedona.git
The following commit(s) were added to refs/heads/master by this push:
new 1a633fccbe [GH-3039] Flink Box3D accessors, ST_AsText, and ST_3DExtent (#3040)
1a633fccbe is described below
commit 1a633fccbe7be1c65b36480f9d03b75f0cab7336
Author: Jia Yu <ji...@apache.org>
AuthorDate: Thu Jun 11 00:44:03 2026 -0700
[GH-3039] Flink Box3D accessors, ST_AsText, and ST_3DExtent (#3040)
---
.../main/java/org/apache/sedona/flink/Catalog.java | 1 +
.../sedona/flink/expressions/Accumulators.java | 18 ++++++
.../sedona/flink/expressions/Aggregators.java | 65 ++++++++++++++++++++
.../apache/sedona/flink/expressions/Functions.java | 70 ++++++++++++++++++++++
.../org/apache/sedona/flink/AggregatorTest.java | 33 ++++++++++
.../java/org/apache/sedona/flink/FunctionTest.java | 18 ++++++
6 files changed, 205 insertions(+)
diff --git a/flink/src/main/java/org/apache/sedona/flink/Catalog.java b/flink/src/main/java/org/apache/sedona/flink/Catalog.java
index 65097b0e44..e66ff5dfaa 100644
--- a/flink/src/main/java/org/apache/sedona/flink/Catalog.java
+++ b/flink/src/main/java/org/apache/sedona/flink/Catalog.java
@@ -26,6 +26,7 @@ public class Catalog {
return new UserDefinedFunction[] {
new Aggregators.ST_Envelope_Aggr(),
new Aggregators.ST_Extent(),
+ new Aggregators.ST_3DExtent(),
new Aggregators.ST_Intersection_Aggr(),
new Aggregators.ST_Union_Aggr(),
// Aliases for *_Aggr functions with *_Agg suffix
diff --git a/flink/src/main/java/org/apache/sedona/flink/expressions/Accumulators.java b/flink/src/main/java/org/apache/sedona/flink/expressions/Accumulators.java
index f51784870a..9550934db9 100644
--- a/flink/src/main/java/org/apache/sedona/flink/expressions/Accumulators.java
+++ b/flink/src/main/java/org/apache/sedona/flink/expressions/Accumulators.java
@@ -39,6 +39,24 @@ public class Accumulators {
}
}
+ public static class Envelope3D {
+ public double minX = Double.MAX_VALUE;
+ public double minY = Double.MAX_VALUE;
+ public double minZ = Double.MAX_VALUE;
+ public double maxX = -Double.MAX_VALUE;
+ public double maxY = -Double.MAX_VALUE;
+ public double maxZ = -Double.MAX_VALUE;
+
+ void reset() {
+ minX = Double.MAX_VALUE;
+ minY = Double.MAX_VALUE;
+ minZ = Double.MAX_VALUE;
+ maxX = -Double.MAX_VALUE;
+ maxY = -Double.MAX_VALUE;
+ maxZ = -Double.MAX_VALUE;
+ }
+ }
+
public static class AccGeometry {
@DataTypeHint(
value = "RAW",
diff --git a/flink/src/main/java/org/apache/sedona/flink/expressions/Aggregators.java b/flink/src/main/java/org/apache/sedona/flink/expressions/Aggregators.java
index 1e15c04f37..ff13fd8e73 100644
--- a/flink/src/main/java/org/apache/sedona/flink/expressions/Aggregators.java
+++ b/flink/src/main/java/org/apache/sedona/flink/expressions/Aggregators.java
@@ -21,7 +21,9 @@ package org.apache.sedona.flink.expressions;
import org.apache.flink.table.annotation.DataTypeHint;
import org.apache.flink.table.functions.AggregateFunction;
import org.apache.sedona.common.geometryObjects.Box2D;
+import org.apache.sedona.common.geometryObjects.Box3D;
import org.apache.sedona.flink.Box2DTypeSerializer;
+import org.apache.sedona.flink.Box3DTypeSerializer;
import org.apache.sedona.flink.GeometryTypeSerializer;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Envelope;
@@ -168,6 +170,69 @@ public class Aggregators {
}
}
+ // Aggregate the 3D bounding box of all input geometries as a Box3D. Mirrors PostGIS
+ // ST_3DExtent. Geometries without a Z dimension fold into z = 0 per coordinate (via
+ // Functions.box3D). Returns null when there are no rows or all inputs are null/empty.
+ @DataTypeHint(value = "RAW", rawSerializer = Box3DTypeSerializer.class, bridgedTo = Box3D.class)
+ public static class ST_3DExtent extends AggregateFunction<Box3D, Accumulators.Envelope3D> {
+
+ @Override
+ public Accumulators.Envelope3D createAccumulator() {
+ return new Accumulators.Envelope3D();
+ }
+
+ @Override
+ @DataTypeHint(value = "RAW", rawSerializer = Box3DTypeSerializer.class, bridgedTo = Box3D.class)
+ public Box3D getValue(Accumulators.Envelope3D acc) {
+ if (acc.minX > acc.maxX) return null;
+ return new Box3D(acc.minX, acc.minY, acc.minZ, acc.maxX, acc.maxY, acc.maxZ);
+ }
+
+ public void accumulate(
+ Accumulators.Envelope3D acc,
+ @DataTypeHint(
+ value = "RAW",
+ rawSerializer = GeometryTypeSerializer.class,
+ bridgedTo = Geometry.class)
+ Object o) {
+ if (o == null) return;
+ // Functions.box3D folds missing Z to 0 and returns null for empty geometries.
+ Box3D box = org.apache.sedona.common.Functions.box3D((Geometry) o);
+ if (box == null) return;
+ acc.minX = Math.min(acc.minX, box.getXMin());
+ acc.minY = Math.min(acc.minY, box.getYMin());
+ acc.minZ = Math.min(acc.minZ, box.getZMin());
+ acc.maxX = Math.max(acc.maxX, box.getXMax());
+ acc.maxY = Math.max(acc.maxY, box.getYMax());
+ acc.maxZ = Math.max(acc.maxZ, box.getZMax());
+ }
+
+ public void retract(
+ Accumulators.Envelope3D acc,
+ @DataTypeHint(
+ value = "RAW",
+ rawSerializer = GeometryTypeSerializer.class,
+ bridgedTo = Geometry.class)
+ Object o) {
+ assert (false);
+ }
+
+ public void merge(Accumulators.Envelope3D acc, Iterable<Accumulators.Envelope3D> it) {
+ for (Accumulators.Envelope3D a : it) {
+ acc.minX = Math.min(acc.minX, a.minX);
+ acc.minY = Math.min(acc.minY, a.minY);
+ acc.minZ = Math.min(acc.minZ, a.minZ);
+ acc.maxX = Math.max(acc.maxX, a.maxX);
+ acc.maxY = Math.max(acc.maxY, a.maxY);
+ acc.maxZ = Math.max(acc.maxZ, a.maxZ);
+ }
+ }
+
+ public void resetAccumulator(Accumulators.Envelope3D acc) {
+ acc.reset();
+ }
+ }
+
// Compute the Union boundary of numbers of geometries
//
@DataTypeHint(
diff --git a/flink/src/main/java/org/apache/sedona/flink/expressions/Functions.java b/flink/src/main/java/org/apache/sedona/flink/expressions/Functions.java
index 6d6560910a..e32835a780 100644
--- a/flink/src/main/java/org/apache/sedona/flink/expressions/Functions.java
+++ b/flink/src/main/java/org/apache/sedona/flink/expressions/Functions.java
@@ -984,6 +984,16 @@ public class Functions {
Box2D box) {
return org.apache.sedona.common.Functions.yMin(box);
}
+
+ @DataTypeHint("Double")
+ public Double eval(
+ @DataTypeHint(
+ value = "RAW",
+ rawSerializer = Box3DTypeSerializer.class,
+ bridgedTo = Box3D.class)
+ Box3D box) {
+ return org.apache.sedona.common.Functions.yMin(box);
+ }
}
public static class ST_YMax extends ScalarFunction {
@@ -1007,6 +1017,16 @@ public class Functions {
Box2D box) {
return org.apache.sedona.common.Functions.yMax(box);
}
+
+ @DataTypeHint("Double")
+ public Double eval(
+ @DataTypeHint(
+ value = "RAW",
+ rawSerializer = Box3DTypeSerializer.class,
+ bridgedTo = Box3D.class)
+ Box3D box) {
+ return org.apache.sedona.common.Functions.yMax(box);
+ }
}
public static class ST_ZMax extends ScalarFunction {
@@ -1020,6 +1040,16 @@ public class Functions {
Geometry geom = (Geometry) o;
return org.apache.sedona.common.Functions.zMax(geom);
}
+
+ @DataTypeHint("Double")
+ public Double eval(
+ @DataTypeHint(
+ value = "RAW",
+ rawSerializer = Box3DTypeSerializer.class,
+ bridgedTo = Box3D.class)
+ Box3D box) {
+ return org.apache.sedona.common.Functions.zMax(box);
+ }
}
public static class ST_ZMin extends ScalarFunction {
@@ -1033,6 +1063,16 @@ public class Functions {
Geometry geom = (Geometry) o;
return org.apache.sedona.common.Functions.zMin(geom);
}
+
+ @DataTypeHint("Double")
+ public Double eval(
+ @DataTypeHint(
+ value = "RAW",
+ rawSerializer = Box3DTypeSerializer.class,
+ bridgedTo = Box3D.class)
+ Box3D box) {
+ return org.apache.sedona.common.Functions.zMin(box);
+ }
}
public static class ST_NDims extends ScalarFunction {
@@ -1370,6 +1410,16 @@ public class Functions {
Box2D box) {
return org.apache.sedona.common.Functions.box2dAsText(box);
}
+
+ @DataTypeHint("String")
+ public String eval(
+ @DataTypeHint(
+ value = "RAW",
+ rawSerializer = Box3DTypeSerializer.class,
+ bridgedTo = Box3D.class)
+ Box3D box) {
+ return org.apache.sedona.common.Functions.box3dAsText(box);
+ }
}
public static class ST_AsEWKB extends ScalarFunction {
@@ -1592,6 +1642,16 @@ public class Functions {
Box2D box) {
return org.apache.sedona.common.Functions.xMax(box);
}
+
+ @DataTypeHint("Double")
+ public Double eval(
+ @DataTypeHint(
+ value = "RAW",
+ rawSerializer = Box3DTypeSerializer.class,
+ bridgedTo = Box3D.class)
+ Box3D box) {
+ return org.apache.sedona.common.Functions.xMax(box);
+ }
}
public static class ST_XMin extends ScalarFunction {
@@ -1615,6 +1675,16 @@ public class Functions {
Box2D box) {
return org.apache.sedona.common.Functions.xMin(box);
}
+
+ @DataTypeHint("Double")
+ public Double eval(
+ @DataTypeHint(
+ value = "RAW",
+ rawSerializer = Box3DTypeSerializer.class,
+ bridgedTo = Box3D.class)
+ Box3D box) {
+ return org.apache.sedona.common.Functions.xMin(box);
+ }
}
public static class ST_BuildArea extends ScalarFunction {
diff --git a/flink/src/test/java/org/apache/sedona/flink/AggregatorTest.java b/flink/src/test/java/org/apache/sedona/flink/AggregatorTest.java
index e1af138fdc..1fb0509a84 100644
--- a/flink/src/test/java/org/apache/sedona/flink/AggregatorTest.java
+++ b/flink/src/test/java/org/apache/sedona/flink/AggregatorTest.java
@@ -25,6 +25,7 @@ import static org.junit.Assert.assertNull;
import org.apache.flink.table.api.*;
import org.apache.flink.types.Row;
import org.apache.sedona.common.geometryObjects.Box2D;
+import org.apache.sedona.common.geometryObjects.Box3D;
import org.apache.sedona.flink.expressions.Functions;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -64,6 +65,38 @@ public class AggregatorTest extends TestBase {
assertEquals(5.0, bbox.getYMax(), 0.0);
}
+ @Test
+ public void test3DExtent() {
+ tableEnv.executeSql(
+ "CREATE OR REPLACE TEMPORARY VIEW extent3d_view AS "
+ + "SELECT ST_GeomFromWKT(wkt) as geom FROM ("
+ + "VALUES ('POINT Z (1 2 3)'), ('POINT Z (4 5 -1)'), ('LINESTRING (-3 0, 0 0)')"
+ + ") AS t(wkt)");
+ Table result = tableEnv.sqlQuery("SELECT ST_3DExtent(geom) FROM extent3d_view");
+ Row last = last(result);
+ Box3D bbox = (Box3D) last.getField(0);
+ assertEquals(-3.0, bbox.getXMin(), 0.0);
+ assertEquals(0.0, bbox.getYMin(), 0.0);
+ // z-min is -1.0 from POINT Z (4 5 -1); the XY-only linestring folds to z = 0, which sits
+ // between the -1 and 3 of the two POINT Z rows and so does not move either Z bound.
+ assertEquals(-1.0, bbox.getZMin(), 0.0);
+ assertEquals(4.0, bbox.getXMax(), 0.0);
+ assertEquals(5.0, bbox.getYMax(), 0.0);
+ assertEquals(3.0, bbox.getZMax(), 0.0);
+ }
+
+ @Test
+ public void test3DExtent_EmptyAndNullGeometries() {
+ tableEnv.executeSql(
+ "CREATE OR REPLACE TEMPORARY VIEW null_extent3d_view AS "
+ + "SELECT ST_GeomFromWKT(wkt) as geom FROM ("
+ + "VALUES (CAST(NULL AS STRING)), ('POINT EMPTY'), ('POLYGON EMPTY')"
+ + ") AS t(wkt)");
+ Table result = tableEnv.sqlQuery("SELECT ST_3DExtent(geom) FROM null_extent3d_view");
+ Row last = last(result);
+ assertNull(last.getField(0));
+ }
+
@Test
public void testExtent_EmptyAndNullGeometries() {
tableEnv.executeSql(
diff --git a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
index f565b591d5..e0621869e3 100644
--- a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
+++ b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
@@ -506,6 +506,24 @@ public class FunctionTest extends TestBase {
assertEquals(5.0, row.getField(4));
}
+ @Test
+ public void testBox3DAsTextAndAccessors() {
+ Table t =
+ tableEnv.sqlQuery(
+ "WITH bx AS ("
+ + " SELECT ST_Box3D(ST_GeomFromText('LINESTRING Z(0 0 -3, 5 10 7)')) AS b)"
+ + " SELECT ST_AsText(b), ST_XMin(b), ST_YMin(b), ST_ZMin(b),"
+ + " ST_XMax(b), ST_YMax(b), ST_ZMax(b) FROM bx");
+ Row row = first(t);
+ assertEquals("BOX3D(0.0 0.0 -3.0, 5.0 10.0 7.0)", row.getField(0));
+ assertEquals(0.0, row.getField(1));
+ assertEquals(0.0, row.getField(2));
+ assertEquals(-3.0, row.getField(3));
+ assertEquals(5.0, row.getField(4));
+ assertEquals(10.0, row.getField(5));
+ assertEquals(7.0, row.getField(6));
+ }
+
@Test
public void testEnvelope() {
Table linestringTable = createLineStringTable(1);