This is an automated email from the ASF dual-hosted git repository. jiayu pushed a commit to branch fix/1874-bounds-empty-null in repository https://gitbox.apache.org/repos/asf/sedona.git
commit 1159aa75e45a495dfd3694d039349569e33488ae Author: Jia Yu <[email protected]> AuthorDate: Sat Feb 7 13:01:50 2026 -0800 [GH-1874] Fix ST_XMin/XMax/YMin/YMax returning sentinel values for EMPTY geometries Changed return type from primitive double to Double for xMin, xMax, yMin, yMax in common Functions and Snowflake UDF wrappers. When geometry is EMPTY (zero coordinates), these functions now return null instead of Double.MAX_VALUE or -Double.MAX_VALUE, matching PostGIS behavior and the existing zMin/zMax pattern. Flink and Spark wrappers already use boxed Double and need no changes. Added unit tests for EMPTY geometries (Point, Polygon, LineString) and non-empty geometry bounds verification. Fixes #1874 --- .../java/org/apache/sedona/common/Functions.java | 16 ++++++------ .../org/apache/sedona/common/FunctionsTest.java | 30 ++++++++++++++++++++++ .../org/apache/sedona/snowflake/snowsql/UDFs.java | 8 +++--- .../apache/sedona/snowflake/snowsql/UDFsV2.java | 8 +++--- 4 files changed, 46 insertions(+), 16 deletions(-) diff --git a/common/src/main/java/org/apache/sedona/common/Functions.java b/common/src/main/java/org/apache/sedona/common/Functions.java index 0be88a9b1b..57f0d9ba8f 100644 --- a/common/src/main/java/org/apache/sedona/common/Functions.java +++ b/common/src/main/java/org/apache/sedona/common/Functions.java @@ -665,40 +665,40 @@ public class Functions { return max == -Double.MAX_VALUE ? null : max; } - public static double xMin(Geometry geometry) { + public static Double xMin(Geometry geometry) { Coordinate[] points = geometry.getCoordinates(); double min = Double.MAX_VALUE; for (int i = 0; i < points.length; i++) { min = Math.min(points[i].getX(), min); } - return min; + return min == Double.MAX_VALUE ? null : min; } - public static double xMax(Geometry geometry) { + public static Double xMax(Geometry geometry) { Coordinate[] points = geometry.getCoordinates(); double max = -Double.MAX_VALUE; for (int i = 0; i < points.length; i++) { max = Math.max(points[i].getX(), max); } - return max; + return max == -Double.MAX_VALUE ? null : max; } - public static double yMin(Geometry geometry) { + public static Double yMin(Geometry geometry) { Coordinate[] points = geometry.getCoordinates(); double min = Double.MAX_VALUE; for (int i = 0; i < points.length; i++) { min = Math.min(points[i].getY(), min); } - return min; + return min == Double.MAX_VALUE ? null : min; } - public static double yMax(Geometry geometry) { + public static Double yMax(Geometry geometry) { Coordinate[] points = geometry.getCoordinates(); double max = -Double.MAX_VALUE; for (int i = 0; i < points.length; i++) { max = Math.max(points[i].getY(), max); } - return max; + return max == -Double.MAX_VALUE ? null : max; } public static Double zMax(Geometry geometry) { diff --git a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java index e379e67d6d..9f95d0d75e 100644 --- a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java +++ b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java @@ -3580,6 +3580,36 @@ public class FunctionsTest extends TestBase { assertEquals(expected, actual); } + @Test + public void boundsEmptyGeometryReturnsNull() { + Geometry emptyPoint = GEOMETRY_FACTORY.createPoint(); + assertNull(Functions.xMin(emptyPoint)); + assertNull(Functions.xMax(emptyPoint)); + assertNull(Functions.yMin(emptyPoint)); + assertNull(Functions.yMax(emptyPoint)); + + Geometry emptyPolygon = GEOMETRY_FACTORY.createPolygon(); + assertNull(Functions.xMin(emptyPolygon)); + assertNull(Functions.xMax(emptyPolygon)); + assertNull(Functions.yMin(emptyPolygon)); + assertNull(Functions.yMax(emptyPolygon)); + + Geometry emptyLineString = GEOMETRY_FACTORY.createLineString(); + assertNull(Functions.xMin(emptyLineString)); + assertNull(Functions.xMax(emptyLineString)); + assertNull(Functions.yMin(emptyLineString)); + assertNull(Functions.yMax(emptyLineString)); + } + + @Test + public void boundsNonEmptyGeometry() throws ParseException { + Geometry polygon = Constructors.geomFromWKT("POLYGON ((-1 -11, 0 10, 1 11, 2 12, -1 -11))", 0); + assertEquals(-1.0, Functions.xMin(polygon), 1e-9); + assertEquals(2.0, Functions.xMax(polygon), 1e-9); + assertEquals(-11.0, Functions.yMin(polygon), 1e-9); + assertEquals(12.0, Functions.yMax(polygon), 1e-9); + } + @Test public void angleFourPoints() { Point start1 = GEOMETRY_FACTORY.createPoint(new Coordinate(0, 0)); diff --git a/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFs.java b/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFs.java index 3e2d0095a6..ccc5d88bea 100644 --- a/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFs.java +++ b/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFs.java @@ -1188,12 +1188,12 @@ public class UDFs { } @UDFAnnotations.ParamMeta(argNames = {"geometry"}) - public static double ST_XMax(byte[] geometry) { + public static Double ST_XMax(byte[] geometry) { return Functions.xMax(GeometrySerde.deserialize(geometry)); } @UDFAnnotations.ParamMeta(argNames = {"geometry"}) - public static double ST_XMin(byte[] geometry) { + public static Double ST_XMin(byte[] geometry) { return Functions.xMin(GeometrySerde.deserialize(geometry)); } @@ -1203,12 +1203,12 @@ public class UDFs { } @UDFAnnotations.ParamMeta(argNames = {"geometry"}) - public static double ST_YMax(byte[] geometry) { + public static Double ST_YMax(byte[] geometry) { return Functions.yMax(GeometrySerde.deserialize(geometry)); } @UDFAnnotations.ParamMeta(argNames = {"geometry"}) - public static double ST_YMin(byte[] geometry) { + public static Double ST_YMin(byte[] geometry) { return Functions.yMin(GeometrySerde.deserialize(geometry)); } diff --git a/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFsV2.java b/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFsV2.java index 5d545f4b6c..0cc7b4e622 100644 --- a/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFsV2.java +++ b/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFsV2.java @@ -1427,14 +1427,14 @@ public class UDFsV2 { @UDFAnnotations.ParamMeta( argNames = {"geometry"}, argTypes = {"Geometry"}) - public static double ST_XMax(String geometry) { + public static Double ST_XMax(String geometry) { return Functions.xMax(GeometrySerde.deserGeoJson(geometry)); } @UDFAnnotations.ParamMeta( argNames = {"geometry"}, argTypes = {"Geometry"}) - public static double ST_XMin(String geometry) { + public static Double ST_XMin(String geometry) { return Functions.xMin(GeometrySerde.deserGeoJson(geometry)); } @@ -1448,14 +1448,14 @@ public class UDFsV2 { @UDFAnnotations.ParamMeta( argNames = {"geometry"}, argTypes = {"Geometry"}) - public static double ST_YMax(String geometry) { + public static Double ST_YMax(String geometry) { return Functions.yMax(GeometrySerde.deserGeoJson(geometry)); } @UDFAnnotations.ParamMeta( argNames = {"geometry"}, argTypes = {"Geometry"}) - public static double ST_YMin(String geometry) { + public static Double ST_YMin(String geometry) { return Functions.yMin(GeometrySerde.deserGeoJson(geometry)); }
