Skip to content

Commit 034207b

Browse files
authored
Fix writing XYM geometries as WKB (#1092)
1 parent a759728 commit 034207b

File tree

4 files changed

+141
-97
lines changed

4 files changed

+141
-97
lines changed
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright (c) 2024 Kristin Cowalcijk.
3+
*
4+
* All rights reserved. This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License 2.0
6+
* and Eclipse Distribution License v. 1.0 which accompanies this distribution.
7+
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
8+
* and the Eclipse Distribution License is available at
9+
*
10+
* http://www.eclipse.org/org/documents/edl-v10.php.
11+
*/
12+
package org.locationtech.jts.io;
13+
14+
import org.locationtech.jts.geom.CoordinateSequence;
15+
import org.locationtech.jts.geom.CoordinateSequenceFilter;
16+
17+
import java.util.EnumSet;
18+
19+
/**
20+
* A filter implementation to test if a coordinate sequence actually has meaningful values for an
21+
* ordinate bit-pattern
22+
*/
23+
class CheckOrdinatesFilter implements CoordinateSequenceFilter {
24+
25+
private final EnumSet<Ordinate> checkOrdinateFlags;
26+
private final EnumSet<Ordinate> outputOrdinates;
27+
28+
/**
29+
* Creates an instance of this class
30+
*
31+
* @param checkOrdinateFlags the index for the ordinates to test.
32+
*/
33+
CheckOrdinatesFilter(EnumSet<Ordinate> checkOrdinateFlags) {
34+
35+
this.outputOrdinates = EnumSet.of(Ordinate.X, Ordinate.Y);
36+
this.checkOrdinateFlags = checkOrdinateFlags;
37+
}
38+
39+
/**
40+
* @see CoordinateSequenceFilter#isGeometryChanged
41+
*/
42+
public void filter(CoordinateSequence seq, int i) {
43+
44+
if (checkOrdinateFlags.contains(Ordinate.Z) && !outputOrdinates.contains(Ordinate.Z)) {
45+
if (!Double.isNaN(seq.getZ(i))) {
46+
outputOrdinates.add(Ordinate.Z);
47+
}
48+
}
49+
50+
if (checkOrdinateFlags.contains(Ordinate.M) && !outputOrdinates.contains(Ordinate.M)) {
51+
if (!Double.isNaN(seq.getM(i))) {
52+
outputOrdinates.add(Ordinate.M);
53+
}
54+
}
55+
}
56+
57+
/**
58+
* @see CoordinateSequenceFilter#isGeometryChanged
59+
*/
60+
public boolean isGeometryChanged() {
61+
return false;
62+
}
63+
64+
/**
65+
* @see CoordinateSequenceFilter#isDone
66+
*/
67+
public boolean isDone() {
68+
return outputOrdinates.equals(checkOrdinateFlags);
69+
}
70+
71+
/**
72+
* Gets the evaluated ordinate bit-pattern
73+
*
74+
* @return A bit-pattern of ordinates with valid values masked by {@link #checkOrdinateFlags}.
75+
*/
76+
EnumSet<Ordinate> getOutputOrdinates() {
77+
return outputOrdinates;
78+
}
79+
}

modules/core/src/main/java/org/locationtech/jts/io/WKBWriter.java

Lines changed: 41 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -381,72 +381,80 @@ public byte[] write(Geometry geom)
381381
*/
382382
public void write(Geometry geom, OutStream os) throws IOException
383383
{
384+
// evaluate the ordinates actually present in the geometry
385+
EnumSet<Ordinate> actualOutputOrdinates = this.outputOrdinates;
386+
if (!geom.isEmpty()) {
387+
CheckOrdinatesFilter cof = new CheckOrdinatesFilter(this.outputOrdinates);
388+
geom.apply(cof);
389+
actualOutputOrdinates = cof.getOutputOrdinates();
390+
}
391+
384392
if (geom instanceof Point)
385-
writePoint((Point) geom, os);
393+
writePoint((Point) geom, actualOutputOrdinates, os);
386394
// LinearRings will be written as LineStrings
387395
else if (geom instanceof LineString)
388-
writeLineString((LineString) geom, os);
396+
writeLineString((LineString) geom, actualOutputOrdinates, os);
389397
else if (geom instanceof Polygon)
390-
writePolygon((Polygon) geom, os);
398+
writePolygon((Polygon) geom, actualOutputOrdinates, os);
391399
else if (geom instanceof MultiPoint)
392400
writeGeometryCollection(WKBConstants.wkbMultiPoint,
393-
(MultiPoint) geom, os);
401+
(MultiPoint) geom, actualOutputOrdinates, os);
394402
else if (geom instanceof MultiLineString)
395403
writeGeometryCollection(WKBConstants.wkbMultiLineString,
396-
(MultiLineString) geom, os);
404+
(MultiLineString) geom, actualOutputOrdinates, os);
397405
else if (geom instanceof MultiPolygon)
398406
writeGeometryCollection(WKBConstants.wkbMultiPolygon,
399-
(MultiPolygon) geom, os);
407+
(MultiPolygon) geom, actualOutputOrdinates, os);
400408
else if (geom instanceof GeometryCollection)
401409
writeGeometryCollection(WKBConstants.wkbGeometryCollection,
402-
(GeometryCollection) geom, os);
410+
(GeometryCollection) geom, actualOutputOrdinates, os);
403411
else {
404412
Assert.shouldNeverReachHere("Unknown Geometry type");
405413
}
406414
}
407415

