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 <[email protected]>
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;
}