Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions modules/core/src/main/java/org/locationtech/jts/geom/Geometry.java

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

XY / XYZ / XYM / XYZM, mixed collections taking the max, and “3 means Z or M” all match GEOS. LinearRing falls through LineString, Multi* through GeometryCollection. The strict WKT reader is the correct fixture. That is enough for the original #654 2-vs-3 examples.

This getter does not distinguish XYZ from XYM (both 3). That matches GEOS. Callers who need the ordinate set still have to look at hasZ / hasM on the sequence — fine for #654; do not claim it fully drives WKBWriter.setOutputOrdinates.

Empty MultiLineString / MultiPolygon from WKT already drop the Z flag in WKTReader (createMultiLineString() with no flags). Pre-existing; this method cannot invent a dimension those objects do not store.

Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,34 @@ 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).
* 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.
* <p>
* Empty geometries report the dimension of their (empty)
* coordinate sequences, so e.g. <code>POINT Z EMPTY</code> 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.
* <p>
* 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ public int getDimension() {
return 1;
}

public int getCoordinateDimension() {
return points.getDimension();
}

public int getBoundaryDimension() {
if (isClosed()) {
return Dimension.FALSE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ public int getDimension() {
return 0;
}

public int getCoordinateDimension() {
return coordinates.getDimension();
}

public int getBoundaryDimension() {
return Dimension.FALSE;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*
* 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;

import junit.textui.TestRunner;
import test.jts.GeometryTestCase;

/**
* Tests for {@link Geometry#getCoordinateDimension()}.
*/
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;
}

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() {
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);
}
Comment on lines +80 to +82

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

POINT EMPTY only locks the untagged case. Add POINT Z EMPTY / LINESTRING Z EMPTY expecting 3 (GEOS / the WKTReader empty sequence). Also run one case on the default CoordinateArraySequence factory — these tests are Packed-only, which hides the factory empty-default-3 footgun.

New test file also needs the license header CONTRIBUTING asks for.


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);
}

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);
}

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) {
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);
}
}