408-
private void writePoint(Point pt, OutStream os) throws IOException
416+
private void writePoint(Point pt, EnumSet<Ordinate> outputOrdinates, OutStream os) throws IOException
409417
{
410418
writeByteOrder(os);
411-
writeGeometryType(WKBConstants.wkbPoint, pt, os);
419+
writeGeometryType(WKBConstants.wkbPoint, outputOrdinates, pt, os);
412420
if (pt.getCoordinateSequence().size() == 0) {
413421
// write empty point as NaNs (extension to OGC standard)
414-
writeNaNs(outputDimension, os);
422+
writeNaNs(outputOrdinates, os);
415423
} else {
416-
writeCoordinateSequence(pt.getCoordinateSequence(), false, os);
424+
writeCoordinateSequence(pt.getCoordinateSequence(), outputOrdinates, false, os);
417425
}
418426
}
419427

420-
private void writeLineString(LineString line, OutStream os)
428+
private void writeLineString(LineString line, EnumSet<Ordinate> outputOrdinates, OutStream os)
421429
throws IOException
422430
{
423431
writeByteOrder(os);
424-
writeGeometryType(WKBConstants.wkbLineString, line, os);
425-
writeCoordinateSequence(line.getCoordinateSequence(), true, os);
432+
writeGeometryType(WKBConstants.wkbLineString, outputOrdinates, line, os);
433+
writeCoordinateSequence(line.getCoordinateSequence(), outputOrdinates, true, os);
426434
}
427435

