From 460d3851db7aedac12168dc8313f79a5c4ec4daa Mon Sep 17 00:00:00 2001 From: Matthew de Detrich Date: Mon, 3 Aug 2026 11:29:41 +0200 Subject: [PATCH 1/5] Add Geometry.getCoordinateDimension Signed-off-by: Matthew de Detrich --- .../org/locationtech/jts/geom/Geometry.java | 47 +++++++++++ .../geom/GeometryCoordinateDimensionTest.java | 78 +++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 modules/core/src/test/java/org/locationtech/jts/geom/GeometryCoordinateDimensionTest.java diff --git a/modules/core/src/main/java/org/locationtech/jts/geom/Geometry.java b/modules/core/src/main/java/org/locationtech/jts/geom/Geometry.java index 104a885c8e..93fc0818c4 100644 --- a/modules/core/src/main/java/org/locationtech/jts/geom/Geometry.java +++ b/modules/core/src/main/java/org/locationtech/jts/geom/Geometry.java @@ -1736,6 +1736,53 @@ static void checkNotGeometryCollection(Geometry g) { } } + /** + * Computes the coordinate dimension of a Geometry, based on the + * coordinate dimensions of its {@link CoordinateSequence}s + * (i.e. 2 for XY, 3 for XYZ or XYM, or 4 for XYZM). + * For a {@link GeometryCollection} this is the maximum coordinate dimension + * over all of its elements. + * Empty geometries (or empty elements of a collection) are assumed + * to have coordinate dimension 2, and do not affect the result + * unless all elements are empty. + * + * @param g the Geometry to get the coordinate dimension of + * @return the coordinate dimension of the geometry (2, 3 or 4) + */ + public static int getCoordinateDimension(Geometry g) { + if (g instanceof Point) { + return coordinateSequenceDimension(((Point) g).getCoordinateSequence()); + } + if (g instanceof LineString) { + return coordinateSequenceDimension(((LineString) g).getCoordinateSequence()); + } + if (g instanceof Polygon) { + Polygon poly = (Polygon) g; + int dimension = 2; + LinearRing shell = poly.getExteriorRing(); + if (shell != null) { + dimension = coordinateSequenceDimension(shell.getCoordinateSequence()); + } + for (int i = 0; i < poly.getNumInteriorRing(); i++) { + dimension = Math.max(dimension, coordinateSequenceDimension(poly.getInteriorRingN(i).getCoordinateSequence())); + } + return dimension; + } + if (g instanceof GeometryCollection) { + GeometryCollection gc = (GeometryCollection) g; + int dimension = 2; + for (int i = 0; i < gc.getNumGeometries(); i++) { + dimension = Math.max(dimension, getCoordinateDimension(gc.getGeometryN(i))); + } + return dimension; + } + return 2; + } + + private static int coordinateSequenceDimension(CoordinateSequence seq) { + return seq.size() > 0 ? seq.getDimension() : 2; + } + /** * Tests whether this is an instance of a general {@link GeometryCollection}, * rather than a homogeneous subclass. diff --git a/modules/core/src/test/java/org/locationtech/jts/geom/GeometryCoordinateDimensionTest.java b/modules/core/src/test/java/org/locationtech/jts/geom/GeometryCoordinateDimensionTest.java new file mode 100644 index 0000000000..885ed2513c --- /dev/null +++ b/modules/core/src/test/java/org/locationtech/jts/geom/GeometryCoordinateDimensionTest.java @@ -0,0 +1,78 @@ +package org.locationtech.jts.geom; + +import org.locationtech.jts.geom.impl.PackedCoordinateSequenceFactory; +import org.locationtech.jts.io.WKTReader; + +import junit.textui.TestRunner; +import test.jts.GeometryTestCase; + +/** + * Tests for {@link Geometry#getCoordinateDimension(Geometry)}. + */ +public class GeometryCoordinateDimensionTest extends GeometryTestCase { + public static void main(String args[]) { + TestRunner.run(GeometryCoordinateDimensionTest.class); + } + + static GeometryFactory geomFact = new GeometryFactory(PackedCoordinateSequenceFactory.DOUBLE_FACTORY); + + /** + * A reader with the legacy "unlabelled 3rd ordinate is Z" syntax disabled, + * so that WKT lacking a Z or M tag is parsed as genuinely 2D. + */ + static WKTReader strictReader = strictReader(); + + private static WKTReader strictReader() { + WKTReader reader = new WKTReader(geomFact); + reader.setIsOldJtsCoordinateSyntaxAllowed(false); + return reader; + } + + public GeometryCoordinateDimensionTest(String name) { super(name); } + + public void testXY() { + checkDimension("POINT (1 2)", 2); + } + + public void testXYZ() { + checkDimension("POINT Z (1 2 3)", 3); + } + + public void testXYM() { + checkDimension("POINT M (1 2 3)", 3); + } + + public void testXYZM() { + checkDimension("POINT ZM (1 2 3 4)", 4); + } + + public void testLineStringXYZ() { + checkDimension("LINESTRING Z (1 1 1, 2 2 2, 3 3 3)", 3); + } + + public void testPolygonXYZ() { + checkDimension("POLYGON Z ((1 9 2, 9 9 2, 9 1 2, 1 1 2, 1 9 2))", 3); + } + + public void testEmptyPoint() { + checkDimension("POINT EMPTY", 2); + } + + public void testMultiPolygonXYZM() { + checkDimension("MULTIPOLYGON ZM (((1 9 2 3, 9 9 2 3, 9 1 2 3, 1 1 2 3, 1 9 2 3)))", 4); + } + + public void testGeometryCollectionMixedDimension() { + checkDimension("GEOMETRYCOLLECTION (POINT (1 1), LINESTRING Z (1 1 1, 2 2 2))", 3); + } + + public void testGeometryCollectionAllEmpty() { + checkDimension("GEOMETRYCOLLECTION (POINT EMPTY, LINESTRING EMPTY)", 2); + } + + private void checkDimension(String wkt, int expectedDimension) { + Geometry geom = read(strictReader, wkt); + int actual = Geometry.getCoordinateDimension(geom); + assertEquals(expectedDimension, actual); + } +} From 1ecfc6045889e115174740a694317f55c65e0f58 Mon Sep 17 00:00:00 2001 From: Matthew de Detrich Date: Wed, 30 Sep 2026 20:48:37 +0200 Subject: [PATCH 2/5] Make getCoordinateDimension an instance method --- .../org/locationtech/jts/geom/Geometry.java | 65 +++++++------------ .../jts/geom/GeometryCollection.java | 8 +++ .../org/locationtech/jts/geom/LineString.java | 4 ++ .../java/org/locationtech/jts/geom/Point.java | 4 ++ .../org/locationtech/jts/geom/Polygon.java | 8 +++ .../geom/GeometryCoordinateDimensionTest.java | 4 +- 6 files changed, 50 insertions(+), 43 deletions(-) diff --git a/modules/core/src/main/java/org/locationtech/jts/geom/Geometry.java b/modules/core/src/main/java/org/locationtech/jts/geom/Geometry.java index 93fc0818c4..6c4c496626 100644 --- a/modules/core/src/main/java/org/locationtech/jts/geom/Geometry.java +++ b/modules/core/src/main/java/org/locationtech/jts/geom/Geometry.java @@ -563,6 +563,25 @@ public Point getInteriorPoint() */ public abstract int getDimension(); + /** + * Returns the coordinate dimension of this geometry, + * based on the dimension of its {@link CoordinateSequence}s + * (i.e. 2 for XY, 3 for XYZ or XYM, or 4 for XYZM). + * For a {@link GeometryCollection} this is the maximum coordinate dimension + * over all of its elements. + * Empty geometries (or empty elements of a collection) are assumed + * to have coordinate dimension 2, and do not affect the result + * unless all elements are empty. + *

+ * Note that this is a different concept to the topological + * dimension returned by {@link #getDimension()}. + * + * @return the coordinate dimension of this geometry (2, 3 or 4) + * + * @see #getDimension() + */ + public abstract int getCoordinateDimension(); + /** * Tests whether an atomic geometry or any element of a collection * has the specified dimension. @@ -1737,49 +1756,13 @@ static void checkNotGeometryCollection(Geometry g) { } /** - * Computes the coordinate dimension of a Geometry, based on the - * coordinate dimensions of its {@link CoordinateSequence}s - * (i.e. 2 for XY, 3 for XYZ or XYM, or 4 for XYZM). - * For a {@link GeometryCollection} this is the maximum coordinate dimension - * over all of its elements. - * Empty geometries (or empty elements of a collection) are assumed - * to have coordinate dimension 2, and do not affect the result - * unless all elements are empty. + * Returns the size of a coordinate sequence's dimension, + * treating an empty sequence as having dimension 2. * - * @param g the Geometry to get the coordinate dimension of - * @return the coordinate dimension of the geometry (2, 3 or 4) + * @param seq the coordinate sequence + * @return the coordinate dimension of the sequence */ - public static int getCoordinateDimension(Geometry g) { - if (g instanceof Point) { - return coordinateSequenceDimension(((Point) g).getCoordinateSequence()); - } - if (g instanceof LineString) { - return coordinateSequenceDimension(((LineString) g).getCoordinateSequence()); - } - if (g instanceof Polygon) { - Polygon poly = (Polygon) g; - int dimension = 2; - LinearRing shell = poly.getExteriorRing(); - if (shell != null) { - dimension = coordinateSequenceDimension(shell.getCoordinateSequence()); - } - for (int i = 0; i < poly.getNumInteriorRing(); i++) { - dimension = Math.max(dimension, coordinateSequenceDimension(poly.getInteriorRingN(i).getCoordinateSequence())); - } - return dimension; - } - if (g instanceof GeometryCollection) { - GeometryCollection gc = (GeometryCollection) g; - int dimension = 2; - for (int i = 0; i < gc.getNumGeometries(); i++) { - dimension = Math.max(dimension, getCoordinateDimension(gc.getGeometryN(i))); - } - return dimension; - } - return 2; - } - - private static int coordinateSequenceDimension(CoordinateSequence seq) { + static int getCoordinateDimension(CoordinateSequence seq) { return seq.size() > 0 ? seq.getDimension() : 2; } diff --git a/modules/core/src/main/java/org/locationtech/jts/geom/GeometryCollection.java b/modules/core/src/main/java/org/locationtech/jts/geom/GeometryCollection.java index f89c84c4b5..b127a0ec73 100644 --- a/modules/core/src/main/java/org/locationtech/jts/geom/GeometryCollection.java +++ b/modules/core/src/main/java/org/locationtech/jts/geom/GeometryCollection.java @@ -113,6 +113,14 @@ public int getDimension() { //*/ } + public int getCoordinateDimension() { + int dimension = 2; + for (int i = 0; i < geometries.length; i++) { + dimension = Math.max(dimension, geometries[i].getCoordinateDimension()); + } + return dimension; + } + public boolean hasDimension(int dim) { if (geomCollDim == null) { geomCollDim = new GeometryCollectionDimension(this); diff --git a/modules/core/src/main/java/org/locationtech/jts/geom/LineString.java b/modules/core/src/main/java/org/locationtech/jts/geom/LineString.java index bf79badbc6..2d67029305 100644 --- a/modules/core/src/main/java/org/locationtech/jts/geom/LineString.java +++ b/modules/core/src/main/java/org/locationtech/jts/geom/LineString.java @@ -113,6 +113,10 @@ public int getDimension() { return 1; } + public int getCoordinateDimension() { + return getCoordinateDimension(points); + } + public int getBoundaryDimension() { if (isClosed()) { return Dimension.FALSE; diff --git a/modules/core/src/main/java/org/locationtech/jts/geom/Point.java b/modules/core/src/main/java/org/locationtech/jts/geom/Point.java index cbe145416f..e35251ef4f 100644 --- a/modules/core/src/main/java/org/locationtech/jts/geom/Point.java +++ b/modules/core/src/main/java/org/locationtech/jts/geom/Point.java @@ -91,6 +91,10 @@ public int getDimension() { return 0; } + public int getCoordinateDimension() { + return getCoordinateDimension(coordinates); + } + public int getBoundaryDimension() { return Dimension.FALSE; } diff --git a/modules/core/src/main/java/org/locationtech/jts/geom/Polygon.java b/modules/core/src/main/java/org/locationtech/jts/geom/Polygon.java index 94a8bc17fa..324de53a99 100644 --- a/modules/core/src/main/java/org/locationtech/jts/geom/Polygon.java +++ b/modules/core/src/main/java/org/locationtech/jts/geom/Polygon.java @@ -165,6 +165,14 @@ public int getDimension() { return 2; } + public int getCoordinateDimension() { + int dimension = shell.getCoordinateDimension(); + for (int i = 0; i < holes.length; i++) { + dimension = Math.max(dimension, holes[i].getCoordinateDimension()); + } + return dimension; + } + public int getBoundaryDimension() { return 1; } diff --git a/modules/core/src/test/java/org/locationtech/jts/geom/GeometryCoordinateDimensionTest.java b/modules/core/src/test/java/org/locationtech/jts/geom/GeometryCoordinateDimensionTest.java index 885ed2513c..28f816fd80 100644 --- a/modules/core/src/test/java/org/locationtech/jts/geom/GeometryCoordinateDimensionTest.java +++ b/modules/core/src/test/java/org/locationtech/jts/geom/GeometryCoordinateDimensionTest.java @@ -7,7 +7,7 @@ import test.jts.GeometryTestCase; /** - * Tests for {@link Geometry#getCoordinateDimension(Geometry)}. + * Tests for {@link Geometry#getCoordinateDimension()}. */ public class GeometryCoordinateDimensionTest extends GeometryTestCase { public static void main(String args[]) { @@ -72,7 +72,7 @@ public void testGeometryCollectionAllEmpty() { private void checkDimension(String wkt, int expectedDimension) { Geometry geom = read(strictReader, wkt); - int actual = Geometry.getCoordinateDimension(geom); + int actual = geom.getCoordinateDimension(); assertEquals(expectedDimension, actual); } } From d2f3b1ae199eeb2828416f06c4a2fc0474f5344b Mon Sep 17 00:00:00 2001 From: Matthew de Detrich Date: Wed, 30 Sep 2026 20:50:16 +0200 Subject: [PATCH 3/5] Report coordinate dimension of empty sequences as-is --- .../org/locationtech/jts/geom/Geometry.java | 23 ++++++++----------- .../org/locationtech/jts/geom/LineString.java | 2 +- .../java/org/locationtech/jts/geom/Point.java | 2 +- .../geom/GeometryCoordinateDimensionTest.java | 16 +++++++++++++ 4 files changed, 27 insertions(+), 16 deletions(-) diff --git a/modules/core/src/main/java/org/locationtech/jts/geom/Geometry.java b/modules/core/src/main/java/org/locationtech/jts/geom/Geometry.java index 6c4c496626..377b144963 100644 --- a/modules/core/src/main/java/org/locationtech/jts/geom/Geometry.java +++ b/modules/core/src/main/java/org/locationtech/jts/geom/Geometry.java @@ -569,9 +569,15 @@ public Point getInteriorPoint() * (i.e. 2 for XY, 3 for XYZ or XYM, or 4 for XYZM). * For a {@link GeometryCollection} this is the maximum coordinate dimension * over all of its elements. - * Empty geometries (or empty elements of a collection) are assumed - * to have coordinate dimension 2, and do not affect the result - * unless all elements are empty. + * An empty collection has coordinate dimension 2. + *

+ * Empty geometries report the dimension of their (empty) + * coordinate sequences, so e.g. POINT Z EMPTY read from WKT + * has coordinate dimension 3. + * Note that empty geometries created by {@link GeometryFactory} + * (e.g. via {@link GeometryFactory#createPoint()}) may report + * a coordinate dimension of 3, since that is the default + * dimension of their coordinate sequences. *

* Note that this is a different concept to the topological * dimension returned by {@link #getDimension()}. @@ -1755,17 +1761,6 @@ static void checkNotGeometryCollection(Geometry g) { } } - /** - * Returns the size of a coordinate sequence's dimension, - * treating an empty sequence as having dimension 2. - * - * @param seq the coordinate sequence - * @return the coordinate dimension of the sequence - */ - static int getCoordinateDimension(CoordinateSequence seq) { - return seq.size() > 0 ? seq.getDimension() : 2; - } - /** * Tests whether this is an instance of a general {@link GeometryCollection}, * rather than a homogeneous subclass. diff --git a/modules/core/src/main/java/org/locationtech/jts/geom/LineString.java b/modules/core/src/main/java/org/locationtech/jts/geom/LineString.java index 2d67029305..8ca835592a 100644 --- a/modules/core/src/main/java/org/locationtech/jts/geom/LineString.java +++ b/modules/core/src/main/java/org/locationtech/jts/geom/LineString.java @@ -114,7 +114,7 @@ public int getDimension() { } public int getCoordinateDimension() { - return getCoordinateDimension(points); + return points.getDimension(); } public int getBoundaryDimension() { diff --git a/modules/core/src/main/java/org/locationtech/jts/geom/Point.java b/modules/core/src/main/java/org/locationtech/jts/geom/Point.java index e35251ef4f..d8e511e510 100644 --- a/modules/core/src/main/java/org/locationtech/jts/geom/Point.java +++ b/modules/core/src/main/java/org/locationtech/jts/geom/Point.java @@ -92,7 +92,7 @@ public int getDimension() { } public int getCoordinateDimension() { - return getCoordinateDimension(coordinates); + return coordinates.getDimension(); } public int getBoundaryDimension() { diff --git a/modules/core/src/test/java/org/locationtech/jts/geom/GeometryCoordinateDimensionTest.java b/modules/core/src/test/java/org/locationtech/jts/geom/GeometryCoordinateDimensionTest.java index 28f816fd80..7bc5f56abf 100644 --- a/modules/core/src/test/java/org/locationtech/jts/geom/GeometryCoordinateDimensionTest.java +++ b/modules/core/src/test/java/org/locationtech/jts/geom/GeometryCoordinateDimensionTest.java @@ -58,6 +58,22 @@ public void testEmptyPoint() { checkDimension("POINT EMPTY", 2); } + public void testEmptyPointZ() { + checkDimension("POINT Z EMPTY", 3); + } + + public void testEmptyPointZM() { + checkDimension("POINT ZM EMPTY", 4); + } + + public void testEmptyLineStringZ() { + checkDimension("LINESTRING Z EMPTY", 3); + } + + public void testGeometryCollectionEmptyZ() { + checkDimension("GEOMETRYCOLLECTION (POINT Z EMPTY, LINESTRING EMPTY)", 3); + } + public void testMultiPolygonXYZM() { checkDimension("MULTIPOLYGON ZM (((1 9 2 3, 9 9 2 3, 9 1 2 3, 1 1 2 3, 1 9 2 3)))", 4); } From 26416a3428c6a44d6995470c0ff5930ee2b2062c Mon Sep 17 00:00:00 2001 From: Matthew de Detrich Date: Wed, 30 Sep 2026 20:52:35 +0200 Subject: [PATCH 4/5] Add CoordinateArraySequence tests and license header --- .../geom/GeometryCoordinateDimensionTest.java | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/modules/core/src/test/java/org/locationtech/jts/geom/GeometryCoordinateDimensionTest.java b/modules/core/src/test/java/org/locationtech/jts/geom/GeometryCoordinateDimensionTest.java index 7bc5f56abf..8bae0b86db 100644 --- a/modules/core/src/test/java/org/locationtech/jts/geom/GeometryCoordinateDimensionTest.java +++ b/modules/core/src/test/java/org/locationtech/jts/geom/GeometryCoordinateDimensionTest.java @@ -1,5 +1,18 @@ +/* + * Copyright (c) 2026 Matthew de Detrich. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * and Eclipse Distribution License v. 1.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * + * http://www.eclipse.org/org/documents/edl-v10.php. + */ + package org.locationtech.jts.geom; +import org.locationtech.jts.geom.impl.CoordinateArraySequenceFactory; import org.locationtech.jts.geom.impl.PackedCoordinateSequenceFactory; import org.locationtech.jts.io.WKTReader; @@ -28,6 +41,16 @@ private static WKTReader strictReader() { return reader; } + static GeometryFactory arrayGeomFact = new GeometryFactory(CoordinateArraySequenceFactory.instance()); + + static WKTReader arrayStrictReader = arrayStrictReader(); + + private static WKTReader arrayStrictReader() { + WKTReader reader = new WKTReader(arrayGeomFact); + reader.setIsOldJtsCoordinateSyntaxAllowed(false); + return reader; + } + public GeometryCoordinateDimensionTest(String name) { super(name); } public void testXY() { @@ -86,8 +109,28 @@ public void testGeometryCollectionAllEmpty() { checkDimension("GEOMETRYCOLLECTION (POINT EMPTY, LINESTRING EMPTY)", 2); } + public void testArraySequenceXY() { + checkDimension(arrayStrictReader, "POINT (1 2)", 2); + } + + public void testArraySequenceEmptyPointZ() { + checkDimension(arrayStrictReader, "POINT Z EMPTY", 3); + } + + /** + * Empty geometries created by the factory use the + * default coordinate sequence dimension (3). + */ + public void testArraySequenceFactoryEmptyPoint() { + assertEquals(3, arrayGeomFact.createPoint().getCoordinateDimension()); + } + private void checkDimension(String wkt, int expectedDimension) { - Geometry geom = read(strictReader, wkt); + checkDimension(strictReader, wkt, expectedDimension); + } + + private void checkDimension(WKTReader reader, String wkt, int expectedDimension) { + Geometry geom = read(reader, wkt); int actual = geom.getCoordinateDimension(); assertEquals(expectedDimension, actual); } From 6b2b3c3103d7c6c3880463b7922ef634b6e7d149 Mon Sep 17 00:00:00 2001 From: Matthew de Detrich Date: Wed, 30 Sep 2026 20:53:54 +0200 Subject: [PATCH 5/5] Document that coordinate dimension 3 is ambiguous --- .../core/src/main/java/org/locationtech/jts/geom/Geometry.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/core/src/main/java/org/locationtech/jts/geom/Geometry.java b/modules/core/src/main/java/org/locationtech/jts/geom/Geometry.java index 377b144963..7876d8b472 100644 --- a/modules/core/src/main/java/org/locationtech/jts/geom/Geometry.java +++ b/modules/core/src/main/java/org/locationtech/jts/geom/Geometry.java @@ -567,6 +567,9 @@ public Point getInteriorPoint() * Returns the coordinate dimension of this geometry, * based on the dimension of its {@link CoordinateSequence}s * (i.e. 2 for XY, 3 for XYZ or XYM, or 4 for XYZM). + * A coordinate dimension of 3 does not distinguish XYZ from XYM; + * use {@link CoordinateSequence#hasZ()} and {@link CoordinateSequence#hasM()} + * to determine which ordinates are present. * For a {@link GeometryCollection} this is the maximum coordinate dimension * over all of its elements. * An empty collection has coordinate dimension 2.