This is an automated email from the ASF dual-hosted git repository.

jiayu 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 4e40828598 [GH-2304]add Multipolygon encode / decode (#2303)
4e40828598 is described below

commit 4e4082859857ddbb045088a295ed3294e236f300
Author: Zhuocheng Shang <122398181+zhuochengsh...@users.noreply.github.com>
AuthorDate: Wed Aug 20 11:19:46 2025 -0700

    [GH-2304]add Multipolygon encode / decode (#2303)
    
    * add Multipolygon encode / decode
    
    * more solid streaming read in
---
 .../sedona/common/S2Geography/Geography.java       |  4 +-
 .../common/S2Geography/GeographyCollection.java    |  2 +-
 .../common/S2Geography/MultiPolygonGeography.java  | 61 ++++++++++++++++++++++
 .../common/S2Geography/PolygonGeographyTest.java   | 42 +++++++++++++++
 .../sedona/common/S2Geography/TestHelper.java      |  8 +++
 5 files changed, 115 insertions(+), 2 deletions(-)

diff --git 
a/common/src/main/java/org/apache/sedona/common/S2Geography/Geography.java 
b/common/src/main/java/org/apache/sedona/common/S2Geography/Geography.java
index da2c0a5dbc..bfc5d6df35 100644
--- a/common/src/main/java/org/apache/sedona/common/S2Geography/Geography.java
+++ b/common/src/main/java/org/apache/sedona/common/S2Geography/Geography.java
@@ -244,9 +244,11 @@ public abstract class Geography {
         geo = PolylineGeography.decode(in, tag);
         break;
       case POLYGON:
-      case MULTIPOLYGON:
         geo = PolygonGeography.decode(in, tag);
         break;
+      case MULTIPOLYGON:
+        geo = MultiPolygonGeography.decode(in, tag);
+        break;
       case GEOGRAPHY_COLLECTION:
         geo = GeographyCollection.decode(in, tag);
         break;
diff --git 
a/common/src/main/java/org/apache/sedona/common/S2Geography/GeographyCollection.java
 
b/common/src/main/java/org/apache/sedona/common/S2Geography/GeographyCollection.java
index d384101c29..71e9f84624 100644
--- 
a/common/src/main/java/org/apache/sedona/common/S2Geography/GeographyCollection.java
+++ 
b/common/src/main/java/org/apache/sedona/common/S2Geography/GeographyCollection.java
@@ -152,7 +152,7 @@ public class GeographyCollection extends Geography {
     return geo;
   }
 
-  private void countShapes() {
+  void countShapes() {
     numShapesList.clear();
     totalShapes = 0;
     for (Geography geo : features) {
diff --git 
a/common/src/main/java/org/apache/sedona/common/S2Geography/MultiPolygonGeography.java
 
b/common/src/main/java/org/apache/sedona/common/S2Geography/MultiPolygonGeography.java
index ec71252874..32090b5c94 100644
--- 
a/common/src/main/java/org/apache/sedona/common/S2Geography/MultiPolygonGeography.java
+++ 
b/common/src/main/java/org/apache/sedona/common/S2Geography/MultiPolygonGeography.java
@@ -18,11 +18,19 @@
  */
 package org.apache.sedona.common.S2Geography;
 
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.UnsafeInput;
+import com.esotericsoftware.kryo.io.UnsafeOutput;
 import com.google.common.geometry.S2Polygon;
+import java.io.EOFException;
+import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.logging.Logger;
 
 public class MultiPolygonGeography extends GeographyCollection {
+  private static final Logger logger = 
Logger.getLogger(MultiPolygonGeography.class.getName());
   /**
    * Wrap each raw S2Polygon in a PolygonGeography, then hand it off to 
GeographyCollection to do
    * the rest (including serialization).
@@ -44,4 +52,57 @@ public class MultiPolygonGeography extends 
GeographyCollection {
     // every child PolygonGeography
     return 2;
   }
+
+  @Override
+  public void encode(UnsafeOutput out, EncodeOptions opts) throws IOException {
+    out.writeInt(features.size());
+    for (Geography feature : features) {
+      ((PolygonGeography) feature).polygon.encode(out);
+    }
+    out.flush();
+  }
+
+  /** This is what decodeTagged() actually calls */
+  public static MultiPolygonGeography decode(Input in, EncodeTag tag) throws 
IOException {
+    // cast to UnsafeInput—will work if you always pass a Kryo-backed stream
+    if (!(in instanceof UnsafeInput)) {
+      throw new IllegalArgumentException("Expected UnsafeInput");
+    }
+    return decode((UnsafeInput) in, tag);
+  }
+
+  /** Decodes a GeographyCollection from a tagged input stream. */
+  public static MultiPolygonGeography decode(UnsafeInput in, EncodeTag tag)
+      throws IOException, EOFException {
+    // Handle EMPTY flag
+    if ((tag.getFlags() & EncodeTag.FLAG_EMPTY) != 0) {
+      logger.fine("Decoded empty Multipolygon.");
+      return new MultiPolygonGeography();
+    }
+
+    // Skip any covering data
+    tag.skipCovering(in);
+
+    MultiPolygonGeography geo;
+    try {
+      int count = in.readInt();
+      if (count < 0) {
+        throw new IOException("MultiPolygon.decodeTagged error: negative 
polygon count: " + count);
+      }
+
+      List<S2Polygon> polygons = new ArrayList<>();
+      for (int i = 0; i < count; i++) {
+        polygons.add(S2Polygon.decode(in));
+      }
+
+      geo = new MultiPolygonGeography(GeographyKind.MULTIPOLYGON, polygons);
+      geo.countShapes();
+
+    } catch (EOFException e) {
+      throw new IOException(
+          "MultiPolygon.decodeTagged error: insufficient data to decode all 
parts of the geography.",
+          e);
+    }
+    return geo;
+  }
 }
diff --git 
a/common/src/test/java/org/apache/sedona/common/S2Geography/PolygonGeographyTest.java
 
b/common/src/test/java/org/apache/sedona/common/S2Geography/PolygonGeographyTest.java
index 5a1ace7976..a86865a27f 100644
--- 
a/common/src/test/java/org/apache/sedona/common/S2Geography/PolygonGeographyTest.java
+++ 
b/common/src/test/java/org/apache/sedona/common/S2Geography/PolygonGeographyTest.java
@@ -18,7 +18,11 @@
  */
 package org.apache.sedona.common.S2Geography;
 
+import static org.junit.Assert.assertEquals;
+
 import com.google.common.geometry.*;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
@@ -42,4 +46,42 @@ public class PolygonGeographyTest {
 
     TestHelper.assertRoundTrip(geo, new EncodeOptions());
   }
+
+  @Test
+  public void testEncodeMultiPolygon() throws IOException {
+    S2Point pt = S2LatLng.fromDegrees(45, -64).toPoint();
+    S2Point pt_mid = S2LatLng.fromDegrees(45, 0).toPoint();
+    S2Point pt_end = S2LatLng.fromDegrees(0, 0).toPoint();
+    // Build a single polygon and wrap in geography
+    List<S2Point> points = new ArrayList<>();
+    points.add(pt);
+    points.add(pt_mid);
+    points.add(pt_end);
+    points.add(pt);
+    S2Loop polyline = new S2Loop(points);
+    S2Polygon poly = new S2Polygon(polyline);
+
+    S2Point pt2 = S2LatLng.fromDegrees(30, 10).toPoint();
+    S2Point pt_mid2 = S2LatLng.fromDegrees(35, 20).toPoint();
+    S2Point pt_end2 = S2LatLng.fromDegrees(32, 15).toPoint();
+    // Build a single polygon and wrap in geography
+    List<S2Point> points2 = new ArrayList<>();
+    points2.add(pt2);
+    points2.add(pt_mid2);
+    points2.add(pt_end2);
+    points2.add(pt2);
+    S2Loop polyline2 = new S2Loop(points2);
+    S2Polygon poly2 = new S2Polygon(polyline2);
+
+    MultiPolygonGeography geo =
+        new MultiPolygonGeography(Geography.GeographyKind.MULTIPOLYGON, 
List.of(poly2, poly));
+
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    geo.encodeTagged(baos, new EncodeOptions());
+    byte[] data = baos.toByteArray();
+
+    ByteArrayInputStream in = new ByteArrayInputStream(data);
+    MultiPolygonGeography decoded = (MultiPolygonGeography) 
Geography.decodeTagged(in);
+    assertEquals(0, TestHelper.compareTo(geo, decoded));
+  }
 }
diff --git 
a/common/src/test/java/org/apache/sedona/common/S2Geography/TestHelper.java 
b/common/src/test/java/org/apache/sedona/common/S2Geography/TestHelper.java
index bc75e8a27a..dee589ab9d 100644
--- a/common/src/test/java/org/apache/sedona/common/S2Geography/TestHelper.java
+++ b/common/src/test/java/org/apache/sedona/common/S2Geography/TestHelper.java
@@ -220,6 +220,14 @@ public class TestHelper {
         Geography g2 = (Geography) ((GeographyCollection) 
geo2).features.get(i);
         compareTo(g1, g2);
       }
+    } else if (geo1 instanceof MultiPolygonGeography && geo2 instanceof 
MultiPolygonGeography) {
+      if (S2_isEmpty(geo1) && S2_isEmpty(geo2)) return 0;
+      assertEquals(geo1.numShapes(), geo2.numShapes());
+      for (int i = 0; i < geo1.numShapes(); i++) {
+        Geography g1 = (Geography) ((MultiPolygonGeography) 
geo1).features.get(i);
+        Geography g2 = (Geography) ((MultiPolygonGeography) 
geo2).features.get(i);
+        compareTo(g1, g2);
+      }
     }
     return 0;
   }

Reply via email to