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/10/20 00:36:18 UTC
(sedona) branch master updated: [GH-3486] Keep the Z or M dimension of WKT tagged Z, M or ZM even when its values are all NaN (#3487)
This is an automated email from the ASF dual-hosted git repository.
jiayuasu 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 78920fc3c60 [GH-3486] Keep the Z or M dimension of WKT tagged Z, M or ZM even when its values are all NaN (#3487)
78920fc3c60 is described below
commit 78920fc3c60fbbfc799297eb57c4fd2f99a31428
Author: Jia Yu <ji...@apache.org>
AuthorDate: Mon Oct 19 17:36:13 2026 -0700
[GH-3486] Keep the Z or M dimension of WKT tagged Z, M or ZM even when its values are all NaN (#3487)
---
.../org/apache/sedona/common/Constructors.java | 20 ++++------
.../apache/sedona/common/utils/FormatUtils.java | 6 +--
.../org/apache/sedona/common/ConstructorsTest.java | 44 ++++++++++++++++++++++
.../org/apache/sedona/common/PredicatesTest.java | 17 +++++++++
pom.xml | 2 +-
python/tests/geopandas/test_geoseries.py | 9 ++---
6 files changed, 76 insertions(+), 22 deletions(-)
diff --git a/common/src/main/java/org/apache/sedona/common/Constructors.java b/common/src/main/java/org/apache/sedona/common/Constructors.java
index 19e1550ed56..91a8aaf978b 100644
--- a/common/src/main/java/org/apache/sedona/common/Constructors.java
+++ b/common/src/main/java/org/apache/sedona/common/Constructors.java
@@ -29,9 +29,9 @@ import org.apache.sedona.common.geometryObjects.Box3D;
import org.apache.sedona.common.utils.FormatUtils;
import org.apache.sedona.common.utils.GeoHashDecoder;
import org.datasyslab.jts.io.WKBReader;
+import org.datasyslab.jts.io.WKTReader;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.io.ParseException;
-import org.locationtech.jts.io.WKTReader;
import org.locationtech.jts.io.gml2.GMLReader;
import org.locationtech.jts.io.kml.KMLReader;
import org.xml.sax.SAXException;
@@ -44,8 +44,7 @@ public class Constructors {
if (wkt == null) {
return null;
}
- GeometryFactory geometryFactory = new GeometryFactory(new PrecisionModel(), srid);
- return new WKTReader(geometryFactory).read(wkt);
+ return WKTReader.forDeclaredDimensions(srid).read(wkt);
}
public static Geometry geomFromEWKT(String ewkt) throws ParseException {
@@ -65,8 +64,7 @@ public class Constructors {
throw new ParseException("Invalid EWKT string");
}
}
- GeometryFactory geometryFactory = new GeometryFactory(new PrecisionModel(), SRID);
- return new WKTReader(geometryFactory).read(wkt);
+ return WKTReader.forDeclaredDimensions(SRID).read(wkt);
}
public static Geometry geomFromWKB(byte[] wkb) throws ParseException {
@@ -114,32 +112,28 @@ public class Constructors {
if (wkt == null || !wkt.startsWith("MULTIPOINT")) {
return null;
}
- GeometryFactory geometryFactory = new GeometryFactory(new PrecisionModel(), srid);
- return new WKTReader(geometryFactory).read(wkt);
+ return WKTReader.forDeclaredDimensions(srid).read(wkt);
}
public static Geometry mLineFromText(String wkt, int srid) throws ParseException {
if (wkt == null || !wkt.startsWith("MULTILINESTRING")) {
return null;
}
- GeometryFactory geometryFactory = new GeometryFactory(new PrecisionModel(), srid);
- return new WKTReader(geometryFactory).read(wkt);
+ return WKTReader.forDeclaredDimensions(srid).read(wkt);
}
public static Geometry mPolyFromText(String wkt, int srid) throws ParseException {
if (wkt == null || !wkt.startsWith("MULTIPOLYGON")) {
return null;
}
- GeometryFactory geometryFactory = new GeometryFactory(new PrecisionModel(), srid);
- return new WKTReader(geometryFactory).read(wkt);
+ return WKTReader.forDeclaredDimensions(srid).read(wkt);
}
public static Geometry geomCollFromText(String wkt, int srid) throws ParseException {
if (wkt == null || !wkt.startsWith("GEOMETRYCOLLECTION")) {
return null;
}
- GeometryFactory geometryFactory = new GeometryFactory(new PrecisionModel(), srid);
- return new WKTReader(geometryFactory).read(wkt);
+ return WKTReader.forDeclaredDimensions(srid).read(wkt);
}
/**
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 9994b8945a4..45b851e88e5 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
@@ -27,9 +27,9 @@ import org.apache.sedona.common.enums.FileDataSplitter;
import org.apache.sedona.common.enums.GeometryType;
import org.apache.sedona.common.jts2geojson.GeoJSONReader;
import org.datasyslab.jts.io.WKBReader;
+import org.datasyslab.jts.io.WKTReader;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.io.ParseException;
-import org.locationtech.jts.io.WKTReader;
import org.locationtech.jts.operation.valid.IsValidOp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -56,7 +56,7 @@ public class FormatUtils<T extends Geometry> implements Serializable {
protected transient GeometryFactory factory = new GeometryFactory();
protected transient GeoJSONReader geoJSONReader = new GeoJSONReader();
- protected transient WKTReader wktReader = new WKTReader();
+ protected transient WKTReader wktReader = WKTReader.forDeclaredDimensions();
/** Allow mapping of invalid geometries. */
public boolean allowTopologicallyInvalidGeometries;
// For some unknown reasons, the wkb reader cannot be used in transient variable like the wkt
@@ -137,7 +137,7 @@ public class FormatUtils<T extends Geometry> implements Serializable {
throws IOException, ClassNotFoundException {
inputStream.defaultReadObject();
factory = new GeometryFactory();
- wktReader = new WKTReader();
+ wktReader = WKTReader.forDeclaredDimensions();
geoJSONReader = new GeoJSONReader();
}
diff --git a/common/src/test/java/org/apache/sedona/common/ConstructorsTest.java b/common/src/test/java/org/apache/sedona/common/ConstructorsTest.java
index 319ecc1e948..77709bb3ada 100644
--- a/common/src/test/java/org/apache/sedona/common/ConstructorsTest.java
+++ b/common/src/test/java/org/apache/sedona/common/ConstructorsTest.java
@@ -22,15 +22,59 @@ import static org.junit.Assert.*;
import java.util.EnumSet;
import org.apache.sedona.common.geometryObjects.Box2D;
+import org.apache.sedona.common.geometrySerde.CoordinateType;
+import org.apache.sedona.common.geometrySerde.GeometrySerializer;
import org.apache.sedona.common.utils.GeomUtils;
import org.junit.Test;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.io.Ordinate;
import org.locationtech.jts.io.ParseException;
+import org.locationtech.jts.io.WKBReader;
import org.locationtech.jts.io.WKBWriter;
public class ConstructorsTest {
+ @Test
+ public void geomFromWKTKeepsTaggedDimensionsWhoseValuesAreAllNaN() throws ParseException {
+ // WKT, layout it is stored with
+ Object[][] cases = {
+ {"POINT Z (1 2 NaN)", CoordinateType.XYZ},
+ {"POINT Z EMPTY", CoordinateType.XYZ},
+ {"LINESTRING ZM (0 0 NaN NaN, 1 1 NaN NaN)", CoordinateType.XYZM},
+ {"POLYGON Z ((0 0 NaN, 1 0 NaN, 1 1 NaN, 0 0 NaN))", CoordinateType.XYZ},
+ {"MULTIPOINT Z (0 0 NaN, 1 1 NaN)", CoordinateType.XYZ},
+ {"POINT M (1 2 NaN)", CoordinateType.XYM},
+ // Untagged WKT is read as before, including a Z in the old JTS syntax
+ {"POINT (1 2)", CoordinateType.XY},
+ {"POINT (1 2 3)", CoordinateType.XYZ},
+ {"POINT (1 2 NaN)", CoordinateType.XY},
+ {"POINT EMPTY", CoordinateType.XY},
+ };
+ for (Object[] c : cases) {
+ Geometry geometry = Constructors.geomFromWKT((String) c[0], 0);
+ assertEquals((String) c[0], c[1], GeometrySerializer.getCoordinateType(geometry));
+ Geometry stored = GeometrySerializer.deserialize(GeometrySerializer.serialize(geometry));
+ assertEquals((String) c[0], c[1], GeometrySerializer.getCoordinateType(stored));
+ }
+ Geometry ewkt = Constructors.geomFromEWKT("SRID=4326;POINT Z (1 2 NaN)");
+ assertEquals(4326, ewkt.getSRID());
+ assertEquals(CoordinateType.XYZ, GeometrySerializer.getCoordinateType(ewkt));
+ }
+
+ @Test
+ public void geomFromWKBReadsAPointWithOneNaNOrdinate() throws ParseException {
+ Geometry nanX =
+ Constructors.geomFromWKB(
+ WKBReader.hexToBytes("0101000000000000000000F87F000000000000F03F"));
+ assertFalse(nanX.isEmpty());
+ assertTrue(Double.isNaN(nanX.getCoordinate().getX()));
+ assertEquals(1.0, nanX.getCoordinate().getY(), 0.0);
+ // POINT (NaN NaN) is how WKB encodes POINT EMPTY
+ assertTrue(
+ Constructors.geomFromWKB(WKBReader.hexToBytes("0101000000000000000000F87F000000000000F87F"))
+ .isEmpty());
+ }
+
@Test
public void geomFromWKT() throws ParseException {
assertNull(Constructors.geomFromWKT(null, 0));
diff --git a/common/src/test/java/org/apache/sedona/common/PredicatesTest.java b/common/src/test/java/org/apache/sedona/common/PredicatesTest.java
index c465b759262..f7687c5b68b 100644
--- a/common/src/test/java/org/apache/sedona/common/PredicatesTest.java
+++ b/common/src/test/java/org/apache/sedona/common/PredicatesTest.java
@@ -44,6 +44,7 @@ import org.locationtech.jts.geom.impl.CoordinateArraySequence;
import org.locationtech.jts.geom.impl.CoordinateArraySequenceFactory;
import org.locationtech.jts.geom.impl.PackedCoordinateSequenceFactory;
import org.locationtech.jts.io.ParseException;
+import org.locationtech.jts.io.WKBWriter;
public class PredicatesTest extends TestBase {
@@ -596,6 +597,22 @@ public class PredicatesTest extends TestBase {
geomFromEWKT("MULTIPOINT((0 0))"), geomFromEWKT("GEOMETRYCOLLECTION(POINT(0 0))")));
}
+ @Test
+ public void testEqualsIdenticalComparesTaggedAllNaNZFromWKT() throws ParseException {
+ Geometry xy = Constructors.geomFromWKT("POINT (1 2)", 0);
+ Geometry z = Constructors.geomFromWKT("POINT Z (1 2 NaN)", 0);
+ assertFalse(Predicates.equalsIdentical(xy, z));
+ assertFalse(
+ Predicates.equalsIdentical(
+ GeometrySerializer.deserialize(GeometrySerializer.serialize(xy)), z));
+ Geometry zFromWkb = Constructors.geomFromWKB(new WKBWriter(3, true).write(z));
+ assertTrue(Predicates.equalsIdentical(zFromWkb, z));
+ assertFalse(
+ Predicates.equalsIdentical(
+ Constructors.geomFromWKT("POINT EMPTY", 0),
+ Constructors.geomFromWKT("POINT Z EMPTY", 0)));
+ }
+
@Test
public void testEqualsIdenticalMatchesSerializedRoundTrip() throws ParseException {
// WKT parsing stores 2D coordinates with a NaN Z ordinate, while the serializer reads them
diff --git a/pom.xml b/pom.xml
index 61993e67f98..51ed8d59d08 100644
--- a/pom.xml
+++ b/pom.xml
@@ -74,7 +74,7 @@
<hadoop.version>3.2.4</hadoop.version>
<jackson.version>2.13.4</jackson.version>
<jts.version>1.20.0</jts.version>
- <jts.io.patch.version>1.21.0-datasyslab-2</jts.io.patch.version>
+ <jts.io.patch.version>1.21.0-datasyslab-3</jts.io.patch.version>
<jts2geojson.version>0.16.1</jts2geojson.version>
<spatial4j.version>0.8</spatial4j.version>
diff --git a/python/tests/geopandas/test_geoseries.py b/python/tests/geopandas/test_geoseries.py
index a25c8b7268d..b7572eaeefd 100644
--- a/python/tests/geopandas/test_geoseries.py
+++ b/python/tests/geopandas/test_geoseries.py
@@ -8039,11 +8039,10 @@ e": "Feature", "properties": {}, "geometry": {"type": "Point", "coordinates": [3
]
)
- # JTS cannot distinguish empty XY and XYZ sequences. M and ZM sequence
- # metadata is explicit, so the GeometryUDT serializers retain it.
+ # WKT tagged Z, M or ZM keeps that layout, even for empty geometries.
self.check_pd_series_equal(
empty_2d.geom_equals_identical(empty_dimensional, align=False),
- pd.Series([True, True, True, False, False], dtype=bool),
+ pd.Series([False, False, False, False, False], dtype=bool),
)
nan_first_z = GeoSeries.from_wkt(["LINESTRING Z (0 0 NaN, 1 1 2)"])
@@ -8057,10 +8056,10 @@ e": "Feature", "properties": {}, "geometry": {"type": "Point", "coordinates": [3
all_nan_z = GeoSeries.from_wkt(["LINESTRING Z (0 0 NaN, 1 1 NaN)"])
- # An all-NaN Z sequence remains indistinguishable from ordinary XY in JTS.
+ # A tagged Z keeps the XYZ layout even when every Z value is NaN.
self.check_pd_series_equal(
all_nan_z.geom_equals_identical(two_dimensional, align=False),
- pd.Series([True], dtype=bool),
+ pd.Series([False], dtype=bool),
)
mixed_dimension = GeoSeries([MultiPoint([Point(0, 0), Point(1, 1, 2)])])