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/02/07 21:20:46 UTC
(sedona) branch fix/1979-envelope-empty created (now ca1ade779f)
This is an automated email from the ASF dual-hosted git repository.
jiayu pushed a change to branch fix/1979-envelope-empty
in repository https://gitbox.apache.org/repos/asf/sedona.git
at ca1ade779f [GH-1979] Fix ST_Envelope and ST_Envelope_Aggr empty geometry handling
This branch includes the following new commits:
new ca1ade779f [GH-1979] Fix ST_Envelope and ST_Envelope_Aggr empty geometry handling
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(sedona) 01/01: [GH-1979] Fix ST_Envelope and ST_Envelope_Aggr empty geometry handling
Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
jiayu pushed a commit to branch fix/1979-envelope-empty
in repository https://gitbox.apache.org/repos/asf/sedona.git
commit ca1ade779f2937c1ac3d1e0f6a795c8bbf2a4c67
Author: Jia Yu <ji...@apache.org>
AuthorDate: Sat Feb 7 13:14:42 2026 -0800
[GH-1979] Fix ST_Envelope and ST_Envelope_Aggr empty geometry handling
ST_Envelope (scalar): Already correctly returns same-type EMPTY geometry for
empty inputs (matching PostGIS). Added test to verify this behavior.
ST_Envelope_Aggr (aggregate): Skip empty geometries during accumulation and
return null when all inputs are empty, matching PostGIS ST_Extent behavior.
Fixed in Spark (AggregateFunctions.scala), Flink (Aggregators.java), and
Snowflake (ST_Envelope_Aggr.java, ST_Envelope_Agg.java).
Fixes #1979
---
.../java/org/apache/sedona/common/FunctionsTest.java | 19 +++++++++++++++++++
.../apache/sedona/flink/expressions/Aggregators.java | 5 ++++-
.../snowflake/snowsql/udtfs/ST_Envelope_Agg.java | 6 ++++++
.../snowflake/snowsql/udtfs/ST_Envelope_Aggr.java | 6 ++++++
.../sedona_sql/expressions/AggregateFunctions.scala | 2 +-
5 files changed, 36 insertions(+), 2 deletions(-)
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..5c94dca7c7 100644
--- a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
+++ b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
@@ -808,6 +808,25 @@ public class FunctionsTest extends TestBase {
assertEquals(4326, concave.getSRID());
}
+ @Test
+ public void envelopeEmptyGeometry() throws ParseException {
+ // ST_Envelope of EMPTY should return same-type EMPTY (matching PostGIS behavior)
+ Geometry emptyLineString = Constructors.geomFromWKT("LINESTRING EMPTY", 0);
+ Geometry result = Functions.envelope(emptyLineString);
+ assertTrue(result.isEmpty());
+ assertEquals("LineString", result.getGeometryType());
+
+ Geometry emptyPolygon = Constructors.geomFromWKT("POLYGON EMPTY", 0);
+ result = Functions.envelope(emptyPolygon);
+ assertTrue(result.isEmpty());
+ assertEquals("Polygon", result.getGeometryType());
+
+ Geometry emptyPoint = Constructors.geomFromWKT("POINT EMPTY", 0);
+ result = Functions.envelope(emptyPoint);
+ assertTrue(result.isEmpty());
+ assertEquals("Point", result.getGeometryType());
+ }
+
@Test
public void envelopeAndCentroidSRID() throws ParseException {
Geometry geom = Constructors.geomFromWKT("POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))", 3857);
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 84cebd6adc..244d5f4b6c 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
@@ -56,6 +56,7 @@ public class Aggregators {
rawSerializer = GeometryTypeSerializer.class,
bridgedTo = Geometry.class)
public Geometry getValue(Accumulators.Envelope acc) {
+ if (acc.minX > acc.maxX) return null;
return createPolygon(acc.minX, acc.minY, acc.maxX, acc.maxY);
}
@@ -66,7 +67,9 @@ public class Aggregators {
rawSerializer = GeometryTypeSerializer.class,
bridgedTo = Geometry.class)
Object o) {
- Envelope envelope = ((Geometry) o).getEnvelopeInternal();
+ Geometry geometry = (Geometry) o;
+ if (geometry.isEmpty()) return;
+ Envelope envelope = geometry.getEnvelopeInternal();
acc.minX = Math.min(acc.minX, envelope.getMinX());
acc.minY = Math.min(acc.minY, envelope.getMinY());
acc.maxX = Math.max(acc.maxX, envelope.getMaxX());
diff --git a/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/udtfs/ST_Envelope_Agg.java b/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/udtfs/ST_Envelope_Agg.java
index f02cbe1068..4a5e6bbea9 100644
--- a/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/udtfs/ST_Envelope_Agg.java
+++ b/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/udtfs/ST_Envelope_Agg.java
@@ -50,6 +50,9 @@ public class ST_Envelope_Agg {
public Stream<OutputRow> process(byte[] geom) throws ParseException {
Geometry geometry = GeometrySerde.deserialize(geom);
+ if (geometry.isEmpty()) {
+ return Stream.empty();
+ }
if (buffer == null) {
buffer = geometry.getEnvelopeInternal();
} else {
@@ -59,6 +62,9 @@ public class ST_Envelope_Agg {
}
public Stream<OutputRow> endPartition() {
+ if (buffer == null || buffer.isNull()) {
+ return Stream.empty();
+ }
// Returns the value we initialized in the constructor.
Polygon poly =
geometryFactory.createPolygon(
diff --git a/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/udtfs/ST_Envelope_Aggr.java b/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/udtfs/ST_Envelope_Aggr.java
index 10aa2d5afa..b1d05e437c 100644
--- a/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/udtfs/ST_Envelope_Aggr.java
+++ b/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/udtfs/ST_Envelope_Aggr.java
@@ -50,6 +50,9 @@ public class ST_Envelope_Aggr {
public Stream<OutputRow> process(byte[] geom) throws ParseException {
Geometry geometry = GeometrySerde.deserialize(geom);
+ if (geometry.isEmpty()) {
+ return Stream.empty();
+ }
if (buffer == null) {
buffer = geometry.getEnvelopeInternal();
} else {
@@ -59,6 +62,9 @@ public class ST_Envelope_Aggr {
}
public Stream<OutputRow> endPartition() {
+ if (buffer == null || buffer.isNull()) {
+ return Stream.empty();
+ }
// Returns the value we initialized in the constructor.
Polygon poly =
geometryFactory.createPolygon(
diff --git a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/AggregateFunctions.scala b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/AggregateFunctions.scala
index ca169a2598..608ad5c141 100644
--- a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/AggregateFunctions.scala
+++ b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/AggregateFunctions.scala
@@ -130,7 +130,7 @@ private[apache] class ST_Envelope_Aggr
val serde = ExpressionEncoder[Geometry]()
def reduce(buffer: Option[EnvelopeBuffer], input: Geometry): Option[EnvelopeBuffer] = {
- if (input == null) return buffer
+ if (input == null || input.isEmpty) return buffer
val env = input.getEnvelopeInternal
val envBuffer = EnvelopeBuffer(env.getMinX, env.getMaxX, env.getMinY, env.getMaxY)
buffer match {