diff --git a/modules/core/src/main/java/org/locationtech/jts/io/CheckOrdinatesFilter.java b/modules/core/src/main/java/org/locationtech/jts/io/CheckOrdinatesFilter.java new file mode 100644 index 0000000000..20715b7e8b --- /dev/null +++ b/modules/core/src/main/java/org/locationtech/jts/io/CheckOrdinatesFilter.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2024 Kristin Cowalcijk. + * + * 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.io; + +import org.locationtech.jts.geom.CoordinateSequence; +import org.locationtech.jts.geom.CoordinateSequenceFilter; + +import java.util.EnumSet; + +/** + * A filter implementation to test if a coordinate sequence actually has meaningful values for an + * ordinate bit-pattern + */ +class CheckOrdinatesFilter implements CoordinateSequenceFilter { + + private final EnumSet checkOrdinateFlags; + private final EnumSet outputOrdinates; + + /** + * Creates an instance of this class + * + * @param checkOrdinateFlags the index for the ordinates to test. + */ + CheckOrdinatesFilter(EnumSet checkOrdinateFlags) { + + this.outputOrdinates = EnumSet.of(Ordinate.X, Ordinate.Y); + this.checkOrdinateFlags = checkOrdinateFlags; + } + + /** + * @see CoordinateSequenceFilter#isGeometryChanged + */ + public void filter(CoordinateSequence seq, int i) { + + if (checkOrdinateFlags.contains(Ordinate.Z) && !outputOrdinates.contains(Ordinate.Z)) { + if (!Double.isNaN(seq.getZ(i))) { + outputOrdinates.add(Ordinate.Z); + } + } + + if (checkOrdinateFlags.contains(Ordinate.M) && !outputOrdinates.contains(Ordinate.M)) { + if (!Double.isNaN(seq.getM(i))) { + outputOrdinates.add(Ordinate.M); + } + } + } + + /** + * @see CoordinateSequenceFilter#isGeometryChanged + */ + public boolean isGeometryChanged() { + return false; + } + + /** + * @see CoordinateSequenceFilter#isDone + */ + public boolean isDone() { + return outputOrdinates.equals(checkOrdinateFlags); + } + + /** + * Gets the evaluated ordinate bit-pattern + * + * @return A bit-pattern of ordinates with valid values masked by {@link #checkOrdinateFlags}. + */ + EnumSet getOutputOrdinates() { + return outputOrdinates; + } +} diff --git a/modules/core/src/main/java/org/locationtech/jts/io/WKBWriter.java b/modules/core/src/main/java/org/locationtech/jts/io/WKBWriter.java index 38fd9b1c5e..1de61c255c 100644 --- a/modules/core/src/main/java/org/locationtech/jts/io/WKBWriter.java +++ b/modules/core/src/main/java/org/locationtech/jts/io/WKBWriter.java @@ -381,72 +381,80 @@ public byte[] write(Geometry geom) */ public void write(Geometry geom, OutStream os) throws IOException { + // evaluate the ordinates actually present in the geometry + EnumSet actualOutputOrdinates = this.outputOrdinates; + if (!geom.isEmpty()) { + CheckOrdinatesFilter cof = new CheckOrdinatesFilter(this.outputOrdinates); + geom.apply(cof); + actualOutputOrdinates = cof.getOutputOrdinates(); + } + if (geom instanceof Point) - writePoint((Point) geom, os); + writePoint((Point) geom, actualOutputOrdinates, os); // LinearRings will be written as LineStrings else if (geom instanceof LineString) - writeLineString((LineString) geom, os); + writeLineString((LineString) geom, actualOutputOrdinates, os); else if (geom instanceof Polygon) - writePolygon((Polygon) geom, os); + writePolygon((Polygon) geom, actualOutputOrdinates, os); else if (geom instanceof MultiPoint) writeGeometryCollection(WKBConstants.wkbMultiPoint, - (MultiPoint) geom, os); + (MultiPoint) geom, actualOutputOrdinates, os); else if (geom instanceof MultiLineString) writeGeometryCollection(WKBConstants.wkbMultiLineString, - (MultiLineString) geom, os); + (MultiLineString) geom, actualOutputOrdinates, os); else if (geom instanceof MultiPolygon) writeGeometryCollection(WKBConstants.wkbMultiPolygon, - (MultiPolygon) geom, os); + (MultiPolygon) geom, actualOutputOrdinates, os); else if (geom instanceof GeometryCollection) writeGeometryCollection(WKBConstants.wkbGeometryCollection, - (GeometryCollection) geom, os); + (GeometryCollection) geom, actualOutputOrdinates, os); else { Assert.shouldNeverReachHere("Unknown Geometry type"); } } - private void writePoint(Point pt, OutStream os) throws IOException + private void writePoint(Point pt, EnumSet outputOrdinates, OutStream os) throws IOException { writeByteOrder(os); - writeGeometryType(WKBConstants.wkbPoint, pt, os); + writeGeometryType(WKBConstants.wkbPoint, outputOrdinates, pt, os); if (pt.getCoordinateSequence().size() == 0) { // write empty point as NaNs (extension to OGC standard) - writeNaNs(outputDimension, os); + writeNaNs(outputOrdinates, os); } else { - writeCoordinateSequence(pt.getCoordinateSequence(), false, os); + writeCoordinateSequence(pt.getCoordinateSequence(), outputOrdinates, false, os); } } - private void writeLineString(LineString line, OutStream os) + private void writeLineString(LineString line, EnumSet outputOrdinates, OutStream os) throws IOException { writeByteOrder(os); - writeGeometryType(WKBConstants.wkbLineString, line, os); - writeCoordinateSequence(line.getCoordinateSequence(), true, os); + writeGeometryType(WKBConstants.wkbLineString, outputOrdinates, line, os); + writeCoordinateSequence(line.getCoordinateSequence(), outputOrdinates, true, os); } - private void writePolygon(Polygon poly, OutStream os) throws IOException + private void writePolygon(Polygon poly, EnumSet outputOrdinates, OutStream os) throws IOException { writeByteOrder(os); - writeGeometryType(WKBConstants.wkbPolygon, poly, os); + writeGeometryType(WKBConstants.wkbPolygon, outputOrdinates, poly, os); //--- write empty polygons with no rings (OCG extension) if (poly.isEmpty()) { writeInt(0, os); return; } writeInt(poly.getNumInteriorRing() + 1, os); - writeCoordinateSequence(poly.getExteriorRing().getCoordinateSequence(), true, os); + writeCoordinateSequence(poly.getExteriorRing().getCoordinateSequence(), outputOrdinates, true, os); for (int i = 0; i < poly.getNumInteriorRing(); i++) { - writeCoordinateSequence(poly.getInteriorRingN(i).getCoordinateSequence(), true, + writeCoordinateSequence(poly.getInteriorRingN(i).getCoordinateSequence(), outputOrdinates, true, os); } } - private void writeGeometryCollection(int geometryType, GeometryCollection gc, + private void writeGeometryCollection(int geometryType, GeometryCollection gc, EnumSet outputOrdinates, OutStream os) throws IOException { writeByteOrder(os); - writeGeometryType(geometryType, gc, os); + writeGeometryType(geometryType, outputOrdinates, gc, os); writeInt(gc.getNumGeometries(), os); boolean originalIncludeSRID = this.includeSRID; this.includeSRID = false; @@ -465,7 +473,7 @@ private void writeByteOrder(OutStream os) throws IOException os.write(buf, 1); } - private void writeGeometryType(int geometryType, Geometry g, OutStream os) + private void writeGeometryType(int geometryType, EnumSet outputOrdinates, Geometry g, OutStream os) throws IOException { int ordinals = 0; @@ -492,18 +500,20 @@ private void writeInt(int intValue, OutStream os) throws IOException os.write(buf, 4); } - private void writeCoordinateSequence(CoordinateSequence seq, boolean writeSize, OutStream os) + private void writeCoordinateSequence(CoordinateSequence seq, EnumSet outputOrdinates, boolean writeSize, OutStream os) throws IOException { if (writeSize) writeInt(seq.size(), os); + boolean hasZ = outputOrdinates.contains(Ordinate.Z); + boolean hasM = outputOrdinates.contains(Ordinate.M); for (int i = 0; i < seq.size(); i++) { - writeCoordinate(seq, i, os); + writeCoordinate(seq, hasZ, hasM, i, os); } } - private void writeCoordinate(CoordinateSequence seq, int index, OutStream os) + private void writeCoordinate(CoordinateSequence seq, boolean hasZ, boolean hasM, int index, OutStream os) throws IOException { ByteOrderValues.putDouble(seq.getX(index), buf, byteOrder); @@ -512,25 +522,26 @@ private void writeCoordinate(CoordinateSequence seq, int index, OutStream os) os.write(buf, 8); // only write 3rd dim if caller has requested it for this writer - if (outputDimension >= 3) { + if (hasZ) { // if 3rd dim is requested, only write it if the CoordinateSequence provides it - double ordVal = seq.getOrdinate(index, 2); + double ordVal = seq.getZ(index); ByteOrderValues.putDouble(ordVal, buf, byteOrder); os.write(buf, 8); } // only write 4th dim if caller has requested it for this writer - if (outputDimension == 4) { + if (hasM) { // if 4th dim is requested, only write it if the CoordinateSequence provides it - double ordVal = seq.getOrdinate(index, 3); + double ordVal = seq.getM(index); ByteOrderValues.putDouble(ordVal, buf, byteOrder); os.write(buf, 8); } } - private void writeNaNs(int numNaNs, OutStream os) + private void writeNaNs(EnumSet outputOrdinates, OutStream os) throws IOException { - for (int i = 0; i < numNaNs; i++) { + int dims = outputOrdinates.size(); + for (int i = 0; i < dims; i++) { ByteOrderValues.putDouble(Double.NaN, buf, byteOrder); os.write(buf, 8); } diff --git a/modules/core/src/main/java/org/locationtech/jts/io/WKTWriter.java b/modules/core/src/main/java/org/locationtech/jts/io/WKTWriter.java index 4cb2b28790..709dbe6869 100644 --- a/modules/core/src/main/java/org/locationtech/jts/io/WKTWriter.java +++ b/modules/core/src/main/java/org/locationtech/jts/io/WKTWriter.java @@ -19,7 +19,6 @@ import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.CoordinateSequence; -import org.locationtech.jts.geom.CoordinateSequenceFilter; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.GeometryCollection; import org.locationtech.jts.geom.LineString; @@ -175,60 +174,6 @@ private static String stringOfChar(char ch, int count) { return buf.toString(); } - /** - * A filter implementation to test if a coordinate sequence actually has - * meaningful values for an ordinate bit-pattern - */ - private class CheckOrdinatesFilter implements CoordinateSequenceFilter { - - private final EnumSet checkOrdinateFlags; - private final EnumSet outputOrdinates; - - /** - * Creates an instance of this class - - * @param checkOrdinateFlags the index for the ordinates to test. - */ - private CheckOrdinatesFilter(EnumSet checkOrdinateFlags) { - - this.outputOrdinates = EnumSet.of(Ordinate.X, Ordinate.Y); - this.checkOrdinateFlags = checkOrdinateFlags; - } - - /** @see org.locationtech.jts.geom.CoordinateSequenceFilter#isGeometryChanged */ - public void filter(CoordinateSequence seq, int i) { - - if (checkOrdinateFlags.contains(Ordinate.Z) && !outputOrdinates.contains(Ordinate.Z)) { - if (!Double.isNaN(seq.getZ(i))) - outputOrdinates.add(Ordinate.Z); - } - - if (checkOrdinateFlags.contains(Ordinate.M) && !outputOrdinates.contains(Ordinate.M)) { - if (!Double.isNaN(seq.getM(i))) - outputOrdinates.add(Ordinate.M); - } - } - - /** @see org.locationtech.jts.geom.CoordinateSequenceFilter#isGeometryChanged */ - public boolean isGeometryChanged() { - return false; - } - - /** @see org.locationtech.jts.geom.CoordinateSequenceFilter#isDone */ - public boolean isDone() { - return outputOrdinates.equals(checkOrdinateFlags); - } - - /** - * Gets the evaluated ordinate bit-pattern - * - * @return A bit-pattern of ordinates with valid values masked by {@link #checkOrdinateFlags}. - */ - EnumSet getOutputOrdinates() { - return outputOrdinates; - } - } - private EnumSet outputOrdinates; private final int outputDimension; private PrecisionModel precisionModel = null; diff --git a/modules/core/src/test/java/org/locationtech/jts/io/WKBWriterTest.java b/modules/core/src/test/java/org/locationtech/jts/io/WKBWriterTest.java index d70276c5b5..eac5196e9e 100644 --- a/modules/core/src/test/java/org/locationtech/jts/io/WKBWriterTest.java +++ b/modules/core/src/test/java/org/locationtech/jts/io/WKBWriterTest.java @@ -127,24 +127,33 @@ public void testGeometryCollection() { 4326, "0107000020E61000000900000001010000000000000000000000000000000000F03F01010000000000000000000000000000000000F03F01010000000000000000000040000000000000084001020000000200000000000000000000400000000000000840000000000000104000000000000014400102000000020000000000000000000000000000000000F03F000000000000004000000000000008400102000000020000000000000000001040000000000000144000000000000018400000000000001C4001030000000200000005000000000000000000000000000000000000000000000000000000000000000000244000000000000024400000000000002440000000000000244000000000000000000000000000000000000000000000000005000000000000000000F03F000000000000F03F000000000000F03F0000000000002240000000000000224000000000000022400000000000002240000000000000F03F000000000000F03F000000000000F03F01030000000200000005000000000000000000000000000000000000000000000000000000000000000000244000000000000024400000000000002440000000000000244000000000000000000000000000000000000000000000000005000000000000000000F03F000000000000F03F000000000000F03F0000000000002240000000000000224000000000000022400000000000002240000000000000F03F000000000000F03F000000000000F03F0103000000010000000500000000000000000022C0000000000000000000000000000022C00000000000002440000000000000F0BF0000000000002440000000000000F0BF000000000000000000000000000022C00000000000000000"); } + + public void testWkbLineStringM() { + checkWKB( + "LINESTRING M(1 2 3, 5 6 7)", + 4, + ByteOrderValues.LITTLE_ENDIAN, + -1, + "010200004002000000000000000000F03F00000000000000400000000000000840000000000000144000000000000018400000000000001C40"); + } public void testWkbLineStringZM() throws ParseException { LineString lineZM = new GeometryFactory().createLineString(new Coordinate[]{new CoordinateXYZM(1,2,3,4), new CoordinateXYZM(5,6,7,8)}); byte[] write = new WKBWriter(4).write(lineZM); - LineString deserialisiert = (LineString) new WKBReader().read(write); + LineString lineZMRead = (LineString) new WKBReader().read(write); - assertEquals(lineZM, deserialisiert); - - assertEquals(1.0, lineZM.getPointN(0).getCoordinate().getX()); - assertEquals(2.0, lineZM.getPointN(0).getCoordinate().getY()); - assertEquals(3.0, lineZM.getPointN(0).getCoordinate().getZ()); - assertEquals(4.0, lineZM.getPointN(0).getCoordinate().getM()); - - assertEquals(5.0, lineZM.getPointN(1).getCoordinate().getX()); - assertEquals(6.0, lineZM.getPointN(1).getCoordinate().getY()); - assertEquals(7.0, lineZM.getPointN(1).getCoordinate().getZ()); - assertEquals(8.0, lineZM.getPointN(1).getCoordinate().getM()); + assertEquals(lineZM, lineZMRead); + + assertEquals(1.0, lineZMRead.getPointN(0).getCoordinate().getX()); + assertEquals(2.0, lineZMRead.getPointN(0).getCoordinate().getY()); + assertEquals(3.0, lineZMRead.getPointN(0).getCoordinate().getZ()); + assertEquals(4.0, lineZMRead.getPointN(0).getCoordinate().getM()); + + assertEquals(5.0, lineZMRead.getPointN(1).getCoordinate().getX()); + assertEquals(6.0, lineZMRead.getPointN(1).getCoordinate().getY()); + assertEquals(7.0, lineZMRead.getPointN(1).getCoordinate().getZ()); + assertEquals(8.0, lineZMRead.getPointN(1).getCoordinate().getM()); } void checkWKB(String wkt, int dimension, String expectedWKBHex) {