428-
private void writePolygon(Polygon poly, OutStream os) throws IOException
436+
private void writePolygon(Polygon poly, EnumSet<Ordinate> outputOrdinates, OutStream os) throws IOException
429437
{
430438
writeByteOrder(os);
431-
writeGeometryType(WKBConstants.wkbPolygon, poly, os);
439+
writeGeometryType(WKBConstants.wkbPolygon, outputOrdinates, poly, os);
432440
//--- write empty polygons with no rings (OCG extension)
433441
if (poly.isEmpty()) {
434442
writeInt(0, os);
435443
return;
436444
}
437445
writeInt(poly.getNumInteriorRing() + 1, os);
438-
writeCoordinateSequence(poly.getExteriorRing().getCoordinateSequence(), true, os);
446+
writeCoordinateSequence(poly.getExteriorRing().getCoordinateSequence(), outputOrdinates, true, os);
439447
for (int i = 0; i < poly.getNumInteriorRing(); i++) {
440-
writeCoordinateSequence(poly.getInteriorRingN(i).getCoordinateSequence(), true,
448+
writeCoordinateSequence(poly.getInteriorRingN(i).getCoordinateSequence(), outputOrdinates, true,
441449
os);
442450
}
443451
}
444452

445-
private void writeGeometryCollection(int geometryType, GeometryCollection gc,
453+
private void writeGeometryCollection(int geometryType, GeometryCollection gc, EnumSet<Ordinate> outputOrdinates,
446454
OutStream os) throws IOException
447455
{
448456
writeByteOrder(os);
449-
writeGeometryType(geometryType, gc, os);
457+
writeGeometryType(geometryType, outputOrdinates, gc, os);
450458
writeInt(gc.getNumGeometries(), os);
451459
boolean originalIncludeSRID = this.includeSRID;
452460
this.includeSRID = false;
@@ -465,7 +473,7 @@ private void writeByteOrder(OutStream os) throws IOException
465473
os.write(buf, 1);
466474
}
467475

468-
private void writeGeometryType(int geometryType, Geometry g, OutStream os)
476+
private void writeGeometryType(int geometryType, EnumSet<Ordinate> outputOrdinates, Geometry g, OutStream os)
469477
throws IOException
470478
{
471479
int ordinals = 0;
@@ -492,18 +500,20 @@ private void writeInt(int intValue, OutStream os) throws IOException
492500
os.write(buf, 4);
493501
}
494502

495-
private void writeCoordinateSequence(CoordinateSequence seq, boolean writeSize, OutStream os)
503+
private void writeCoordinateSequence(CoordinateSequence seq, EnumSet<Ordinate> outputOrdinates, boolean writeSize, OutStream os)
496504
throws IOException
497505
{
498506
if (writeSize)
499507
writeInt(seq.size(), os);
500508

509+
boolean hasZ = outputOrdinates.contains(Ordinate.Z);
510+
boolean hasM = outputOrdinates.contains(Ordinate.M);
501511
for (int i = 0; i < seq.size(); i++) {
502-
writeCoordinate(seq, i, os);
512+
writeCoordinate(seq, hasZ, hasM, i, os);
503513
}
504514
}
505515

506-
private void writeCoordinate(CoordinateSequence seq, int index, OutStream os)
516+
private void writeCoordinate(CoordinateSequence seq, boolean hasZ, boolean hasM, int index, OutStream os)
507517
throws IOException
508518
{
509519
ByteOrderValues.putDouble(seq.getX(index), buf, byteOrder);
@@ -512,25 +522,26 @@ private void writeCoordinate(CoordinateSequence seq, int index, OutStream os)
512522
os.write(buf, 8);
513523

514524
// only write 3rd dim if caller has requested it for this writer
515-
if (outputDimension >= 3) {
525+
if (hasZ) {
516526
// if 3rd dim is requested, only write it if the CoordinateSequence provides it
517-
double ordVal = seq.getOrdinate(index, 2);
527+
double ordVal = seq.getZ(index);
518528
ByteOrderValues.putDouble(ordVal, buf, byteOrder);
519529
os.write(buf, 8);
520530
}
521531
// only write 4th dim if caller has requested it for this writer
522-
if (outputDimension == 4) {
532+
if (hasM) {
523533
// if 4th dim is requested, only write it if the CoordinateSequence provides it
524-
double ordVal = seq.getOrdinate(index, 3);
534+
double ordVal = seq.getM(index);
525535
ByteOrderValues.putDouble(ordVal, buf, byteOrder);
526536
os.write(buf, 8);
527537
}
528538
}
529539

530-
private void writeNaNs(int numNaNs, OutStream os)
540+
private void writeNaNs(EnumSet<Ordinate> outputOrdinates, OutStream os)
531541
throws IOException
532542
{
533-
for (int i = 0; i < numNaNs; i++) {
543+
int dims = outputOrdinates.size();
544+
for (int i = 0; i < dims; i++) {
534545
ByteOrderValues.putDouble(Double.NaN, buf, byteOrder);
535546
os.write(buf, 8);
536547
}

modules/core/src/main/java/org/locationtech/jts/io/WKTWriter.java

Lines changed: 0 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919

2020
import org.locationtech.jts.geom.Coordinate;
2121
import org.locationtech.jts.geom.CoordinateSequence;
22-
import org.locationtech.jts.geom.CoordinateSequenceFilter;
2322
import org.locationtech.jts.geom.Geometry;
2423
import org.locationtech.jts.geom.GeometryCollection;
2524
import org.locationtech.jts.geom.LineString;
@@ -175,60 +174,6 @@ private static String stringOfChar(char ch, int count) {
175174
return buf.toString();
176175
}
177176

178-
/**
179-
* A filter implementation to test if a coordinate sequence actually has
180-
* meaningful values for an ordinate bit-pattern
181-
*/
182-
private class CheckOrdinatesFilter implements CoordinateSequenceFilter {
183-
184-
private final EnumSet<Ordinate> checkOrdinateFlags;
185-
private final EnumSet<Ordinate> outputOrdinates;
186-
187-
/**
188-
* Creates an instance of this class
189-
190-
* @param checkOrdinateFlags the index for the ordinates to test.
191-
*/
192-
private CheckOrdinatesFilter(EnumSet<Ordinate> checkOrdinateFlags) {
193-
194-
this.outputOrdinates = EnumSet.of(Ordinate.X, Ordinate.Y);
195-
this.checkOrdinateFlags = checkOrdinateFlags;
196-
}
197-
198-
/** @see org.locationtech.jts.geom.CoordinateSequenceFilter#isGeometryChanged */
199-
public void filter(CoordinateSequence seq, int i) {
200-
201-
if (checkOrdinateFlags.contains(Ordinate.Z) && !outputOrdinates.contains(Ordinate.Z)) {
202-
if (!Double.isNaN(seq.getZ(i)))
203-
outputOrdinates.add(Ordinate.Z);
204-
}
205-
206-
if (checkOrdinateFlags.contains(Ordinate.M) && !outputOrdinates.contains(Ordinate.M)) {
207-
if (!Double.isNaN(seq.getM(i)))
208-
outputOrdinates.add(Ordinate.M);
209-
}
210-
}
211-
212-
/** @see org.locationtech.jts.geom.CoordinateSequenceFilter#isGeometryChanged */
213-
public boolean isGeometryChanged() {
214-
return false;
215-
}
216-
217-
/** @see org.locationtech.jts.geom.CoordinateSequenceFilter#isDone */
218-
public boolean isDone() {
219-
return outputOrdinates.equals(checkOrdinateFlags);
220-
}
221-
222-
/**
223-
* Gets the evaluated ordinate bit-pattern
224-
*
225-
* @return A bit-pattern of ordinates with valid values masked by {@link #checkOrdinateFlags}.
226-
*/
227-
EnumSet<Ordinate> getOutputOrdinates() {
228-
return outputOrdinates;
229-
}
230-
}
231-
232177
private EnumSet<Ordinate> outputOrdinates;
233178
private final int outputDimension;
234179
private PrecisionModel precisionModel = null;

modules/core/src/test/java/org/locationtech/jts/io/WKBWriterTest.java

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -127,24 +127,33 @@ public void testGeometryCollection() {
127127
4326,
128128
"0107000020E61000000900000001010000000000000000000000000000000000F03F01010000000000000000000000000000000000F03F01010000000000000000000040000000000000084001020000000200000000000000000000400000000000000840000000000000104000000000000014400102000000020000000000000000000000000000000000F03F000000000000004000000000000008400102000000020000000000000000001040000000000000144000000000000018400000000000001C4001030000000200000005000000000000000000000000000000000000000000000000000000000000000000244000000000000024400000000000002440000000000000244000000000000000000000000000000000000000000000000005000000000000000000F03F000000000000F03F000000000000F03F0000000000002240000000000000224000000000000022400000000000002240000000000000F03F000000000000F03F000000000000F03F01030000000200000005000000000000000000000000000000000000000000000000000000000000000000244000000000000024400000000000002440000000000000244000000000000000000000000000000000000000000000000005000000000000000000F03F000000000000F03F000000000000F03F0000000000002240000000000000224000000000000022400000000000002240000000000000F03F000000000000F03F000000000000F03F0103000000010000000500000000000000000022C0000000000000000000000000000022C00000000000002440000000000000F0BF0000000000002440000000000000F0BF000000000000000000000000000022C00000000000000000");
129129
}
130+
131+
public void testWkbLineStringM() {
132+
checkWKB(
133+
"LINESTRING M(1 2 3, 5 6 7)",
134+
4,
135+
ByteOrderValues.LITTLE_ENDIAN,
136+
-1,
137+
"010200004002000000000000000000F03F00000000000000400000000000000840000000000000144000000000000018400000000000001C40");
138+
}
130139

131140
public void testWkbLineStringZM() throws ParseException {
132141
LineString lineZM = new GeometryFactory().createLineString(new Coordinate[]{new CoordinateXYZM(1,2,3,4), new CoordinateXYZM(5,6,7,8)});
133142
byte[] write = new WKBWriter(4).write(lineZM);
134143

135-
LineString deserialisiert = (LineString) new WKBReader().read(write);
144+
LineString lineZMRead = (LineString) new WKBReader().read(write);
136145

137-
assertEquals(lineZM, deserialisiert);
138-
139-
assertEquals(1.0, lineZM.getPointN(0).getCoordinate().getX());
140-
assertEquals(2.0, lineZM.getPointN(0).getCoordinate().getY());
141-
assertEquals(3.0, lineZM.getPointN(0).getCoordinate().getZ());
142-
assertEquals(4.0, lineZM.getPointN(0).getCoordinate().getM());
143-
144-
assertEquals(5.0, lineZM.getPointN(1).getCoordinate().getX());
145-
assertEquals(6.0, lineZM.getPointN(1).getCoordinate().getY());
146-
assertEquals(7.0, lineZM.getPointN(1).getCoordinate().getZ());
147-
assertEquals(8.0, lineZM.getPointN(1).getCoordinate().getM());
146+
assertEquals(lineZM, lineZMRead);
147+
148+
assertEquals(1.0, lineZMRead.getPointN(0).getCoordinate().getX());
149+
assertEquals(2.0, lineZMRead.getPointN(0).getCoordinate().getY());
150+
assertEquals(3.0, lineZMRead.getPointN(0).getCoordinate().getZ());
151+
assertEquals(4.0, lineZMRead.getPointN(0).getCoordinate().getM());
152+
153+
assertEquals(5.0, lineZMRead.getPointN(1).getCoordinate().getX());
154+
assertEquals(6.0, lineZMRead.getPointN(1).getCoordinate().getY());
155+
assertEquals(7.0, lineZMRead.getPointN(1).getCoordinate().getZ());
156+
assertEquals(8.0, lineZMRead.getPointN(1).getCoordinate().getM());
148157
}
149158

150159
void checkWKB(String wkt, int dimension, String expectedWKBHex) {

0 commit comments

Comments
 (0)