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
23 changes: 17 additions & 6 deletions modules/core/src/main/java/org/locationtech/jts/io/WKTWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,17 @@ private void appendOrdinateText(EnumSet<Ordinate> outputOrdinates, Writer writer
writer.append(WKTConstants.M);
}

/**
* Appends the {@code EMPTY} token, inserting a separator after any dimensional
* marker written by {@link #appendOrdinateText}.
*/
private void appendEmptyText(EnumSet<Ordinate> outputOrdinates, Writer writer) throws IOException {
if (outputOrdinates.contains(Ordinate.Z) || outputOrdinates.contains(Ordinate.M)) {
writer.write(" ");
}
writer.write(WKTConstants.EMPTY);
}
Comment on lines +738 to +743

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.

Right cut. appendOrdinateText writes Z/M/ZM with no trailing space, and existing goldens pin non-empty WKT as POINT Z(1 1 1), not Z (. Do not “fix” this by adding a trailing space on the ordinate marker — that would change every dimensional token.

Spacing only before EMPTY when Z or M is present also keeps XY as POINT EMPTY: the tagged writer already wrote the space after the type, so this helper must not add a second one.


/**
* Appends all members of a <code>CoordinateSequence</code> to the stream. Each {@code Coordinate} is separated from
* another using a colon, the ordinates of a {@code Coordinate} are separated by a space.
Expand All @@ -748,7 +759,7 @@ private void appendSequenceText(CoordinateSequence seq, EnumSet<Ordinate> output
throws IOException
{
if (seq.size() == 0) {
writer.write(WKTConstants.EMPTY);
appendEmptyText(outputOrdinates, writer);
}
else {
if (indentFirst) indent(useFormatting, level, writer);
Expand Down Expand Up @@ -785,7 +796,7 @@ private void appendPolygonText(
throws IOException
{
if (polygon.isEmpty()) {
writer.write(WKTConstants.EMPTY);
appendEmptyText(outputOrdinates, writer);
}
else {
if (indentFirst) indent(useFormatting, level, writer);
Expand Down Expand Up @@ -817,7 +828,7 @@ private void appendMultiPointText(
throws IOException
{
if (multiPoint.getNumGeometries() == 0) {
writer.write(WKTConstants.EMPTY);
appendEmptyText(outputOrdinates, writer);
}
else {
writer.write("(");
Expand Down Expand Up @@ -850,7 +861,7 @@ private void appendMultiLineStringText(MultiLineString multiLineString, EnumSet<
throws IOException
{
if (multiLineString.getNumGeometries() == 0) {
writer.write(WKTConstants.EMPTY);
appendEmptyText(outputOrdinates, writer);
}
else {
int level2 = level;
Expand Down Expand Up @@ -885,7 +896,7 @@ private void appendMultiPolygonText(
throws IOException
{
if (multiPolygon.getNumGeometries() == 0) {
writer.write(WKTConstants.EMPTY);
appendEmptyText(outputOrdinates, writer);
}
else {
int level2 = level;
Expand Down Expand Up @@ -920,7 +931,7 @@ private void appendGeometryCollectionText(
throws IOException
{
if (geometryCollection.getNumGeometries() == 0) {
writer.write(WKTConstants.EMPTY);
appendEmptyText(outputOrdinates, writer);

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.

All six instance writer.write(EMPTY) sites now go through the helper (sequence / polygon / multi-point / multi-linestring / multi-polygon / geometry collection). Static toLineString already used " " + EMPTY and is correctly left alone.

}
else {
int level2 = level;
Expand Down

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.

Dimensional EMPTY belongs here, not in WKTReadWriteTest (that file already has XY empty round-trips). Leaving Z/M/ZM EMPTY in WKTWriterTest is the right place.

Original file line number Diff line number Diff line change
Expand Up @@ -199,4 +199,42 @@ public void testWktLineStringZM() throws ParseException {
assertEquals(7.0, lineZM.getPointN(1).getCoordinate().getZ());
assertEquals(8.0, lineZM.getPointN(1).getCoordinate().getM());
}

/**
* Nested empty geometries with dimensional markers must keep the marker and
* {@code EMPTY} as separate tokens so that {@link WKTReader} can parse the output.
*/
public void testWriteNestedEmptyDimensionalGeometry() throws ParseException {
WKTReader reader = new WKTReader();
WKTWriter writer4 = new WKTWriter(4);

Geometry geometryZ = reader.read(
"GEOMETRYCOLLECTION Z ("
+ "LINESTRING Z (0 0 1, 1 1 2), "
+ "MULTILINESTRING Z EMPTY)");
String writtenZ = writer4.write(geometryZ);
assertTrue(writtenZ.contains("MULTILINESTRING Z EMPTY"));
assertFalse(writtenZ.contains("ZEMPTY"));
reader.read(writtenZ);
Comment on lines +211 to +218

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.

Exact #1223 repro for Z (and the M / ZM siblings below), plus a WKTReader round-trip. Enough to lock the reported bug.

contains + assertFalse(...ZEMPTY) is fine. An exact-string assert on the collection would also lock that the non-empty sibling still writes LINESTRING Z(...) with no space before (.

Optional, non-blocking: one top-level POINT Z EMPTY (and maybe POLYGON Z EMPTY) would pin appendSequenceText / appendPolygonText without changing the design. Nested empty LINESTRING / POLYGON take the same helper.


WKTWriter writerM = new WKTWriter(3);
writerM.setOutputOrdinates(Ordinate.createXYM());
Geometry geometryM = reader.read(
"GEOMETRYCOLLECTION M ("
+ "LINESTRING M (0 0 1, 1 1 2), "
+ "MULTILINESTRING M EMPTY)");
String writtenM = writerM.write(geometryM);
assertTrue(writtenM.contains("MULTILINESTRING M EMPTY"));
assertFalse(writtenM.contains("MEMPTY"));
reader.read(writtenM);

Geometry geometryZM = reader.read(
"GEOMETRYCOLLECTION ZM ("
+ "LINESTRING ZM (0 0 1 2, 1 1 2 3), "
+ "MULTILINESTRING ZM EMPTY)");
String writtenZM = writer4.write(geometryZM);
assertTrue(writtenZM.contains("MULTILINESTRING ZM EMPTY"));
assertFalse(writtenZM.contains("ZMEMPTY"));
reader.read(writtenZM);
}
}