Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix writing XYM geometries as WKB #1092

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright (c) 2016 Vivid Solutions.
Copy link
Contributor

Choose a reason for hiding this comment

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

this is incorrect for a new file

Copy link
Contributor Author

Choose a reason for hiding this comment

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

All source code in this file is taken from WKTWriter.java, which is an old file. Should we update the copyright header or preserve the copyright header of the file where it originates from?

*
* 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
Loading