diff --git a/CHANGES.md b/CHANGES.md index 8613c7383..98a103666 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,9 +7,12 @@ DATE: unreleased equidistant from the center. Before the change, there was potentially a large inaccuracy. (Hrishi Bakshi) -* \#163: EMPTY points in JTS are now convertible to a Spatial4j Shape instead of throwing an exception. +* \#163: "Empty" points in JTS are now convertible to a Spatial4j Shape instead of throwing an exception. (David Smiley) +* \#162: Fixed WKT & GeoJSON \[de\]serialization of "empty" points and geometrycollections. + (Jeen Broekstra, David Smiley) + ## VERSION 0.7 DATE: 27 December 2017 diff --git a/src/main/java/org/locationtech/spatial4j/io/GeoJSONReader.java b/src/main/java/org/locationtech/spatial4j/io/GeoJSONReader.java index fab6012d5..21cc78d3b 100644 --- a/src/main/java/org/locationtech/spatial4j/io/GeoJSONReader.java +++ b/src/main/java/org/locationtech/spatial4j/io/GeoJSONReader.java @@ -262,10 +262,6 @@ protected Shape readShape(JSONParser parser) throws IOException, ParseException } sub = parser.nextEvent(); } - if (shapes.isEmpty()) { - throw new ParseException("Shape Collection with no geometries!", - (int) parser.getPosition()); - } return ctx.makeCollection(shapes); } else { diff --git a/src/main/java/org/locationtech/spatial4j/io/GeoJSONWriter.java b/src/main/java/org/locationtech/spatial4j/io/GeoJSONWriter.java index c00aec70c..223ee9d73 100644 --- a/src/main/java/org/locationtech/spatial4j/io/GeoJSONWriter.java +++ b/src/main/java/org/locationtech/spatial4j/io/GeoJSONWriter.java @@ -39,6 +39,9 @@ public String getFormatName() { protected void write(Writer output, NumberFormat nf, double... coords) throws IOException { output.write('['); for (int i = 0; i < coords.length; i++) { + if (Double.isNaN(coords[i])) { + break; // empty point or no more coordinates + } if (i > 0) { output.append(','); } diff --git a/src/main/java/org/locationtech/spatial4j/io/WKTWriter.java b/src/main/java/org/locationtech/spatial4j/io/WKTWriter.java index 38a49156a..fc32e5983 100644 --- a/src/main/java/org/locationtech/spatial4j/io/WKTWriter.java +++ b/src/main/java/org/locationtech/spatial4j/io/WKTWriter.java @@ -8,19 +8,14 @@ package org.locationtech.spatial4j.io; -import org.locationtech.spatial4j.shape.Circle; -import org.locationtech.spatial4j.shape.Point; -import org.locationtech.spatial4j.shape.Rectangle; -import org.locationtech.spatial4j.shape.Shape; -import org.locationtech.spatial4j.shape.ShapeCollection; -import org.locationtech.spatial4j.shape.impl.BufferedLine; -import org.locationtech.spatial4j.shape.impl.BufferedLineString; - import java.io.IOException; import java.io.Writer; import java.math.RoundingMode; import java.text.NumberFormat; import java.util.Iterator; +import org.locationtech.spatial4j.shape.*; +import org.locationtech.spatial4j.shape.impl.BufferedLine; +import org.locationtech.spatial4j.shape.impl.BufferedLineString; public class WKTWriter implements ShapeWriter { @@ -42,8 +37,13 @@ protected NumberFormat getNumberFormat() { public String toString(Shape shape) { NumberFormat nf = getNumberFormat(); if (shape instanceof Point) { + Point point = (Point)shape; + if (point.isEmpty()) { + return "POINT EMPTY"; + } + StringBuilder buffer = new StringBuilder(); - return append(buffer.append("POINT ("),(Point)shape,nf).append(")").toString(); + return append(buffer.append("POINT ("), point, nf).append(")").toString(); } if (shape instanceof Rectangle) { NumberFormat nfMIN = nf; @@ -103,10 +103,17 @@ public String toString(Shape shape) { return str.toString(); } if(shape instanceof ShapeCollection) { + @SuppressWarnings("unchecked") + ShapeCollection collection = (ShapeCollection) shape; + + if (collection.isEmpty()) { + return "GEOMETRYCOLLECTION EMPTY"; + } + StringBuilder buffer = new StringBuilder(); buffer.append("GEOMETRYCOLLECTION ("); boolean first = true; - for(Shape sub : ((ShapeCollection)shape).getShapes()) { + for (Shape sub : collection.getShapes()) { if(!first) { buffer.append(","); } diff --git a/src/main/java/org/locationtech/spatial4j/io/jackson/ShapeAsGeoJSONSerializer.java b/src/main/java/org/locationtech/spatial4j/io/jackson/ShapeAsGeoJSONSerializer.java index 568639958..5bef269e8 100644 --- a/src/main/java/org/locationtech/spatial4j/io/jackson/ShapeAsGeoJSONSerializer.java +++ b/src/main/java/org/locationtech/spatial4j/io/jackson/ShapeAsGeoJSONSerializer.java @@ -37,8 +37,11 @@ public class ShapeAsGeoJSONSerializer extends JsonSerializer protected void write(JsonGenerator gen, double... coords) throws IOException { gen.writeStartArray(); - for (int i = 0; i < coords.length; i++) { - gen.writeNumber(coords[i]); + for (double coord : coords) { + if (Double.isNaN(coord)) { + break; // empty + } + gen.writeNumber(coord); } gen.writeEndArray(); } diff --git a/src/main/java/org/locationtech/spatial4j/io/jackson/ShapeDeserializer.java b/src/main/java/org/locationtech/spatial4j/io/jackson/ShapeDeserializer.java index 42790af7b..7092d573a 100644 --- a/src/main/java/org/locationtech/spatial4j/io/jackson/ShapeDeserializer.java +++ b/src/main/java/org/locationtech/spatial4j/io/jackson/ShapeDeserializer.java @@ -38,6 +38,9 @@ public ShapeDeserializer(SpatialContext ctx) { } public Point readPoint(ArrayNode arr, ShapeFactory factory) { + if(arr.size()==0) { + return factory.pointXY(Double.NaN, Double.NaN); + } double x = arr.get(0).asDouble(); double y = arr.get(1).asDouble(); if(arr.size()==3) { diff --git a/src/test/java/org/locationtech/spatial4j/io/GeneralPolyshapeTest.java b/src/test/java/org/locationtech/spatial4j/io/GeneralPolyshapeTest.java index cc74f53de..c8d652b6f 100644 --- a/src/test/java/org/locationtech/spatial4j/io/GeneralPolyshapeTest.java +++ b/src/test/java/org/locationtech/spatial4j/io/GeneralPolyshapeTest.java @@ -19,6 +19,7 @@ import org.junit.Assert; import org.junit.Before; +import org.junit.Ignore; public class GeneralPolyshapeTest extends GeneralReadWriteShapeTest { @@ -61,4 +62,9 @@ public boolean shouldBeEqualAfterRoundTrip() { return false; // the polyline values will be off by a small fraction -- everything is rounded to: Math.round(value * 1e5) } + @Override + @Ignore + public void testEmptyGeometryCollection() throws Exception { + assumeTrue(false); // not supported + } } \ No newline at end of file diff --git a/src/test/java/org/locationtech/spatial4j/io/GeneralReadWriteShapeTest.java b/src/test/java/org/locationtech/spatial4j/io/GeneralReadWriteShapeTest.java index 434933f2e..78cdef522 100644 --- a/src/test/java/org/locationtech/spatial4j/io/GeneralReadWriteShapeTest.java +++ b/src/test/java/org/locationtech/spatial4j/io/GeneralReadWriteShapeTest.java @@ -27,6 +27,7 @@ import org.locationtech.spatial4j.util.GeomBuilder; import java.util.Arrays; +import java.util.Collections; public abstract class GeneralReadWriteShapeTest extends BaseRoundTripTest { @@ -70,7 +71,17 @@ protected void assertRoundTrip(Shape shape, boolean andEquals) throws Exception Assert.assertEquals(shape, out); } } - + + @Test + public void testEmptyPoint() throws Exception { + assertRoundTrip(ctx.getShapeFactory().pointXY(Double.NaN, Double.NaN)); + } + + @Test + public void testEmptyGeometryCollection() throws Exception { + assertRoundTrip(ctx.getShapeFactory().multiShape(Collections.emptyList())); + } + @Test public void testWriteThenReadPoint() throws Exception { assertRoundTrip(point()); diff --git a/src/test/java/org/locationtech/spatial4j/io/WKTWriterTest.java b/src/test/java/org/locationtech/spatial4j/io/WKTWriterTest.java new file mode 100644 index 000000000..a0c45d66f --- /dev/null +++ b/src/test/java/org/locationtech/spatial4j/io/WKTWriterTest.java @@ -0,0 +1,37 @@ +package org.locationtech.spatial4j.io; + +import static org.junit.Assert.assertEquals; +import java.util.ArrayList; +import org.junit.Test; +import org.locationtech.spatial4j.context.SpatialContext; +import org.locationtech.spatial4j.shape.Point; +import org.locationtech.spatial4j.shape.ShapeCollection; + +public class WKTWriterTest { + + private SpatialContext ctx; + + protected WKTWriterTest(SpatialContext ctx) { + this.ctx = ctx; + } + + public WKTWriterTest() { + this(SpatialContext.GEO); + } + + @Test + public void testToStringOnEmptyPoint() throws Exception { + ShapeWriter writer = ctx.getFormats().getWktWriter(); + Point emptyPoint = ctx.makePoint(Double.NaN, Double.NaN); + + assertEquals("POINT EMPTY", writer.toString(emptyPoint)); + } + + @Test + public void testToStringOnEmptyShapeCollection() throws Exception { + ShapeWriter writer = ctx.getFormats().getWktWriter(); + ShapeCollection emptyCollection = ctx.makeCollection(new ArrayList()); + + assertEquals("GEOMETRYCOLLECTION EMPTY", writer.toString(emptyCollection)); + } +}