Skip to content

Commit

Permalink
Fix writing XYM geometries as WKB
Browse files Browse the repository at this point in the history
  • Loading branch information
Kontinuation committed Oct 17, 2024
1 parent 6a9af07 commit 9412944
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 97 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright (c) 2016 Vivid Solutions.
*
* 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<Ordinate> checkOrdinateFlags;
private final EnumSet<Ordinate> outputOrdinates;

/**
* Creates an instance of this class
*
* @param checkOrdinateFlags the index for the ordinates to test.
*/
CheckOrdinatesFilter(EnumSet<Ordinate> 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<Ordinate> getOutputOrdinates() {
return outputOrdinates;
}
}
71 changes: 41 additions & 30 deletions modules/core/src/main/java/org/locationtech/jts/io/WKBWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<Ordinate> 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<Ordinate> 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<Ordinate> 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<Ordinate> 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<Ordinate> 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;
Expand All @@ -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<Ordinate> outputOrdinates, Geometry g, OutStream os)
throws IOException
{
int ordinals = 0;
Expand All @@ -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<Ordinate> 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);
Expand All @@ -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<Ordinate> 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);
}
Expand Down
55 changes: 0 additions & 55 deletions modules/core/src/main/java/org/locationtech/jts/io/WKTWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Ordinate> checkOrdinateFlags;
private final EnumSet<Ordinate> outputOrdinates;

/**
* Creates an instance of this class
* @param checkOrdinateFlags the index for the ordinates to test.
*/
private CheckOrdinatesFilter(EnumSet<Ordinate> 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<Ordinate> getOutputOrdinates() {
return outputOrdinates;
}
}

private EnumSet<Ordinate> outputOrdinates;
private final int outputDimension;
private PrecisionModel precisionModel = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down

0 comments on commit 9412944

Please sign in to comment.