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 d27bba9b07 [GH-2294] Introduce XYZM support in GeoJSON reader and 
writer (#2295)
d27bba9b07 is described below

commit d27bba9b07319f6b9050242e0b7d010b21e0f300
Author: Jia Yu <[email protected]>
AuthorDate: Sat Aug 16 21:30:16 2025 -0700

    [GH-2294] Introduce XYZM support in GeoJSON reader and writer (#2295)
---
 .../java/org/apache/sedona/common/Functions.java   |   2 +-
 .../sedona/common/jts2geojson/GeoJSONReader.java   | 129 ++++
 .../sedona/common/jts2geojson/GeoJSONWriter.java   | 122 ++++
 .../apache/sedona/common/utils/FormatUtils.java    |   2 +-
 .../common/jts2geojson/GeoJSONReaderTest.java      | 303 ++++++++++
 .../common/jts2geojson/GeoJSONWriterTest.java      | 663 +++++++++++++++++++++
 .../org/apache/sedona/flink/ConstructorTest.java   |   2 +-
 .../java/org/apache/sedona/flink/TestBase.java     |   2 +-
 .../apache/sedona/core/spatialRDD/SpatialRDD.java  |   2 +-
 .../org/apache/sedona/core/utils/testGeoJSON.java  |   2 +-
 10 files changed, 1223 insertions(+), 6 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 83522080d3..0469683fb2 100644
--- a/common/src/main/java/org/apache/sedona/common/Functions.java
+++ b/common/src/main/java/org/apache/sedona/common/Functions.java
@@ -30,6 +30,7 @@ import java.util.stream.Collectors;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.sedona.common.S2Geography.Geography;
 import org.apache.sedona.common.geometryObjects.Circle;
+import org.apache.sedona.common.jts2geojson.GeoJSONWriter;
 import org.apache.sedona.common.sphere.Spheroid;
 import org.apache.sedona.common.subDivide.GeometrySubDivider;
 import org.apache.sedona.common.utils.*;
@@ -70,7 +71,6 @@ import 
org.locationtech.jts.triangulate.DelaunayTriangulationBuilder;
 import 
org.locationtech.jts.triangulate.polygon.ConstrainedDelaunayTriangulator;
 import org.wololo.geojson.Feature;
 import org.wololo.geojson.FeatureCollection;
-import org.wololo.jts2geojson.GeoJSONWriter;
 
 public class Functions {
   private static final double DEFAULT_TOLERANCE = 1e-6;
diff --git 
a/common/src/main/java/org/apache/sedona/common/jts2geojson/GeoJSONReader.java 
b/common/src/main/java/org/apache/sedona/common/jts2geojson/GeoJSONReader.java
new file mode 100644
index 0000000000..92b802baf4
--- /dev/null
+++ 
b/common/src/main/java/org/apache/sedona/common/jts2geojson/GeoJSONReader.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sedona.common.jts2geojson;
+
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.CoordinateXYZM;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LinearRing;
+import org.locationtech.jts.geom.PrecisionModel;
+import org.wololo.geojson.*;
+
+public class GeoJSONReader {
+  static final GeometryFactory FACTORY =
+      new GeometryFactory(new PrecisionModel(PrecisionModel.FLOATING));
+
+  public Geometry read(String json) {
+    return read(json, null);
+  }
+
+  public Geometry read(String json, GeometryFactory geomFactory) {
+    return read(GeoJSONFactory.create(json), geomFactory);
+  }
+
+  public Geometry read(GeoJSON geoJSON) {
+    return read(geoJSON, null);
+  }
+
+  public Geometry read(GeoJSON geoJSON, GeometryFactory geomFactory) {
+    var factory = geomFactory != null ? geomFactory : FACTORY;
+    if (geoJSON instanceof Point) return convert((Point) geoJSON, factory);
+    else if (geoJSON instanceof LineString) return convert((LineString) 
geoJSON, factory);
+    else if (geoJSON instanceof Polygon) return convert((Polygon) geoJSON, 
factory);
+    else if (geoJSON instanceof MultiPoint) return convert((MultiPoint) 
geoJSON, factory);
+    else if (geoJSON instanceof MultiLineString) return 
convert((MultiLineString) geoJSON, factory);
+    else if (geoJSON instanceof MultiPolygon) return convert((MultiPolygon) 
geoJSON, factory);
+    else if (geoJSON instanceof GeometryCollection)
+      return convert((GeometryCollection) geoJSON, factory);
+    else throw new UnsupportedOperationException();
+  }
+
+  Geometry convert(Point point, GeometryFactory factory) {
+    return factory.createPoint(convert(point.getCoordinates()));
+  }
+
+  Geometry convert(MultiPoint multiPoint, GeometryFactory factory) {
+    return 
factory.createMultiPointFromCoords(convert(multiPoint.getCoordinates()));
+  }
+
+  Geometry convert(LineString lineString, GeometryFactory factory) {
+    return factory.createLineString(convert(lineString.getCoordinates()));
+  }
+
+  Geometry convert(MultiLineString multiLineString, GeometryFactory factory) {
+    var size = multiLineString.getCoordinates().length;
+    var lineStrings = new org.locationtech.jts.geom.LineString[size];
+    for (int i = 0; i < size; i++)
+      lineStrings[i] = 
factory.createLineString(convert(multiLineString.getCoordinates()[i]));
+    return factory.createMultiLineString(lineStrings);
+  }
+
+  Geometry convert(Polygon polygon, GeometryFactory factory) {
+    return convertToPolygon(polygon.getCoordinates(), factory);
+  }
+
+  org.locationtech.jts.geom.Polygon convertToPolygon(
+      double[][][] coordinates, GeometryFactory factory) {
+    var shell = factory.createLinearRing(convert(coordinates[0]));
+    if (coordinates.length > 1) {
+      var size = coordinates.length - 1;
+      var holes = new LinearRing[size];
+      for (var i = 0; i < size; i++)
+        holes[i] = factory.createLinearRing(convert(coordinates[i + 1]));
+      return factory.createPolygon(shell, holes);
+    } else {
+      return factory.createPolygon(shell);
+    }
+  }
+
+  Geometry convert(MultiPolygon multiPolygon, GeometryFactory factory) {
+    var size = multiPolygon.getCoordinates().length;
+    var polygons = new org.locationtech.jts.geom.Polygon[size];
+    for (int i = 0; i < size; i++)
+      polygons[i] = convertToPolygon(multiPolygon.getCoordinates()[i], 
factory);
+    return factory.createMultiPolygon(polygons);
+  }
+
+  Geometry convert(GeometryCollection gc, GeometryFactory factory) {
+    var size = gc.getGeometries().length;
+    var geometries = new Geometry[size];
+    for (var i = 0; i < size; i++) geometries[i] = read(gc.getGeometries()[i], 
factory);
+    return factory.createGeometryCollection(geometries);
+  }
+
+  Coordinate convert(double[] c) {
+    if (c.length == 2) {
+      return new Coordinate(c[0], c[1]);
+    } else if (c.length == 3) {
+      return new Coordinate(c[0], c[1], c[2]);
+    } else if (c.length == 4) {
+      // Handle XYZM coordinates (4 values)
+      return new CoordinateXYZM(c[0], c[1], c[2], c[3]);
+    } else {
+      return new Coordinate(c[0], c[1]);
+    }
+  }
+
+  Coordinate[] convert(double[][] ca) {
+    var coordinates = new Coordinate[ca.length];
+    for (int i = 0; i < ca.length; i++) coordinates[i] = convert(ca[i]);
+    return coordinates;
+  }
+}
diff --git 
a/common/src/main/java/org/apache/sedona/common/jts2geojson/GeoJSONWriter.java 
b/common/src/main/java/org/apache/sedona/common/jts2geojson/GeoJSONWriter.java
new file mode 100644
index 0000000000..f9bd189b2f
--- /dev/null
+++ 
b/common/src/main/java/org/apache/sedona/common/jts2geojson/GeoJSONWriter.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sedona.common.jts2geojson;
+
+import java.util.List;
+import org.locationtech.jts.geom.*;
+import org.wololo.geojson.Feature;
+
+public class GeoJSONWriter {
+
+  static final GeoJSONReader reader = new GeoJSONReader();
+
+  public org.wololo.geojson.Geometry write(Geometry geometry) {
+    Class<? extends Geometry> c = geometry.getClass();
+    if (c.equals(Point.class)) return convert((Point) geometry);
+    else if (c.equals(LineString.class)) return convert((LineString) geometry);
+    else if (c.equals(LinearRing.class)) return convert((LinearRing) geometry);
+    else if (c.equals(Polygon.class)) return convert((Polygon) geometry);
+    else if (c.equals(MultiPoint.class)) return convert((MultiPoint) geometry);
+    else if (c.equals(MultiLineString.class)) return convert((MultiLineString) 
geometry);
+    else if (c.equals(MultiPolygon.class)) return convert((MultiPolygon) 
geometry);
+    else if (c.equals(GeometryCollection.class)) return 
convert((GeometryCollection) geometry);
+    else throw new UnsupportedOperationException();
+  }
+
+  public org.wololo.geojson.FeatureCollection write(List<Feature> features) {
+    var size = features.size();
+    var featuresJson = new Feature[size];
+    for (var i = 0; i < size; i++) featuresJson[i] = features.get(i);
+    return new org.wololo.geojson.FeatureCollection(featuresJson);
+  }
+
+  org.wololo.geojson.Point convert(Point point) {
+    return new org.wololo.geojson.Point(convert(point.getCoordinate()));
+  }
+
+  org.wololo.geojson.MultiPoint convert(MultiPoint multiPoint) {
+    return new 
org.wololo.geojson.MultiPoint(convert(multiPoint.getCoordinates()));
+  }
+
+  org.wololo.geojson.LineString convert(LineString lineString) {
+    return new 
org.wololo.geojson.LineString(convert(lineString.getCoordinates()));
+  }
+
+  org.wololo.geojson.LineString convert(LinearRing ringString) {
+    return new 
org.wololo.geojson.LineString(convert(ringString.getCoordinates()));
+  }
+
+  org.wololo.geojson.MultiLineString convert(MultiLineString multiLineString) {
+    var size = multiLineString.getNumGeometries();
+    var lineStrings = new double[size][][];
+    for (int i = 0; i < size; i++)
+      lineStrings[i] = 
convert(multiLineString.getGeometryN(i).getCoordinates());
+    return new org.wololo.geojson.MultiLineString(lineStrings);
+  }
+
+  org.wololo.geojson.Polygon convert(Polygon polygon) {
+    var size = polygon.getNumInteriorRing() + 1;
+    var rings = new double[size][][];
+    rings[0] = convert(polygon.getExteriorRing().getCoordinates());
+    for (int i = 0; i < size - 1; i++)
+      rings[i + 1] = convert(polygon.getInteriorRingN(i).getCoordinates());
+    return new org.wololo.geojson.Polygon(rings);
+  }
+
+  org.wololo.geojson.MultiPolygon convert(MultiPolygon multiPolygon) {
+    var size = multiPolygon.getNumGeometries();
+    var polygons = new double[size][][][];
+    for (int i = 0; i < size; i++)
+      polygons[i] = convert((Polygon) 
multiPolygon.getGeometryN(i)).getCoordinates();
+    return new org.wololo.geojson.MultiPolygon(polygons);
+  }
+
+  org.wololo.geojson.GeometryCollection convert(GeometryCollection gc) {
+    var size = gc.getNumGeometries();
+    var geometries = new org.wololo.geojson.Geometry[size];
+    for (int i = 0; i < size; i++) geometries[i] = write((Geometry) 
gc.getGeometryN(i));
+    return new org.wololo.geojson.GeometryCollection(geometries);
+  }
+
+  double[] convert(Coordinate coordinate) {
+    boolean hasZ = !Double.isNaN(coordinate.getZ());
+    boolean hasM = !Double.isNaN(coordinate.getM());
+
+    if (!hasZ && !hasM) {
+      // XY case - only 2D coordinates
+      return new double[] {coordinate.x, coordinate.y};
+    } else if (hasZ && !hasM) {
+      // XYZ case - 3D coordinates without measure
+      return new double[] {coordinate.x, coordinate.y, coordinate.getZ()};
+    } else if (hasZ && hasM) {
+      // XYZM case - 3D coordinates with measure
+      return new double[] {coordinate.x, coordinate.y, coordinate.getZ(), 
coordinate.getM()};
+    } else {
+      // XYM case - We don't support this directly
+      throw new UnsupportedOperationException(
+          "XYM coordinates are not supported. Please convert to XYZM 
coordinates by adding a Z value.");
+    }
+  }
+
+  double[][] convert(Coordinate[] coordinates) {
+    var array = new double[coordinates.length][];
+    for (int i = 0; i < coordinates.length; i++) array[i] = 
convert(coordinates[i]);
+    return array;
+  }
+}
diff --git 
a/common/src/main/java/org/apache/sedona/common/utils/FormatUtils.java 
b/common/src/main/java/org/apache/sedona/common/utils/FormatUtils.java
index dcbf1cfb2a..e37d05f050 100644
--- a/common/src/main/java/org/apache/sedona/common/utils/FormatUtils.java
+++ b/common/src/main/java/org/apache/sedona/common/utils/FormatUtils.java
@@ -25,6 +25,7 @@ import java.util.*;
 import org.apache.sedona.common.Functions;
 import org.apache.sedona.common.enums.FileDataSplitter;
 import org.apache.sedona.common.enums.GeometryType;
+import org.apache.sedona.common.jts2geojson.GeoJSONReader;
 import org.locationtech.jts.geom.*;
 import org.locationtech.jts.io.ParseException;
 import org.locationtech.jts.io.WKBReader;
@@ -34,7 +35,6 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.wololo.geojson.Feature;
 import org.wololo.geojson.GeoJSONFactory;
-import org.wololo.jts2geojson.GeoJSONReader;
 
 /** This format mapper is isolated on purpose for the sake of sharing across 
Spark and Flink */
 public class FormatUtils<T extends Geometry> implements Serializable {
diff --git 
a/common/src/test/java/org/apache/sedona/common/jts2geojson/GeoJSONReaderTest.java
 
b/common/src/test/java/org/apache/sedona/common/jts2geojson/GeoJSONReaderTest.java
new file mode 100644
index 0000000000..6b6ee05ab5
--- /dev/null
+++ 
b/common/src/test/java/org/apache/sedona/common/jts2geojson/GeoJSONReaderTest.java
@@ -0,0 +1,303 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sedona.common.jts2geojson;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.locationtech.jts.geom.CoordinateXYZM;
+import org.locationtech.jts.geom.Geometry;
+import org.wololo.geojson.Point;
+
+public class GeoJSONReaderTest {
+
+  @Test
+  public void testPointConversion() {
+    org.apache.sedona.common.jts2geojson.GeoJSONReader reader =
+        new org.apache.sedona.common.jts2geojson.GeoJSONReader();
+    double[] coords = new double[] {1.0, 2.0};
+    Point point = new Point(coords);
+    Geometry geometry = reader.read(point);
+    assertEquals(1.0, geometry.getCoordinate().x, 0.0);
+    assertEquals(2.0, geometry.getCoordinate().y, 0.0);
+  }
+
+  @Test
+  public void testMultiPointConversion() {
+    org.apache.sedona.common.jts2geojson.GeoJSONReader reader =
+        new org.apache.sedona.common.jts2geojson.GeoJSONReader();
+    double[][] coords = new double[][] {{1.0, 2.0}, {3.0, 4.0}};
+    org.wololo.geojson.MultiPoint multiPoint = new 
org.wololo.geojson.MultiPoint(coords);
+    Geometry geometry = reader.read(multiPoint);
+    assertEquals(1.0, geometry.getCoordinates()[0].x, 0.0);
+    assertEquals(2.0, geometry.getCoordinates()[0].y, 0.0);
+    assertEquals(3.0, geometry.getCoordinates()[1].x, 0.0);
+    assertEquals(4.0, geometry.getCoordinates()[1].y, 0.0);
+  }
+
+  @Test
+  public void testPointWithZConversion() {
+    org.apache.sedona.common.jts2geojson.GeoJSONReader reader =
+        new org.apache.sedona.common.jts2geojson.GeoJSONReader();
+    double[] coords = new double[] {1.0, 2.0, 3.0};
+    Point point = new Point(coords);
+    Geometry geometry = reader.read(point);
+    assertEquals(1.0, geometry.getCoordinate().x, 0.0);
+    assertEquals(2.0, geometry.getCoordinate().y, 0.0);
+    assertEquals(3.0, geometry.getCoordinate().getZ(), 0.0);
+  }
+
+  @Test
+  public void testMultiPointWithZConversion() {
+    org.apache.sedona.common.jts2geojson.GeoJSONReader reader =
+        new org.apache.sedona.common.jts2geojson.GeoJSONReader();
+    double[][] coords = new double[][] {{1.0, 2.0, 3.0}, {4.0, 5.0, 6.0}};
+    org.wololo.geojson.MultiPoint multiPoint = new 
org.wololo.geojson.MultiPoint(coords);
+    Geometry geometry = reader.read(multiPoint);
+    assertEquals(1.0, geometry.getCoordinates()[0].x, 0.0);
+    assertEquals(2.0, geometry.getCoordinates()[0].y, 0.0);
+    assertEquals(3.0, geometry.getCoordinates()[0].getZ(), 0.0);
+    assertEquals(4.0, geometry.getCoordinates()[1].x, 0.0);
+    assertEquals(5.0, geometry.getCoordinates()[1].y, 0.0);
+    assertEquals(6.0, geometry.getCoordinates()[1].getZ(), 0.0);
+  }
+
+  @Test
+  public void testPointWithXYZMConversion() {
+    org.apache.sedona.common.jts2geojson.GeoJSONReader reader =
+        new org.apache.sedona.common.jts2geojson.GeoJSONReader();
+    double[] coords = new double[] {1.0, 2.0, 3.0, 4.0};
+    Point point = new Point(coords);
+    Geometry geometry = reader.read(point);
+
+    assertEquals(1.0, geometry.getCoordinate().x, 0.0);
+    assertEquals(2.0, geometry.getCoordinate().y, 0.0);
+    assertEquals(3.0, geometry.getCoordinate().getZ(), 0.0);
+    assertEquals(4.0, geometry.getCoordinate().getM(), 0.0);
+
+    // Verify the coordinate is an XYZM coordinate
+    assertTrue(geometry.getCoordinate() instanceof CoordinateXYZM);
+  }
+
+  @Test
+  public void testMultiPointWithXYZMConversion() {
+    org.apache.sedona.common.jts2geojson.GeoJSONReader reader =
+        new org.apache.sedona.common.jts2geojson.GeoJSONReader();
+    double[][] coords = new double[][] {{1.0, 2.0, 3.0, 4.0}, {5.0, 6.0, 7.0, 
8.0}};
+    org.wololo.geojson.MultiPoint multiPoint = new 
org.wololo.geojson.MultiPoint(coords);
+    Geometry geometry = reader.read(multiPoint);
+
+    assertEquals(1.0, geometry.getCoordinates()[0].x, 0.0);
+    assertEquals(2.0, geometry.getCoordinates()[0].y, 0.0);
+    assertEquals(3.0, geometry.getCoordinates()[0].getZ(), 0.0);
+    assertEquals(4.0, geometry.getCoordinates()[0].getM(), 0.0);
+
+    assertEquals(5.0, geometry.getCoordinates()[1].x, 0.0);
+    assertEquals(6.0, geometry.getCoordinates()[1].y, 0.0);
+    assertEquals(7.0, geometry.getCoordinates()[1].getZ(), 0.0);
+    assertEquals(8.0, geometry.getCoordinates()[1].getM(), 0.0);
+
+    // Verify the coordinates are XYZM coordinates
+    assertTrue(geometry.getCoordinates()[0] instanceof CoordinateXYZM);
+    assertTrue(geometry.getCoordinates()[1] instanceof CoordinateXYZM);
+  }
+
+  @Test
+  public void testLineStringConversion() {
+    org.apache.sedona.common.jts2geojson.GeoJSONReader reader =
+        new org.apache.sedona.common.jts2geojson.GeoJSONReader();
+    double[][] coords = new double[][] {{1.0, 2.0}, {3.0, 4.0}, {5.0, 6.0}};
+    org.wololo.geojson.LineString lineString = new 
org.wololo.geojson.LineString(coords);
+    Geometry geometry = reader.read(lineString);
+
+    assertEquals("LineString", geometry.getGeometryType());
+    assertEquals(3, geometry.getCoordinates().length);
+    assertEquals(1.0, geometry.getCoordinates()[0].x, 0.0);
+    assertEquals(2.0, geometry.getCoordinates()[0].y, 0.0);
+    assertEquals(3.0, geometry.getCoordinates()[1].x, 0.0);
+    assertEquals(4.0, geometry.getCoordinates()[1].y, 0.0);
+    assertEquals(5.0, geometry.getCoordinates()[2].x, 0.0);
+    assertEquals(6.0, geometry.getCoordinates()[2].y, 0.0);
+  }
+
+  @Test
+  public void testLineStringWithZConversion() {
+    org.apache.sedona.common.jts2geojson.GeoJSONReader reader =
+        new org.apache.sedona.common.jts2geojson.GeoJSONReader();
+    double[][] coords = new double[][] {{1.0, 2.0, 3.0}, {4.0, 5.0, 6.0}, 
{7.0, 8.0, 9.0}};
+    org.wololo.geojson.LineString lineString = new 
org.wololo.geojson.LineString(coords);
+    Geometry geometry = reader.read(lineString);
+
+    assertEquals("LineString", geometry.getGeometryType());
+    assertEquals(3, geometry.getCoordinates().length);
+    assertEquals(1.0, geometry.getCoordinates()[0].x, 0.0);
+    assertEquals(2.0, geometry.getCoordinates()[0].y, 0.0);
+    assertEquals(3.0, geometry.getCoordinates()[0].getZ(), 0.0);
+    assertEquals(4.0, geometry.getCoordinates()[1].x, 0.0);
+    assertEquals(5.0, geometry.getCoordinates()[1].y, 0.0);
+    assertEquals(6.0, geometry.getCoordinates()[1].getZ(), 0.0);
+    assertEquals(7.0, geometry.getCoordinates()[2].x, 0.0);
+    assertEquals(8.0, geometry.getCoordinates()[2].y, 0.0);
+    assertEquals(9.0, geometry.getCoordinates()[2].getZ(), 0.0);
+  }
+
+  @Test
+  public void testPolygonConversion() {
+    org.apache.sedona.common.jts2geojson.GeoJSONReader reader =
+        new org.apache.sedona.common.jts2geojson.GeoJSONReader();
+    // Polygon with one exterior ring
+    double[][][] coords =
+        new double[][][] {{{0.0, 0.0}, {0.0, 10.0}, {10.0, 10.0}, {10.0, 0.0}, 
{0.0, 0.0}}};
+    org.wololo.geojson.Polygon polygon = new 
org.wololo.geojson.Polygon(coords);
+    Geometry geometry = reader.read(polygon);
+
+    assertEquals("Polygon", geometry.getGeometryType());
+    assertEquals(5, geometry.getCoordinates().length);
+    assertEquals(0.0, geometry.getCoordinates()[0].x, 0.0);
+    assertEquals(0.0, geometry.getCoordinates()[0].y, 0.0);
+    assertEquals(0.0, geometry.getCoordinates()[1].x, 0.0);
+    assertEquals(10.0, geometry.getCoordinates()[1].y, 0.0);
+  }
+
+  @Test
+  public void testPolygonWithHoleConversion() {
+    org.apache.sedona.common.jts2geojson.GeoJSONReader reader =
+        new org.apache.sedona.common.jts2geojson.GeoJSONReader();
+    // Polygon with exterior ring and one hole
+    double[][][] coords =
+        new double[][][] {
+          {{0.0, 0.0}, {0.0, 10.0}, {10.0, 10.0}, {10.0, 0.0}, {0.0, 0.0}},
+          {{2.0, 2.0}, {2.0, 8.0}, {8.0, 8.0}, {8.0, 2.0}, {2.0, 2.0}}
+        };
+    org.wololo.geojson.Polygon polygon = new 
org.wololo.geojson.Polygon(coords);
+    Geometry geometry = reader.read(polygon);
+
+    assertEquals("Polygon", geometry.getGeometryType());
+    assertEquals(1, geometry.getNumGeometries());
+    assertEquals(1, ((org.locationtech.jts.geom.Polygon) 
geometry).getNumInteriorRing());
+
+    // Check exterior ring
+    assertEquals(
+        5,
+        ((org.locationtech.jts.geom.Polygon) 
geometry).getExteriorRing().getCoordinates().length);
+
+    // Check interior ring
+    assertEquals(
+        5,
+        ((org.locationtech.jts.geom.Polygon) 
geometry).getInteriorRingN(0).getCoordinates().length);
+    assertEquals(
+        2.0,
+        ((org.locationtech.jts.geom.Polygon) 
geometry).getInteriorRingN(0).getCoordinates()[0].x,
+        0.0);
+    assertEquals(
+        2.0,
+        ((org.locationtech.jts.geom.Polygon) 
geometry).getInteriorRingN(0).getCoordinates()[0].y,
+        0.0);
+  }
+
+  @Test
+  public void testMultiLineStringConversion() {
+    org.apache.sedona.common.jts2geojson.GeoJSONReader reader =
+        new org.apache.sedona.common.jts2geojson.GeoJSONReader();
+    double[][][] coords =
+        new double[][][] {
+          {{1.0, 2.0}, {3.0, 4.0}},
+          {{5.0, 6.0}, {7.0, 8.0}}
+        };
+    org.wololo.geojson.MultiLineString multiLineString =
+        new org.wololo.geojson.MultiLineString(coords);
+    Geometry geometry = reader.read(multiLineString);
+
+    assertEquals("MultiLineString", geometry.getGeometryType());
+    assertEquals(2, geometry.getNumGeometries());
+
+    // Check first linestring
+    assertEquals(2, geometry.getGeometryN(0).getCoordinates().length);
+    assertEquals(1.0, geometry.getGeometryN(0).getCoordinates()[0].x, 0.0);
+    assertEquals(2.0, geometry.getGeometryN(0).getCoordinates()[0].y, 0.0);
+
+    // Check second linestring
+    assertEquals(2, geometry.getGeometryN(1).getCoordinates().length);
+    assertEquals(5.0, geometry.getGeometryN(1).getCoordinates()[0].x, 0.0);
+    assertEquals(6.0, geometry.getGeometryN(1).getCoordinates()[0].y, 0.0);
+  }
+
+  @Test
+  public void testMultiPolygonConversion() {
+    org.apache.sedona.common.jts2geojson.GeoJSONReader reader =
+        new org.apache.sedona.common.jts2geojson.GeoJSONReader();
+    double[][][][] coords =
+        new double[][][][] {
+          // First polygon
+          {{{0.0, 0.0}, {0.0, 5.0}, {5.0, 5.0}, {5.0, 0.0}, {0.0, 0.0}}},
+          // Second polygon
+          {{{10.0, 10.0}, {10.0, 15.0}, {15.0, 15.0}, {15.0, 10.0}, {10.0, 
10.0}}}
+        };
+    org.wololo.geojson.MultiPolygon multiPolygon = new 
org.wololo.geojson.MultiPolygon(coords);
+    Geometry geometry = reader.read(multiPolygon);
+
+    assertEquals("MultiPolygon", geometry.getGeometryType());
+    assertEquals(2, geometry.getNumGeometries());
+
+    // Check first polygon
+    assertEquals(5, geometry.getGeometryN(0).getCoordinates().length);
+    assertEquals(0.0, geometry.getGeometryN(0).getCoordinates()[0].x, 0.0);
+    assertEquals(0.0, geometry.getGeometryN(0).getCoordinates()[0].y, 0.0);
+
+    // Check second polygon
+    assertEquals(5, geometry.getGeometryN(1).getCoordinates().length);
+    assertEquals(10.0, geometry.getGeometryN(1).getCoordinates()[0].x, 0.0);
+    assertEquals(10.0, geometry.getGeometryN(1).getCoordinates()[0].y, 0.0);
+  }
+
+  @Test
+  public void testGeometryCollectionConversion() {
+    org.apache.sedona.common.jts2geojson.GeoJSONReader reader = new 
GeoJSONReader();
+
+    // Create a Point
+    double[] pointCoords = new double[] {1.0, 2.0};
+    org.wololo.geojson.Point point = new org.wololo.geojson.Point(pointCoords);
+
+    // Create a LineString
+    double[][] lineCoords = new double[][] {{3.0, 4.0}, {5.0, 6.0}};
+    org.wololo.geojson.LineString lineString = new 
org.wololo.geojson.LineString(lineCoords);
+
+    // Create a GeometryCollection with these geometries
+    org.wololo.geojson.Geometry[] geometries =
+        new org.wololo.geojson.Geometry[] {point, lineString};
+    org.wololo.geojson.GeometryCollection geometryCollection =
+        new org.wololo.geojson.GeometryCollection(geometries);
+
+    Geometry geometry = reader.read(geometryCollection);
+
+    assertEquals("GeometryCollection", geometry.getGeometryType());
+    assertEquals(2, geometry.getNumGeometries());
+
+    // Check first geometry (point)
+    assertEquals("Point", geometry.getGeometryN(0).getGeometryType());
+    assertEquals(1.0, geometry.getGeometryN(0).getCoordinate().x, 0.0);
+    assertEquals(2.0, geometry.getGeometryN(0).getCoordinate().y, 0.0);
+
+    // Check second geometry (linestring)
+    assertEquals("LineString", geometry.getGeometryN(1).getGeometryType());
+    assertEquals(3.0, geometry.getGeometryN(1).getCoordinates()[0].x, 0.0);
+    assertEquals(4.0, geometry.getGeometryN(1).getCoordinates()[0].y, 0.0);
+  }
+}
diff --git 
a/common/src/test/java/org/apache/sedona/common/jts2geojson/GeoJSONWriterTest.java
 
b/common/src/test/java/org/apache/sedona/common/jts2geojson/GeoJSONWriterTest.java
new file mode 100644
index 0000000000..3d9f2ee995
--- /dev/null
+++ 
b/common/src/test/java/org/apache/sedona/common/jts2geojson/GeoJSONWriterTest.java
@@ -0,0 +1,663 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sedona.common.jts2geojson;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.CoordinateXYM;
+import org.locationtech.jts.geom.CoordinateXYZM;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.LinearRing;
+import org.locationtech.jts.geom.MultiLineString;
+import org.locationtech.jts.geom.MultiPoint;
+import org.locationtech.jts.geom.MultiPolygon;
+import org.locationtech.jts.geom.Point;
+import org.locationtech.jts.geom.Polygon;
+import org.wololo.geojson.Feature;
+import org.wololo.geojson.FeatureCollection;
+
+public class GeoJSONWriterTest {
+
+  @Test
+  public void testPointConversion() {
+    // Setup
+    org.apache.sedona.common.jts2geojson.GeoJSONReader reader =
+        new org.apache.sedona.common.jts2geojson.GeoJSONReader();
+    org.apache.sedona.common.jts2geojson.GeoJSONWriter writer =
+        new org.apache.sedona.common.jts2geojson.GeoJSONWriter();
+    GeometryFactory factory = new GeometryFactory();
+
+    // Test data
+    Point point = factory.createPoint(new Coordinate(1, 1));
+    String expected = "{\"type\":\"Point\",\"coordinates\":[1.0,1.0]}";
+
+    // Test
+    org.wololo.geojson.Geometry json = writer.write(point);
+    assertEquals("Point should be correctly converted to GeoJSON", expected, 
json.toString());
+
+    org.locationtech.jts.geom.Geometry geometry = reader.read(json);
+    assertEquals(
+        "GeoJSON should be correctly converted back to geometry",
+        "POINT (1 1)",
+        geometry.toString());
+  }
+
+  @Test
+  public void testLineStringConversion() {
+    // Setup
+    org.apache.sedona.common.jts2geojson.GeoJSONReader reader =
+        new org.apache.sedona.common.jts2geojson.GeoJSONReader();
+    org.apache.sedona.common.jts2geojson.GeoJSONWriter writer =
+        new org.apache.sedona.common.jts2geojson.GeoJSONWriter();
+    GeometryFactory factory = new GeometryFactory();
+
+    // Test data
+    Coordinate[] coordinates =
+        new Coordinate[] {
+          new Coordinate(1, 1), new Coordinate(1, 2), new Coordinate(2, 2), 
new Coordinate(1, 1)
+        };
+    LineString lineString = factory.createLineString(coordinates);
+    String expected =
+        
"{\"type\":\"LineString\",\"coordinates\":[[1.0,1.0],[1.0,2.0],[2.0,2.0],[1.0,1.0]]}";
+
+    // Test
+    org.wololo.geojson.Geometry json = writer.write(lineString);
+    assertEquals("LineString should be correctly converted to GeoJSON", 
expected, json.toString());
+  }
+
+  @Test
+  public void testLineStringWithIdConversion() {
+    // Setup
+    org.apache.sedona.common.jts2geojson.GeoJSONWriter writer =
+        new org.apache.sedona.common.jts2geojson.GeoJSONWriter();
+    GeometryFactory factory = new GeometryFactory();
+
+    // Test data
+    Coordinate[] coordinates =
+        new Coordinate[] {
+          new Coordinate(1, 1), new Coordinate(1, 2), new Coordinate(2, 2), 
new Coordinate(1, 1)
+        };
+    LineString lineString = factory.createLineString(coordinates);
+    String expected =
+        
"{\"type\":\"LineString\",\"coordinates\":[[1.0,1.0],[1.0,2.0],[2.0,2.0],[1.0,1.0]]}";
+
+    // Test
+    org.wololo.geojson.Geometry json = writer.write(lineString);
+    assertEquals(
+        "LineString with ID should be correctly converted to GeoJSON", 
expected, json.toString());
+  }
+
+  @Test
+  public void testLinearRingConversion() {
+    // Setup
+    org.apache.sedona.common.jts2geojson.GeoJSONWriter writer =
+        new org.apache.sedona.common.jts2geojson.GeoJSONWriter();
+    GeometryFactory factory = new GeometryFactory();
+
+    // Test data
+    Coordinate[] coordinates =
+        new Coordinate[] {
+          new Coordinate(1, 1), new Coordinate(1, 2), new Coordinate(2, 2), 
new Coordinate(1, 1)
+        };
+    LinearRing ring = factory.createLinearRing(coordinates);
+    String expected =
+        
"{\"type\":\"LineString\",\"coordinates\":[[1.0,1.0],[1.0,2.0],[2.0,2.0],[1.0,1.0]]}";
+
+    // Test
+    org.wololo.geojson.Geometry json = writer.write(ring);
+    assertEquals("LinearRing should be correctly converted to GeoJSON", 
expected, json.toString());
+  }
+
+  @Test
+  public void testPolygonConversion() {
+    // Setup
+    org.apache.sedona.common.jts2geojson.GeoJSONWriter writer =
+        new org.apache.sedona.common.jts2geojson.GeoJSONWriter();
+    GeometryFactory factory = new GeometryFactory();
+
+    // Test data
+    Coordinate[] coordinates =
+        new Coordinate[] {
+          new Coordinate(1, 1), new Coordinate(1, 2), new Coordinate(2, 2), 
new Coordinate(1, 1)
+        };
+    Polygon polygon = factory.createPolygon(coordinates);
+    String expected =
+        
"{\"type\":\"Polygon\",\"coordinates\":[[[1.0,1.0],[1.0,2.0],[2.0,2.0],[1.0,1.0]]]}";
+
+    // Test
+    org.wololo.geojson.Geometry json = writer.write(polygon);
+    assertEquals("Polygon should be correctly converted to GeoJSON", expected, 
json.toString());
+  }
+
+  @Test
+  public void testMultiPointConversion() {
+    // Setup
+    org.apache.sedona.common.jts2geojson.GeoJSONWriter writer =
+        new org.apache.sedona.common.jts2geojson.GeoJSONWriter();
+    GeometryFactory factory = new GeometryFactory();
+
+    // Test data
+    Coordinate[] coordinates =
+        new Coordinate[] {
+          new Coordinate(1, 1), new Coordinate(1, 2), new Coordinate(2, 2), 
new Coordinate(1, 1)
+        };
+    LineString lineString = factory.createLineString(coordinates);
+    MultiPoint multiPoint = 
factory.createMultiPointFromCoords(lineString.getCoordinates());
+    String expected =
+        
"{\"type\":\"MultiPoint\",\"coordinates\":[[1.0,1.0],[1.0,2.0],[2.0,2.0],[1.0,1.0]]}";
+
+    // Test
+    org.wololo.geojson.Geometry json = writer.write(multiPoint);
+    assertEquals("MultiPoint should be correctly converted to GeoJSON", 
expected, json.toString());
+  }
+
+  @Test
+  public void testMultiLineStringConversion() {
+    // Setup
+    org.apache.sedona.common.jts2geojson.GeoJSONWriter writer =
+        new org.apache.sedona.common.jts2geojson.GeoJSONWriter();
+    GeometryFactory factory = new GeometryFactory();
+
+    // Test data
+    Coordinate[] coordinates =
+        new Coordinate[] {
+          new Coordinate(1, 1), new Coordinate(1, 2), new Coordinate(2, 2), 
new Coordinate(1, 1)
+        };
+    LineString lineString = factory.createLineString(coordinates);
+    MultiLineString multiLineString =
+        factory.createMultiLineString(new LineString[] {lineString, 
lineString});
+    String expected =
+        
"{\"type\":\"MultiLineString\",\"coordinates\":[[[1.0,1.0],[1.0,2.0],[2.0,2.0],[1.0,1.0]],[[1.0,1.0],[1.0,2.0],[2.0,2.0],[1.0,1.0]]]}";
+
+    // Test
+    org.wololo.geojson.Geometry json = writer.write(multiLineString);
+    assertEquals(
+        "MultiLineString should be correctly converted to GeoJSON", expected, 
json.toString());
+  }
+
+  @Test
+  public void testMultiPolygonConversion() {
+    // Setup
+    org.apache.sedona.common.jts2geojson.GeoJSONWriter writer =
+        new org.apache.sedona.common.jts2geojson.GeoJSONWriter();
+    GeometryFactory factory = new GeometryFactory();
+
+    // Test data
+    Coordinate[] coordinates =
+        new Coordinate[] {
+          new Coordinate(1, 1), new Coordinate(1, 2), new Coordinate(2, 2), 
new Coordinate(1, 1)
+        };
+    LineString lineString = factory.createLineString(coordinates);
+    Polygon polygon = factory.createPolygon(lineString.getCoordinates());
+    MultiPolygon multiPolygon = factory.createMultiPolygon(new Polygon[] 
{polygon, polygon});
+    String expected =
+        
"{\"type\":\"MultiPolygon\",\"coordinates\":[[[[1.0,1.0],[1.0,2.0],[2.0,2.0],[1.0,1.0]]],[[[1.0,1.0],[1.0,2.0],[2.0,2.0],[1.0,1.0]]]]}";
+
+    // Test
+    org.wololo.geojson.Geometry json = writer.write(multiPolygon);
+    assertEquals(
+        "MultiPolygon should be correctly converted to GeoJSON", expected, 
json.toString());
+  }
+
+  @Test
+  public void testFeatureCollectionConversion() {
+    // Setup
+    org.apache.sedona.common.jts2geojson.GeoJSONWriter writer =
+        new org.apache.sedona.common.jts2geojson.GeoJSONWriter();
+    GeometryFactory factory = new GeometryFactory();
+
+    // Test data
+    Point point = factory.createPoint(new Coordinate(1, 1));
+    org.wololo.geojson.Geometry json = writer.write(point);
+    Feature feature1 = new Feature(json, null);
+    Feature feature2 = new Feature(json, null);
+    FeatureCollection featureCollection = new FeatureCollection(new Feature[] 
{feature1, feature2});
+    String expected =
+        
"{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[1.0,1.0]},\"properties\":null},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[1.0,1.0]},\"properties\":null}]}";
+
+    // Test
+    assertEquals(
+        "FeatureCollection should be correctly converted to GeoJSON",
+        expected,
+        featureCollection.toString());
+  }
+
+  // 3D Tests
+
+  @Test
+  public void testPointWithZConversion() {
+    // Setup
+    org.apache.sedona.common.jts2geojson.GeoJSONReader reader = new 
GeoJSONReader();
+    org.apache.sedona.common.jts2geojson.GeoJSONWriter writer =
+        new org.apache.sedona.common.jts2geojson.GeoJSONWriter();
+    GeometryFactory factory = new GeometryFactory();
+
+    // Test data
+    Point point = factory.createPoint(new Coordinate(1, 1, 1));
+    String expected = "{\"type\":\"Point\",\"coordinates\":[1.0,1.0,1.0]}";
+
+    // Test
+    org.wololo.geojson.Geometry json = writer.write(point);
+    assertEquals("3D Point should be correctly converted to GeoJSON", 
expected, json.toString());
+
+    org.locationtech.jts.geom.Geometry geometry = reader.read(json);
+    assertEquals(
+        "GeoJSON should be correctly converted back to geometry",
+        "POINT (1 1)",
+        geometry.toString());
+  }
+
+  @Test
+  public void testLineStringWithZConversion() {
+    // Setup
+    org.apache.sedona.common.jts2geojson.GeoJSONWriter writer =
+        new org.apache.sedona.common.jts2geojson.GeoJSONWriter();
+    GeometryFactory factory = new GeometryFactory();
+
+    // Test data
+    Coordinate[] coordinates =
+        new Coordinate[] {
+          new Coordinate(1, 1, 1),
+          new Coordinate(1, 2, 1),
+          new Coordinate(2, 2, 2),
+          new Coordinate(1, 1, 1)
+        };
+    LineString lineString = factory.createLineString(coordinates);
+    String expected =
+        
"{\"type\":\"LineString\",\"coordinates\":[[1.0,1.0,1.0],[1.0,2.0,1.0],[2.0,2.0,2.0],[1.0,1.0,1.0]]}";
+
+    // Test
+    org.wololo.geojson.Geometry json = writer.write(lineString);
+    assertEquals(
+        "3D LineString should be correctly converted to GeoJSON", expected, 
json.toString());
+  }
+
+  @Test
+  public void testPolygonWithZConversion() {
+    // Setup
+    org.apache.sedona.common.jts2geojson.GeoJSONWriter writer =
+        new org.apache.sedona.common.jts2geojson.GeoJSONWriter();
+    GeometryFactory factory = new GeometryFactory();
+
+    // Test data
+    Coordinate[] coordinates =
+        new Coordinate[] {
+          new Coordinate(1, 1, 1),
+          new Coordinate(1, 2, 1),
+          new Coordinate(2, 2, 2),
+          new Coordinate(1, 1, 1)
+        };
+    LineString lineString = factory.createLineString(coordinates);
+    Polygon polygon = factory.createPolygon(lineString.getCoordinates());
+    String expected =
+        
"{\"type\":\"Polygon\",\"coordinates\":[[[1.0,1.0,1.0],[1.0,2.0,1.0],[2.0,2.0,2.0],[1.0,1.0,1.0]]]}";
+
+    // Test
+    org.wololo.geojson.Geometry json = writer.write(polygon);
+    assertEquals("3D Polygon should be correctly converted to GeoJSON", 
expected, json.toString());
+  }
+
+  @Test
+  public void testMultiPointWithZConversion() {
+    // Setup
+    org.apache.sedona.common.jts2geojson.GeoJSONWriter writer =
+        new org.apache.sedona.common.jts2geojson.GeoJSONWriter();
+    GeometryFactory factory = new GeometryFactory();
+
+    // Test data
+    Coordinate[] coordinates =
+        new Coordinate[] {
+          new Coordinate(1, 1, 1),
+          new Coordinate(1, 2, 1),
+          new Coordinate(2, 2, 2),
+          new Coordinate(1, 1, 1)
+        };
+    LineString lineString = factory.createLineString(coordinates);
+    MultiPoint multiPoint = 
factory.createMultiPointFromCoords(lineString.getCoordinates());
+    String expected =
+        
"{\"type\":\"MultiPoint\",\"coordinates\":[[1.0,1.0,1.0],[1.0,2.0,1.0],[2.0,2.0,2.0],[1.0,1.0,1.0]]}";
+
+    // Test
+    org.wololo.geojson.Geometry json = writer.write(multiPoint);
+    assertEquals(
+        "3D MultiPoint should be correctly converted to GeoJSON", expected, 
json.toString());
+  }
+
+  @Test
+  public void testMultiLineStringWithZConversion() {
+    // Setup
+    org.apache.sedona.common.jts2geojson.GeoJSONWriter writer =
+        new org.apache.sedona.common.jts2geojson.GeoJSONWriter();
+    GeometryFactory factory = new GeometryFactory();
+
+    // Test data
+    Coordinate[] coordinates =
+        new Coordinate[] {
+          new Coordinate(1, 1, 1),
+          new Coordinate(1, 2, 1),
+          new Coordinate(2, 2, 2),
+          new Coordinate(1, 1, 1)
+        };
+    LineString lineString = factory.createLineString(coordinates);
+    MultiLineString multiLineString =
+        factory.createMultiLineString(new LineString[] {lineString, 
lineString});
+    String expected =
+        
"{\"type\":\"MultiLineString\",\"coordinates\":[[[1.0,1.0,1.0],[1.0,2.0,1.0],[2.0,2.0,2.0],[1.0,1.0,1.0]],[[1.0,1.0,1.0],[1.0,2.0,1.0],[2.0,2.0,2.0],[1.0,1.0,1.0]]]}";
+
+    // Test
+    org.wololo.geojson.Geometry json = writer.write(multiLineString);
+    assertEquals(
+        "3D MultiLineString should be correctly converted to GeoJSON", 
expected, json.toString());
+  }
+
+  @Test
+  public void testMultiPolygonWithZConversion() {
+    // Setup
+    org.apache.sedona.common.jts2geojson.GeoJSONWriter writer =
+        new org.apache.sedona.common.jts2geojson.GeoJSONWriter();
+    GeometryFactory factory = new GeometryFactory();
+
+    // Test data
+    Coordinate[] coordinates =
+        new Coordinate[] {
+          new Coordinate(1, 1, 1),
+          new Coordinate(1, 2, 1),
+          new Coordinate(2, 2, 2),
+          new Coordinate(1, 1, 1)
+        };
+    LineString lineString = factory.createLineString(coordinates);
+    Polygon polygon = factory.createPolygon(lineString.getCoordinates());
+    MultiPolygon multiPolygon = factory.createMultiPolygon(new Polygon[] 
{polygon, polygon});
+    String expected =
+        
"{\"type\":\"MultiPolygon\",\"coordinates\":[[[[1.0,1.0,1.0],[1.0,2.0,1.0],[2.0,2.0,2.0],[1.0,1.0,1.0]]],[[[1.0,1.0,1.0],[1.0,2.0,1.0],[2.0,2.0,2.0],[1.0,1.0,1.0]]]]}";
+
+    // Test
+    org.wololo.geojson.Geometry json = writer.write(multiPolygon);
+    assertEquals(
+        "3D MultiPolygon should be correctly converted to GeoJSON", expected, 
json.toString());
+  }
+
+  @Test
+  public void testFeatureCollectionWithZConversion() {
+    // Setup
+    org.apache.sedona.common.jts2geojson.GeoJSONWriter writer =
+        new org.apache.sedona.common.jts2geojson.GeoJSONWriter();
+    GeometryFactory factory = new GeometryFactory();
+
+    // Test data
+    Point point = factory.createPoint(new Coordinate(1, 1, 1));
+    org.wololo.geojson.Geometry json = writer.write(point);
+    Feature feature1 = new Feature(json, null);
+    Feature feature2 = new Feature(json, null);
+    FeatureCollection featureCollection = new FeatureCollection(new Feature[] 
{feature1, feature2});
+    String expected =
+        
"{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[1.0,1.0,1.0]},\"properties\":null},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[1.0,1.0,1.0]},\"properties\":null}]}";
+
+    // Test
+    assertEquals(
+        "3D FeatureCollection should be correctly converted to GeoJSON",
+        expected,
+        featureCollection.toString());
+  }
+
+  // XYZM Tests
+
+  @Test
+  public void testPointWithZMConversion() {
+    // Setup
+    org.apache.sedona.common.jts2geojson.GeoJSONWriter writer =
+        new org.apache.sedona.common.jts2geojson.GeoJSONWriter();
+    GeometryFactory factory = new GeometryFactory();
+
+    // Test data
+    Point point = factory.createPoint(new CoordinateXYZM(1, 1, 1, 2));
+    String expected = "{\"type\":\"Point\",\"coordinates\":[1.0,1.0,1.0,2.0]}";
+
+    // Test
+    org.wololo.geojson.Geometry json = writer.write(point);
+    assertEquals("XYZM Point should be correctly converted to GeoJSON", 
expected, json.toString());
+  }
+
+  @Test
+  public void testLineStringWithZMConversion() {
+    // Setup
+    org.apache.sedona.common.jts2geojson.GeoJSONWriter writer =
+        new org.apache.sedona.common.jts2geojson.GeoJSONWriter();
+    GeometryFactory factory = new GeometryFactory();
+
+    // Test data
+    Coordinate[] coordinates =
+        new Coordinate[] {
+          new CoordinateXYZM(1, 1, 1, 10),
+          new CoordinateXYZM(1, 2, 1, 20),
+          new CoordinateXYZM(2, 2, 2, 30),
+          new CoordinateXYZM(1, 1, 1, 10)
+        };
+    LineString lineString = factory.createLineString(coordinates);
+    String expected =
+        
"{\"type\":\"LineString\",\"coordinates\":[[1.0,1.0,1.0,10.0],[1.0,2.0,1.0,20.0],[2.0,2.0,2.0,30.0],[1.0,1.0,1.0,10.0]]}";
+
+    // Test
+    org.wololo.geojson.Geometry json = writer.write(lineString);
+    assertEquals(
+        "XYZM LineString should be correctly converted to GeoJSON", expected, 
json.toString());
+  }
+
+  @Test
+  public void testPolygonWithZMConversion() {
+    // Setup
+    org.apache.sedona.common.jts2geojson.GeoJSONWriter writer =
+        new org.apache.sedona.common.jts2geojson.GeoJSONWriter();
+    GeometryFactory factory = new GeometryFactory();
+
+    // Test data
+    Coordinate[] coordinates =
+        new Coordinate[] {
+          new CoordinateXYZM(1, 1, 1, 10),
+          new CoordinateXYZM(1, 2, 1, 20),
+          new CoordinateXYZM(2, 2, 2, 30),
+          new CoordinateXYZM(1, 1, 1, 10)
+        };
+    Polygon polygon = factory.createPolygon(coordinates);
+    String expected =
+        
"{\"type\":\"Polygon\",\"coordinates\":[[[1.0,1.0,1.0,10.0],[1.0,2.0,1.0,20.0],[2.0,2.0,2.0,30.0],[1.0,1.0,1.0,10.0]]]}";
+
+    // Test
+    org.wololo.geojson.Geometry json = writer.write(polygon);
+    assertEquals(
+        "XYZM Polygon should be correctly converted to GeoJSON", expected, 
json.toString());
+  }
+
+  @Test
+  public void testMultiPointWithZMConversion() {
+    // Setup
+    org.apache.sedona.common.jts2geojson.GeoJSONWriter writer =
+        new org.apache.sedona.common.jts2geojson.GeoJSONWriter();
+    GeometryFactory factory = new GeometryFactory();
+
+    // Test data
+    Coordinate[] coordinates =
+        new Coordinate[] {
+          new CoordinateXYZM(1, 1, 1, 10),
+          new CoordinateXYZM(1, 2, 1, 20),
+          new CoordinateXYZM(2, 2, 2, 30)
+        };
+    MultiPoint multiPoint = factory.createMultiPointFromCoords(coordinates);
+    String expected =
+        
"{\"type\":\"MultiPoint\",\"coordinates\":[[1.0,1.0,1.0,10.0],[1.0,2.0,1.0,20.0],[2.0,2.0,2.0,30.0]]}";
+
+    // Test
+    org.wololo.geojson.Geometry json = writer.write(multiPoint);
+    assertEquals(
+        "XYZM MultiPoint should be correctly converted to GeoJSON", expected, 
json.toString());
+  }
+
+  @Test
+  public void testMultiLineStringWithZMConversion() {
+    // Setup
+    org.apache.sedona.common.jts2geojson.GeoJSONWriter writer =
+        new org.apache.sedona.common.jts2geojson.GeoJSONWriter();
+    GeometryFactory factory = new GeometryFactory();
+
+    // Test data
+    Coordinate[] coordinates1 =
+        new Coordinate[] {new CoordinateXYZM(1, 1, 1, 10), new 
CoordinateXYZM(1, 2, 1, 20)};
+    Coordinate[] coordinates2 =
+        new Coordinate[] {new CoordinateXYZM(2, 2, 2, 30), new 
CoordinateXYZM(3, 3, 3, 40)};
+    LineString lineString1 = factory.createLineString(coordinates1);
+    LineString lineString2 = factory.createLineString(coordinates2);
+    MultiLineString multiLineString =
+        factory.createMultiLineString(new LineString[] {lineString1, 
lineString2});
+    String expected =
+        
"{\"type\":\"MultiLineString\",\"coordinates\":[[[1.0,1.0,1.0,10.0],[1.0,2.0,1.0,20.0]],[[2.0,2.0,2.0,30.0],[3.0,3.0,3.0,40.0]]]}";
+
+    // Test
+    org.wololo.geojson.Geometry json = writer.write(multiLineString);
+    assertEquals(
+        "XYZM MultiLineString should be correctly converted to GeoJSON", 
expected, json.toString());
+  }
+
+  @Test
+  public void testMultiPolygonWithZMConversion() {
+    // Setup
+    org.apache.sedona.common.jts2geojson.GeoJSONWriter writer =
+        new org.apache.sedona.common.jts2geojson.GeoJSONWriter();
+    GeometryFactory factory = new GeometryFactory();
+
+    // Test data
+    Coordinate[] coordinates =
+        new Coordinate[] {
+          new CoordinateXYZM(1, 1, 1, 10),
+          new CoordinateXYZM(1, 2, 1, 20),
+          new CoordinateXYZM(2, 2, 2, 30),
+          new CoordinateXYZM(1, 1, 1, 10)
+        };
+    Polygon polygon = factory.createPolygon(coordinates);
+    MultiPolygon multiPolygon = factory.createMultiPolygon(new Polygon[] 
{polygon, polygon});
+    String expected =
+        
"{\"type\":\"MultiPolygon\",\"coordinates\":[[[[1.0,1.0,1.0,10.0],[1.0,2.0,1.0,20.0],[2.0,2.0,2.0,30.0],[1.0,1.0,1.0,10.0]]],[[[1.0,1.0,1.0,10.0],[1.0,2.0,1.0,20.0],[2.0,2.0,2.0,30.0],[1.0,1.0,1.0,10.0]]]]}";
+
+    // Test
+    org.wololo.geojson.Geometry json = writer.write(multiPolygon);
+    assertEquals(
+        "XYZM MultiPolygon should be correctly converted to GeoJSON", 
expected, json.toString());
+  }
+
+  @Test
+  public void testFeatureCollectionWithZMConversion() {
+    // Setup
+    org.apache.sedona.common.jts2geojson.GeoJSONWriter writer =
+        new org.apache.sedona.common.jts2geojson.GeoJSONWriter();
+    GeometryFactory factory = new GeometryFactory();
+
+    // Test data
+    Point point = factory.createPoint(new CoordinateXYZM(1, 1, 1, 10));
+    org.wololo.geojson.Geometry json = writer.write(point);
+    Feature feature1 = new Feature(json, null);
+    Feature feature2 = new Feature(json, null);
+    FeatureCollection featureCollection = new FeatureCollection(new Feature[] 
{feature1, feature2});
+    String expected =
+        
"{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[1.0,1.0,1.0,10.0]},\"properties\":null},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[1.0,1.0,1.0,10.0]},\"properties\":null}]}";
+
+    // Test
+    assertEquals(
+        "XYZM FeatureCollection should be correctly converted to GeoJSON",
+        expected,
+        featureCollection.toString());
+  }
+
+  // XYM Tests - These should fail as XYM is not supported
+
+  @Test
+  public void testPointWithMConversionFails() {
+    // Setup
+    org.apache.sedona.common.jts2geojson.GeoJSONWriter writer =
+        new org.apache.sedona.common.jts2geojson.GeoJSONWriter();
+    GeometryFactory factory = new GeometryFactory();
+
+    // Test data - XYM coordinate
+    Point point = factory.createPoint(new CoordinateXYM(1, 1, 10));
+
+    // Test - should throw UnsupportedOperationException
+    try {
+      writer.write(point);
+      fail("XYM coordinates should not be supported");
+    } catch (UnsupportedOperationException e) {
+      assertEquals(
+          "XYM coordinates are not supported. Please convert to XYZM 
coordinates by adding a Z value.",
+          e.getMessage());
+    }
+  }
+
+  @Test
+  public void testLineStringWithMConversionFails() {
+    // Setup
+    org.apache.sedona.common.jts2geojson.GeoJSONWriter writer =
+        new org.apache.sedona.common.jts2geojson.GeoJSONWriter();
+    GeometryFactory factory = new GeometryFactory();
+
+    // Test data - XYM coordinates
+    Coordinate[] coordinates =
+        new Coordinate[] {
+          new CoordinateXYM(1, 1, 10), new CoordinateXYM(1, 2, 20), new 
CoordinateXYM(2, 2, 30)
+        };
+    LineString lineString = factory.createLineString(coordinates);
+
+    // Test - should throw UnsupportedOperationException
+    try {
+      writer.write(lineString);
+      fail("XYM coordinates should not be supported");
+    } catch (UnsupportedOperationException e) {
+      assertEquals(
+          "XYM coordinates are not supported. Please convert to XYZM 
coordinates by adding a Z value.",
+          e.getMessage());
+    }
+  }
+
+  @Test
+  public void testPolygonWithMConversionFails() {
+    // Setup
+    org.apache.sedona.common.jts2geojson.GeoJSONWriter writer = new 
GeoJSONWriter();
+    GeometryFactory factory = new GeometryFactory();
+
+    // Test data - XYM coordinates
+    Coordinate[] coordinates =
+        new Coordinate[] {
+          new CoordinateXYM(1, 1, 10),
+          new CoordinateXYM(1, 2, 20),
+          new CoordinateXYM(2, 2, 30),
+          new CoordinateXYM(1, 1, 10)
+        };
+    Polygon polygon = factory.createPolygon(coordinates);
+
+    // Test - should throw UnsupportedOperationException
+    try {
+      writer.write(polygon);
+      fail("XYM coordinates should not be supported");
+    } catch (UnsupportedOperationException e) {
+      assertEquals(
+          "XYM coordinates are not supported. Please convert to XYZM 
coordinates by adding a Z value.",
+          e.getMessage());
+    }
+  }
+}
diff --git a/flink/src/test/java/org/apache/sedona/flink/ConstructorTest.java 
b/flink/src/test/java/org/apache/sedona/flink/ConstructorTest.java
index 5f4242b8d2..9732e4ba8e 100644
--- a/flink/src/test/java/org/apache/sedona/flink/ConstructorTest.java
+++ b/flink/src/test/java/org/apache/sedona/flink/ConstructorTest.java
@@ -34,6 +34,7 @@ import org.apache.flink.api.java.typeutils.RowTypeInfo;
 import org.apache.flink.streaming.api.datastream.DataStream;
 import org.apache.flink.table.api.Table;
 import org.apache.flink.types.Row;
+import org.apache.sedona.common.jts2geojson.GeoJSONReader;
 import org.apache.sedona.flink.expressions.Constructors;
 import org.apache.sedona.flink.expressions.Functions;
 import org.junit.BeforeClass;
@@ -43,7 +44,6 @@ import org.locationtech.jts.geom.Geometry;
 import org.locationtech.jts.geom.GeometryFactory;
 import org.locationtech.jts.geom.Point;
 import org.locationtech.jts.geom.Polygon;
-import org.wololo.jts2geojson.GeoJSONReader;
 
 public class ConstructorTest extends TestBase {
 
diff --git a/flink/src/test/java/org/apache/sedona/flink/TestBase.java 
b/flink/src/test/java/org/apache/sedona/flink/TestBase.java
index 4506d798e1..0ba5503f90 100644
--- a/flink/src/test/java/org/apache/sedona/flink/TestBase.java
+++ b/flink/src/test/java/org/apache/sedona/flink/TestBase.java
@@ -38,10 +38,10 @@ import org.apache.flink.table.api.Table;
 import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;
 import org.apache.flink.types.Row;
 import org.apache.flink.util.CloseableIterator;
+import org.apache.sedona.common.jts2geojson.GeoJSONWriter;
 import org.apache.sedona.flink.expressions.Constructors;
 import org.locationtech.jts.geom.*;
 import org.locationtech.jts.io.WKTReader;
-import org.wololo.jts2geojson.GeoJSONWriter;
 
 public class TestBase {
   protected static StreamExecutionEnvironment env;
diff --git 
a/spark/common/src/main/java/org/apache/sedona/core/spatialRDD/SpatialRDD.java 
b/spark/common/src/main/java/org/apache/sedona/core/spatialRDD/SpatialRDD.java
index 8b8f3c96f3..2cfc661a64 100644
--- 
a/spark/common/src/main/java/org/apache/sedona/core/spatialRDD/SpatialRDD.java
+++ 
b/spark/common/src/main/java/org/apache/sedona/core/spatialRDD/SpatialRDD.java
@@ -28,6 +28,7 @@ import java.util.Map;
 import org.apache.commons.lang.NullArgumentException;
 import org.apache.log4j.Logger;
 import org.apache.sedona.common.FunctionsGeoTools;
+import org.apache.sedona.common.jts2geojson.GeoJSONWriter;
 import org.apache.sedona.common.utils.GeomUtils;
 import org.apache.sedona.core.enums.GridType;
 import org.apache.sedona.core.enums.IndexType;
@@ -56,7 +57,6 @@ import org.locationtech.jts.index.strtree.STRtree;
 import org.locationtech.jts.io.WKBWriter;
 import org.locationtech.jts.io.WKTWriter;
 import org.wololo.geojson.Feature;
-import org.wololo.jts2geojson.GeoJSONWriter;
 import scala.Tuple2;
 
 // TODO: Auto-generated Javadoc
diff --git 
a/spark/common/src/test/java/org/apache/sedona/core/utils/testGeoJSON.java 
b/spark/common/src/test/java/org/apache/sedona/core/utils/testGeoJSON.java
index bc4be60830..2d80e7d983 100644
--- a/spark/common/src/test/java/org/apache/sedona/core/utils/testGeoJSON.java
+++ b/spark/common/src/test/java/org/apache/sedona/core/utils/testGeoJSON.java
@@ -22,6 +22,7 @@ import java.util.HashMap;
 import java.util.Map;
 import org.apache.log4j.Level;
 import org.apache.log4j.Logger;
+import org.apache.sedona.common.jts2geojson.GeoJSONWriter;
 import org.apache.spark.SparkConf;
 import org.apache.spark.api.java.JavaSparkContext;
 import org.junit.AfterClass;
@@ -31,7 +32,6 @@ import org.locationtech.jts.geom.Coordinate;
 import org.locationtech.jts.geom.Geometry;
 import org.locationtech.jts.geom.GeometryFactory;
 import org.wololo.geojson.Feature;
-import org.wololo.jts2geojson.GeoJSONWriter;
 
 /**
  * @author Arizona State University DataSystems Lab

Reply via email to