This is an automated email from the ASF dual-hosted git repository. jiayu pushed a commit to branch geojson-m in repository https://gitbox.apache.org/repos/asf/sedona.git
commit 6b251a596958a603341d002196ceced12b95a69d Author: Jia Yu <ji...@apache.org> AuthorDate: Sat Aug 16 14:50:08 2025 -0700 Add geojson m support in Java --- common/pom.xml | 4 - .../src/main/java/org/wololo/geojson/Feature.java | 64 +++ .../java/org/wololo/geojson/FeatureCollection.java | 38 ++ .../src/main/java/org/wololo/geojson/GeoJSON.java | 75 +++ .../java/org/wololo/geojson/GeoJSONFactory.java | 73 +++ .../src/main/java/org/wololo/geojson/Geometry.java | 49 ++ .../org/wololo/geojson/GeometryCollection.java | 38 ++ .../main/java/org/wololo/geojson/LineString.java | 45 ++ .../java/org/wololo/geojson/MultiLineString.java | 45 ++ .../main/java/org/wololo/geojson/MultiPoint.java | 45 ++ .../main/java/org/wololo/geojson/MultiPolygon.java | 45 ++ common/src/main/java/org/wololo/geojson/Point.java | 45 ++ .../src/main/java/org/wololo/geojson/Polygon.java | 45 ++ .../java/org/wololo/jts2geojson/GeoJSONReader.java | 129 +++++ .../java/org/wololo/jts2geojson/GeoJSONWriter.java | 122 ++++ .../src/test/java/org/wololo/jts2geojson/Data.java | 26 + .../java/org/wololo/jts2geojson/DataFactory.java | 35 ++ .../DeserializeGeometryCollectionTest.java | 70 +++ .../org/wololo/jts2geojson/GeoJSONFactoryTest.java | 140 +++++ .../org/wololo/jts2geojson/GeoJSONReaderTest.java | 291 ++++++++++ .../org/wololo/jts2geojson/GeoJSONWriterTest.java | 636 +++++++++++++++++++++ pom.xml | 12 - spark/common/pom.xml | 10 - spark/spark-3.4/pom.xml | 10 - spark/spark-3.5/pom.xml | 10 - spark/spark-4.0/pom.xml | 10 - 26 files changed, 2056 insertions(+), 56 deletions(-) diff --git a/common/pom.xml b/common/pom.xml index 3108ecf99c..336ad44b97 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -73,10 +73,6 @@ <groupId>org.locationtech.jts</groupId> <artifactId>jts-core</artifactId> </dependency> - <dependency> - <groupId>org.wololo</groupId> - <artifactId>jts2geojson</artifactId> - </dependency> <dependency> <groupId>org.locationtech.spatial4j</groupId> <artifactId>spatial4j</artifactId> diff --git a/common/src/main/java/org/wololo/geojson/Feature.java b/common/src/main/java/org/wololo/geojson/Feature.java new file mode 100644 index 0000000000..86f6de6114 --- /dev/null +++ b/common/src/main/java/org/wololo/geojson/Feature.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.wololo.geojson; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import java.util.Map; + +@JsonPropertyOrder({"type", "id", "geometry", "properties"}) +public class Feature extends GeoJSON { + @JsonInclude(Include.NON_EMPTY) + private final Object id; + + private final Geometry geometry; + private final Map<String, Object> properties; + + public Feature( + @JsonProperty("geometry") Geometry geometry, + @JsonProperty("properties") Map<String, Object> properties) { + this(null, geometry, properties); + } + + @JsonCreator + public Feature( + @JsonProperty("id") Object id, + @JsonProperty("geometry") Geometry geometry, + @JsonProperty("properties") Map<String, Object> properties) { + super(); + this.id = id; + this.geometry = geometry; + this.properties = properties; + } + + public Object getId() { + return id; + } + + public Geometry getGeometry() { + return geometry; + } + + public Map<String, Object> getProperties() { + return properties; + } +} diff --git a/common/src/main/java/org/wololo/geojson/FeatureCollection.java b/common/src/main/java/org/wololo/geojson/FeatureCollection.java new file mode 100644 index 0000000000..df5d58c641 --- /dev/null +++ b/common/src/main/java/org/wololo/geojson/FeatureCollection.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.wololo.geojson; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonPropertyOrder({"type", "features"}) +public class FeatureCollection extends GeoJSON { + private final Feature[] features; + + @JsonCreator + public FeatureCollection(@JsonProperty("features") Feature[] features) { + super(); + this.features = features; + } + + public Feature[] getFeatures() { + return features; + } +} diff --git a/common/src/main/java/org/wololo/geojson/GeoJSON.java b/common/src/main/java/org/wololo/geojson/GeoJSON.java new file mode 100644 index 0000000000..db5a30746f --- /dev/null +++ b/common/src/main/java/org/wololo/geojson/GeoJSON.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.wololo.geojson; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.core.JsonGenerationException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.EXISTING_PROPERTY, + property = "type") +@JsonSubTypes({ + @JsonSubTypes.Type(value = Point.class, name = "Point"), + @JsonSubTypes.Type(value = LineString.class, name = "LineString"), + @JsonSubTypes.Type(value = Polygon.class, name = "Polygon"), + @JsonSubTypes.Type(value = MultiPoint.class, name = "MultiPoint"), + @JsonSubTypes.Type(value = MultiLineString.class, name = "MultiLineString"), + @JsonSubTypes.Type(value = MultiPolygon.class, name = "MultiPolygon"), + @JsonSubTypes.Type(value = Feature.class, name = "Feature"), + @JsonSubTypes.Type(value = FeatureCollection.class, name = "FeatureCollection"), + @JsonSubTypes.Type(value = GeometryCollection.class, name = "GeometryCollection") +}) +public abstract class GeoJSON { + private static final ObjectMapper mapper = new ObjectMapper(); + + @JsonProperty("type") + private String type; + + @JsonCreator + public GeoJSON() { + setType(getClass().getSimpleName()); + } + + public String toString() { + try { + return mapper.writeValueAsString(this); + } catch (JsonGenerationException e) { + return "Unhandled exception occurred when serializing this instance"; + } catch (JsonMappingException e) { + return "Unhandled exception occurred when serializing this instance"; + } catch (IOException e) { + return "Unhandled exception occurred when serializing this instance"; + } + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } +} diff --git a/common/src/main/java/org/wololo/geojson/GeoJSONFactory.java b/common/src/main/java/org/wololo/geojson/GeoJSONFactory.java new file mode 100644 index 0000000000..bb0bc8acdb --- /dev/null +++ b/common/src/main/java/org/wololo/geojson/GeoJSONFactory.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.wololo.geojson; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Map; + +public class GeoJSONFactory { + private static final ObjectMapper mapper = new ObjectMapper(); + + public static GeoJSON create(String json) { + try { + var node = mapper.readTree(json); + var type = node.get("type").asText(); + if (type.equals("FeatureCollection")) return readFeatureCollection(node); + else if (type.equals("Feature")) return readFeature(node); + else return readGeometry(node, type); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static FeatureCollection readFeatureCollection(JsonNode node) + throws JsonParseException, JsonMappingException, IOException, ClassNotFoundException { + var it = node.get("features").iterator(); + var features = new ArrayList<Feature>(); + while (it.hasNext()) features.add(readFeature(it.next())); + return new FeatureCollection(features.toArray(new Feature[features.size()])); + } + + private static Feature readFeature(JsonNode node) + throws JsonParseException, JsonMappingException, IOException, ClassNotFoundException { + var geometryNode = node.get("geometry"); + var javaType = mapper.getTypeFactory().constructMapType(Map.class, String.class, Object.class); + var id = node.get("id"); + Map<String, Object> properties = mapper.readValue(node.get("properties").traverse(), javaType); + var geometry = readGeometry(geometryNode); + return new Feature(id, geometry, properties); + } + + private static Geometry readGeometry(JsonNode node) + throws JsonParseException, JsonMappingException, IOException, ClassNotFoundException { + if (node != null && !node.isNull()) return readGeometry(node, node.get("type").asText()); + else return null; + } + + private static Geometry readGeometry(JsonNode node, String type) + throws JsonParseException, JsonMappingException, IOException, ClassNotFoundException { + return (Geometry) + mapper.readValue(node.traverse(), Class.forName("org.wololo.geojson." + type)); + } +} diff --git a/common/src/main/java/org/wololo/geojson/Geometry.java b/common/src/main/java/org/wololo/geojson/Geometry.java new file mode 100644 index 0000000000..070ec8d23f --- /dev/null +++ b/common/src/main/java/org/wololo/geojson/Geometry.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.wololo.geojson; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.EXISTING_PROPERTY, + property = "type") +@JsonSubTypes({ + @JsonSubTypes.Type(value = Point.class, name = "Point"), + @JsonSubTypes.Type(value = LineString.class, name = "LineString"), + @JsonSubTypes.Type(value = Polygon.class, name = "Polygon"), + @JsonSubTypes.Type(value = MultiPoint.class, name = "MultiPoint"), + @JsonSubTypes.Type(value = MultiLineString.class, name = "MultiLineString"), + @JsonSubTypes.Type(value = MultiPolygon.class, name = "MultiPolygon"), + @JsonSubTypes.Type(value = Feature.class, name = "Feature"), + @JsonSubTypes.Type(value = FeatureCollection.class, name = "FeatureCollection"), + @JsonSubTypes.Type(value = GeometryCollection.class, name = "GeometryCollection") +}) +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonPropertyOrder({"type", "coordinates", "bbox"}) +public abstract class Geometry extends GeoJSON { + @JsonCreator + public Geometry() { + super(); + } +} diff --git a/common/src/main/java/org/wololo/geojson/GeometryCollection.java b/common/src/main/java/org/wololo/geojson/GeometryCollection.java new file mode 100644 index 0000000000..f22e6c114b --- /dev/null +++ b/common/src/main/java/org/wololo/geojson/GeometryCollection.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.wololo.geojson; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonPropertyOrder({"type", "geometries"}) +public class GeometryCollection extends Geometry { + private final Geometry[] geometries; + + @JsonCreator + public GeometryCollection(@JsonProperty("geometries") Geometry[] geometries) { + super(); + this.geometries = geometries; + } + + public Geometry[] getGeometries() { + return geometries; + } +} diff --git a/common/src/main/java/org/wololo/geojson/LineString.java b/common/src/main/java/org/wololo/geojson/LineString.java new file mode 100644 index 0000000000..d798a47cb5 --- /dev/null +++ b/common/src/main/java/org/wololo/geojson/LineString.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.wololo.geojson; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonInclude(Include.NON_NULL) +public class LineString extends Geometry { + private final double[][] coordinates; + private final double[] bbox; + + @JsonCreator + public LineString(@JsonProperty("coordinates") double[][] coordinates) { + super(); + this.coordinates = coordinates; + this.bbox = null; + } + + public double[][] getCoordinates() { + return coordinates; + } + + public double[] getBbox() { + return bbox; + } +} diff --git a/common/src/main/java/org/wololo/geojson/MultiLineString.java b/common/src/main/java/org/wololo/geojson/MultiLineString.java new file mode 100644 index 0000000000..1c89ad406c --- /dev/null +++ b/common/src/main/java/org/wololo/geojson/MultiLineString.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.wololo.geojson; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonInclude(Include.NON_NULL) +public class MultiLineString extends Geometry { + private final double[][][] coordinates; + private final double[] bbox; + + @JsonCreator + public MultiLineString(@JsonProperty("coordinates") double[][][] coordinates) { + super(); + this.coordinates = coordinates; + this.bbox = null; + } + + public double[][][] getCoordinates() { + return coordinates; + } + + public double[] getBbox() { + return bbox; + } +} diff --git a/common/src/main/java/org/wololo/geojson/MultiPoint.java b/common/src/main/java/org/wololo/geojson/MultiPoint.java new file mode 100644 index 0000000000..495e77cf70 --- /dev/null +++ b/common/src/main/java/org/wololo/geojson/MultiPoint.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.wololo.geojson; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonInclude(Include.NON_NULL) +public class MultiPoint extends Geometry { + private final double[][] coordinates; + private final double[] bbox; + + @JsonCreator + public MultiPoint(@JsonProperty("coordinates") double[][] coordinates) { + super(); + this.coordinates = coordinates; + this.bbox = null; + } + + public double[][] getCoordinates() { + return coordinates; + } + + public double[] getBbox() { + return bbox; + } +} diff --git a/common/src/main/java/org/wololo/geojson/MultiPolygon.java b/common/src/main/java/org/wololo/geojson/MultiPolygon.java new file mode 100644 index 0000000000..3513ff95a2 --- /dev/null +++ b/common/src/main/java/org/wololo/geojson/MultiPolygon.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.wololo.geojson; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonInclude(Include.NON_NULL) +public class MultiPolygon extends Geometry { + private final double[][][][] coordinates; + private final double[] bbox; + + @JsonCreator + public MultiPolygon(@JsonProperty("coordinates") double[][][][] coordinates) { + super(); + this.coordinates = coordinates; + this.bbox = null; + } + + public double[][][][] getCoordinates() { + return coordinates; + } + + public double[] getBbox() { + return bbox; + } +} diff --git a/common/src/main/java/org/wololo/geojson/Point.java b/common/src/main/java/org/wololo/geojson/Point.java new file mode 100644 index 0000000000..b2cf6b75b3 --- /dev/null +++ b/common/src/main/java/org/wololo/geojson/Point.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.wololo.geojson; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonInclude(Include.NON_NULL) +public class Point extends Geometry { + private final double[] coordinates; + private final double[] bbox; + + @JsonCreator + public Point(@JsonProperty("coordinates") double[] coordinates) { + super(); + this.coordinates = coordinates; + this.bbox = null; + } + + public double[] getCoordinates() { + return coordinates; + } + + public double[] getBbox() { + return bbox; + } +} diff --git a/common/src/main/java/org/wololo/geojson/Polygon.java b/common/src/main/java/org/wololo/geojson/Polygon.java new file mode 100644 index 0000000000..2ee5660868 --- /dev/null +++ b/common/src/main/java/org/wololo/geojson/Polygon.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.wololo.geojson; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonInclude(Include.NON_NULL) +public class Polygon extends Geometry { + private final double[][][] coordinates; + private final double[] bbox; + + @JsonCreator + public Polygon(@JsonProperty("coordinates") double[][][] coordinates) { + super(); + this.coordinates = coordinates; + this.bbox = null; + } + + public double[][][] getCoordinates() { + return coordinates; + } + + public double[] getBbox() { + return bbox; + } +} diff --git a/common/src/main/java/org/wololo/jts2geojson/GeoJSONReader.java b/common/src/main/java/org/wololo/jts2geojson/GeoJSONReader.java new file mode 100644 index 0000000000..b7a6b7a8d2 --- /dev/null +++ b/common/src/main/java/org/wololo/jts2geojson/GeoJSONReader.java @@ -0,0 +1,129 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.wololo.jts2geojson; + +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.CoordinateXYZM; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.geom.LinearRing; +import org.locationtech.jts.geom.PrecisionModel; +import org.wololo.geojson.*; + +public class GeoJSONReader { + static final GeometryFactory FACTORY = + new GeometryFactory(new PrecisionModel(PrecisionModel.FLOATING)); + + public Geometry read(String json) { + return read(json, null); + } + + public Geometry read(String json, GeometryFactory geomFactory) { + return read(GeoJSONFactory.create(json), geomFactory); + } + + public Geometry read(GeoJSON geoJSON) { + return read(geoJSON, null); + } + + public Geometry read(GeoJSON geoJSON, GeometryFactory geomFactory) { + var factory = geomFactory != null ? geomFactory : FACTORY; + if (geoJSON instanceof Point) return convert((Point) geoJSON, factory); + else if (geoJSON instanceof LineString) return convert((LineString) geoJSON, factory); + else if (geoJSON instanceof Polygon) return convert((Polygon) geoJSON, factory); + else if (geoJSON instanceof MultiPoint) return convert((MultiPoint) geoJSON, factory); + else if (geoJSON instanceof MultiLineString) return convert((MultiLineString) geoJSON, factory); + else if (geoJSON instanceof MultiPolygon) return convert((MultiPolygon) geoJSON, factory); + else if (geoJSON instanceof GeometryCollection) + return convert((GeometryCollection) geoJSON, factory); + else throw new UnsupportedOperationException(); + } + + Geometry convert(Point point, GeometryFactory factory) { + return factory.createPoint(convert(point.getCoordinates())); + } + + Geometry convert(MultiPoint multiPoint, GeometryFactory factory) { + return factory.createMultiPointFromCoords(convert(multiPoint.getCoordinates())); + } + + Geometry convert(LineString lineString, GeometryFactory factory) { + return factory.createLineString(convert(lineString.getCoordinates())); + } + + Geometry convert(MultiLineString multiLineString, GeometryFactory factory) { + var size = multiLineString.getCoordinates().length; + var lineStrings = new org.locationtech.jts.geom.LineString[size]; + for (int i = 0; i < size; i++) + lineStrings[i] = factory.createLineString(convert(multiLineString.getCoordinates()[i])); + return factory.createMultiLineString(lineStrings); + } + + Geometry convert(Polygon polygon, GeometryFactory factory) { + return convertToPolygon(polygon.getCoordinates(), factory); + } + + org.locationtech.jts.geom.Polygon convertToPolygon( + double[][][] coordinates, GeometryFactory factory) { + var shell = factory.createLinearRing(convert(coordinates[0])); + if (coordinates.length > 1) { + var size = coordinates.length - 1; + var holes = new LinearRing[size]; + for (var i = 0; i < size; i++) + holes[i] = factory.createLinearRing(convert(coordinates[i + 1])); + return factory.createPolygon(shell, holes); + } else { + return factory.createPolygon(shell); + } + } + + Geometry convert(MultiPolygon multiPolygon, GeometryFactory factory) { + var size = multiPolygon.getCoordinates().length; + var polygons = new org.locationtech.jts.geom.Polygon[size]; + for (int i = 0; i < size; i++) + polygons[i] = convertToPolygon(multiPolygon.getCoordinates()[i], factory); + return factory.createMultiPolygon(polygons); + } + + Geometry convert(GeometryCollection gc, GeometryFactory factory) { + var size = gc.getGeometries().length; + var geometries = new Geometry[size]; + for (var i = 0; i < size; i++) geometries[i] = read(gc.getGeometries()[i], factory); + return factory.createGeometryCollection(geometries); + } + + Coordinate convert(double[] c) { + if (c.length == 2) { + return new Coordinate(c[0], c[1]); + } else if (c.length == 3) { + return new Coordinate(c[0], c[1], c[2]); + } else if (c.length == 4) { + // Handle XYZM coordinates (4 values) + return new CoordinateXYZM(c[0], c[1], c[2], c[3]); + } else { + return new Coordinate(c[0], c[1]); + } + } + + Coordinate[] convert(double[][] ca) { + var coordinates = new Coordinate[ca.length]; + for (int i = 0; i < ca.length; i++) coordinates[i] = convert(ca[i]); + return coordinates; + } +} diff --git a/common/src/main/java/org/wololo/jts2geojson/GeoJSONWriter.java b/common/src/main/java/org/wololo/jts2geojson/GeoJSONWriter.java new file mode 100644 index 0000000000..a407f7ec32 --- /dev/null +++ b/common/src/main/java/org/wololo/jts2geojson/GeoJSONWriter.java @@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.wololo.jts2geojson; + +import java.util.List; +import org.locationtech.jts.geom.*; +import org.wololo.geojson.Feature; + +public class GeoJSONWriter { + + static final GeoJSONReader reader = new GeoJSONReader(); + + public org.wololo.geojson.Geometry write(Geometry geometry) { + Class<? extends Geometry> c = geometry.getClass(); + if (c.equals(Point.class)) return convert((Point) geometry); + else if (c.equals(LineString.class)) return convert((LineString) geometry); + else if (c.equals(LinearRing.class)) return convert((LinearRing) geometry); + else if (c.equals(Polygon.class)) return convert((Polygon) geometry); + else if (c.equals(MultiPoint.class)) return convert((MultiPoint) geometry); + else if (c.equals(MultiLineString.class)) return convert((MultiLineString) geometry); + else if (c.equals(MultiPolygon.class)) return convert((MultiPolygon) geometry); + else if (c.equals(GeometryCollection.class)) return convert((GeometryCollection) geometry); + else throw new UnsupportedOperationException(); + } + + public org.wololo.geojson.FeatureCollection write(List<Feature> features) { + var size = features.size(); + var featuresJson = new Feature[size]; + for (var i = 0; i < size; i++) featuresJson[i] = features.get(i); + return new org.wololo.geojson.FeatureCollection(featuresJson); + } + + org.wololo.geojson.Point convert(Point point) { + return new org.wololo.geojson.Point(convert(point.getCoordinate())); + } + + org.wololo.geojson.MultiPoint convert(MultiPoint multiPoint) { + return new org.wololo.geojson.MultiPoint(convert(multiPoint.getCoordinates())); + } + + org.wololo.geojson.LineString convert(LineString lineString) { + return new org.wololo.geojson.LineString(convert(lineString.getCoordinates())); + } + + org.wololo.geojson.LineString convert(LinearRing ringString) { + return new org.wololo.geojson.LineString(convert(ringString.getCoordinates())); + } + + org.wololo.geojson.MultiLineString convert(MultiLineString multiLineString) { + var size = multiLineString.getNumGeometries(); + var lineStrings = new double[size][][]; + for (int i = 0; i < size; i++) + lineStrings[i] = convert(multiLineString.getGeometryN(i).getCoordinates()); + return new org.wololo.geojson.MultiLineString(lineStrings); + } + + org.wololo.geojson.Polygon convert(Polygon polygon) { + var size = polygon.getNumInteriorRing() + 1; + var rings = new double[size][][]; + rings[0] = convert(polygon.getExteriorRing().getCoordinates()); + for (int i = 0; i < size - 1; i++) + rings[i + 1] = convert(polygon.getInteriorRingN(i).getCoordinates()); + return new org.wololo.geojson.Polygon(rings); + } + + org.wololo.geojson.MultiPolygon convert(MultiPolygon multiPolygon) { + var size = multiPolygon.getNumGeometries(); + var polygons = new double[size][][][]; + for (int i = 0; i < size; i++) + polygons[i] = convert((Polygon) multiPolygon.getGeometryN(i)).getCoordinates(); + return new org.wololo.geojson.MultiPolygon(polygons); + } + + org.wololo.geojson.GeometryCollection convert(GeometryCollection gc) { + var size = gc.getNumGeometries(); + var geometries = new org.wololo.geojson.Geometry[size]; + for (int i = 0; i < size; i++) geometries[i] = write((Geometry) gc.getGeometryN(i)); + return new org.wololo.geojson.GeometryCollection(geometries); + } + + double[] convert(Coordinate coordinate) { + boolean hasZ = !Double.isNaN(coordinate.getZ()); + boolean hasM = !Double.isNaN(coordinate.getM()); + + if (!hasZ && !hasM) { + // XY case - only 2D coordinates + return new double[] {coordinate.x, coordinate.y}; + } else if (hasZ && !hasM) { + // XYZ case - 3D coordinates without measure + return new double[] {coordinate.x, coordinate.y, coordinate.getZ()}; + } else if (hasZ && hasM) { + // XYZM case - 3D coordinates with measure + return new double[] {coordinate.x, coordinate.y, coordinate.getZ(), coordinate.getM()}; + } else { + // XYM case - We don't support this directly + throw new UnsupportedOperationException( + "XYM coordinates are not supported. Please convert to XYZM coordinates by adding a Z value."); + } + } + + double[][] convert(Coordinate[] coordinates) { + var array = new double[coordinates.length][]; + for (int i = 0; i < coordinates.length; i++) array[i] = convert(coordinates[i]); + return array; + } +} diff --git a/common/src/test/java/org/wololo/jts2geojson/Data.java b/common/src/test/java/org/wololo/jts2geojson/Data.java new file mode 100644 index 0000000000..396067ddcc --- /dev/null +++ b/common/src/test/java/org/wololo/jts2geojson/Data.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.wololo.jts2geojson; + +import org.wololo.geojson.GeoJSON; + +class Data { + + public GeoJSON geometry; +} diff --git a/common/src/test/java/org/wololo/jts2geojson/DataFactory.java b/common/src/test/java/org/wololo/jts2geojson/DataFactory.java new file mode 100644 index 0000000000..357f56ebdc --- /dev/null +++ b/common/src/test/java/org/wololo/jts2geojson/DataFactory.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.wololo.jts2geojson; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +class DataFactory { + + private static final ObjectMapper mapper = new ObjectMapper(); + + public static Data fromJson(String json) { + try { + return mapper.readerFor(Data.class).readValue(json); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } +} diff --git a/common/src/test/java/org/wololo/jts2geojson/DeserializeGeometryCollectionTest.java b/common/src/test/java/org/wololo/jts2geojson/DeserializeGeometryCollectionTest.java new file mode 100644 index 0000000000..4c18a060fe --- /dev/null +++ b/common/src/test/java/org/wololo/jts2geojson/DeserializeGeometryCollectionTest.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.wololo.jts2geojson; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.wololo.geojson.GeoJSON; +import org.wololo.geojson.GeoJSONFactory; +import org.wololo.geojson.Geometry; +import org.wololo.geojson.GeometryCollection; +import org.wololo.geojson.Point; + +public class DeserializeGeometryCollectionTest { + + @Test + public void testDeserializeGeometryCollectionSuccessfully() { + // Setup test data + String geoJSON = + "{\"type\": \"GeometryCollection\", \"geometries\": [{\"type\": \"Point\", \"coordinates\": [1.1, 2.2]}]}"; + + // Execute test + GeoJSON json = GeoJSONFactory.create(geoJSON); + + // Verify results + assertTrue("Should be a GeometryCollection", json instanceof GeometryCollection); + GeometryCollection gc = (GeometryCollection) json; + assertTrue("Should have geometries", gc.getGeometries().length > 0); + + Geometry g = gc.getGeometries()[0]; + assertNotNull("Geometry should not be null", g); + assertTrue("Should be a Point", g instanceof Point); + + Point point = (Point) g; + double[] coordinates = point.getCoordinates(); + assertEquals("X coordinate should match", 1.1, coordinates[0], 0.000001); + assertEquals("Y coordinate should match", 2.2, coordinates[1], 0.000001); + } + + @Test + public void testDeserializeGeometryCollectionSuccessfullyIfUsingObjectMapper() { + // Setup test data + String geoJSON = + "{ \"geometry\" : {\"type\": \"GeometryCollection\", \"geometries\": [{\"type\": \"Point\", \"coordinates\": [1.1, 2.2]}]}}"; + + // Execute test + Data value = DataFactory.fromJson(geoJSON); + + // Verify results + assertTrue("Should be a Data object", value instanceof Data); + } +} diff --git a/common/src/test/java/org/wololo/jts2geojson/GeoJSONFactoryTest.java b/common/src/test/java/org/wololo/jts2geojson/GeoJSONFactoryTest.java new file mode 100644 index 0000000000..b1dd3676c9 --- /dev/null +++ b/common/src/test/java/org/wololo/jts2geojson/GeoJSONFactoryTest.java @@ -0,0 +1,140 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.wololo.jts2geojson; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.HashMap; +import org.junit.Test; +import org.wololo.geojson.Feature; +import org.wololo.geojson.FeatureCollection; +import org.wololo.geojson.GeoJSON; +import org.wololo.geojson.GeoJSONFactory; +import org.wololo.geojson.Geometry; +import org.wololo.geojson.Point; + +public class GeoJSONFactoryTest { + + @Test + public void testReadMissingGeometryFieldAsNull() { + String json = "{\"type\":\"Feature\",\"properties\":{\"test\":1}}"; + String expected = "{\"type\":\"Feature\",\"geometry\":null,\"properties\":{\"test\":1}}"; + + GeoJSON geojson = GeoJSONFactory.create(json); + assertEquals(expected, geojson.toString()); + } + + @Test + public void testIdenticalToProgrammaticallyCreatedFeature() { + Geometry geometry = new Point(new double[] {1, 1}); + HashMap<String, Object> properties = new HashMap<>(); + properties.put("test", 1); + Feature feature = new Feature(geometry, properties); + + String expected = + "{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[1.0,1.0]},\"properties\":{\"test\":1}}"; + + String json = feature.toString(); + assertEquals(expected, json); + + GeoJSON geoJSON = GeoJSONFactory.create(json); + assertEquals(expected, geoJSON.toString()); + } + + @Test + public void testIdenticalToProgrammaticallyCreatedFeatureWithId() { + Geometry geometry = new Point(new double[] {1, 1}); + HashMap<String, Object> properties = new HashMap<>(); + properties.put("test", 1); + Feature feature = new Feature(1, geometry, properties); + + String expected = + "{\"type\":\"Feature\",\"id\":1,\"geometry\":{\"type\":\"Point\",\"coordinates\":[1.0,1.0]},\"properties\":{\"test\":1}}"; + + String json = feature.toString(); + assertEquals(expected, json); + + GeoJSON geoJSON = GeoJSONFactory.create(json); + assertEquals(expected, geoJSON.toString()); + } + + @Test + public void testIdenticalToProgrammaticallyCreatedFeatureWithoutGeometry() { + HashMap<String, Object> properties = new HashMap<>(); + properties.put("test", 1); + Feature feature = new Feature(null, properties); + + String expected = "{\"type\":\"Feature\",\"geometry\":null,\"properties\":{\"test\":1}}"; + + String json = feature.toString(); + assertEquals(expected, json); + + GeoJSON geoJSON = GeoJSONFactory.create(json); + assertEquals(expected, geoJSON.toString()); + } + + @Test + public void testIdenticalToProgrammaticallyCreatedFeatureCollection() { + Geometry geometry = new Point(new double[] {1, 1}); + HashMap<String, Object> properties = new HashMap<>(); + properties.put("test", 1); + Feature feature = new Feature(geometry, properties); + + String expected = + "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[1.0,1.0]},\"properties\":{\"test\":1}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[1.0,1.0]},\"properties\":{\"test\":1}}]}"; + + FeatureCollection fc = new FeatureCollection(new Feature[] {feature, feature}); + assertEquals(expected, fc.toString()); + } + + @Test + public void testTakeCareOfBboxProperty() { + String geoJSON = + "{\"type\": \"FeatureCollection\", \"features\": [{\"type\": \"Feature\", \"id\": 1, \"geometry\": {\"type\": \"Point\", \"coordinates\": [-8.311419016226296, 53.894485921596285], \"bbox\": [-8.311419016226296, 53.894485921596285, -8.311419016226296, 53.894485921596285] }, \"properties\": {\"FID\": 335, \"PLAN_REF\": \"151\", \"APP_TYPE\": \"RETENTION\", \"LOCATION\": \"Knockglass, Ballinameen, Co. Roscommon.\", \"REC_DATE\": \"05/01/2015\", \"DESCRIPT\": \"Of entrance to existin [...] + GeoJSON json = GeoJSONFactory.create(geoJSON); + assertTrue(json instanceof FeatureCollection); + FeatureCollection fc = (FeatureCollection) json; + assertTrue(fc.getFeatures().length > 0); + Feature f = fc.getFeatures()[0]; + assertNotNull(f.getGeometry()); + assertTrue(f.getGeometry() instanceof Point); + Point point = (Point) f.getGeometry(); + assertNotNull(point.getBbox()); + + double[] expected = { + -8.311419016226296, 53.894485921596285, -8.311419016226296, 53.894485921596285 + }; + double[] actual = point.getBbox(); + assertEquals(expected.length, actual.length); + for (int i = 0; i < expected.length; i++) { + assertEquals(expected[i], actual[i], 1e-10); + } + } + + @Test + public void testTakeCareOfMultiPointBboxProperty() { + String geoJSON = + "{\"type\": \"FeatureCollection\", \"features\": [{\"type\": \"Feature\", \"id\": 1, \"geometry\": {\"type\": \"MultiPoint\", \"coordinates\": [[-8.311419016226296, 53.894485921596285]], \"bbox\": [-8.311419016226296, 53.894485921596285, -8.311419016226296, 53.894485921596285] }, \"properties\": {\"FID\": 335, \"PLAN_REF\": \"151\", \"APP_TYPE\": \"RETENTION\", \"LOCATION\": \"Knockglass, Ballinameen, Co. Roscommon.\", \"REC_DATE\": \"05/01/2015\", \"DESCRIPT\": \"Of entrance to [...] + GeoJSON json = GeoJSONFactory.create(geoJSON); + assertTrue(json instanceof FeatureCollection); + FeatureCollection fc = (FeatureCollection) json; + } +} diff --git a/common/src/test/java/org/wololo/jts2geojson/GeoJSONReaderTest.java b/common/src/test/java/org/wololo/jts2geojson/GeoJSONReaderTest.java new file mode 100644 index 0000000000..a4cd594227 --- /dev/null +++ b/common/src/test/java/org/wololo/jts2geojson/GeoJSONReaderTest.java @@ -0,0 +1,291 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.wololo.jts2geojson; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.locationtech.jts.geom.CoordinateXYZM; +import org.locationtech.jts.geom.Geometry; +import org.wololo.geojson.Point; + +public class GeoJSONReaderTest { + + @Test + public void testPointConversion() { + GeoJSONReader reader = new GeoJSONReader(); + double[] coords = new double[] {1.0, 2.0}; + Point point = new Point(coords); + Geometry geometry = reader.read(point); + assertEquals(1.0, geometry.getCoordinate().x, 0.0); + assertEquals(2.0, geometry.getCoordinate().y, 0.0); + } + + @Test + public void testMultiPointConversion() { + GeoJSONReader reader = new GeoJSONReader(); + double[][] coords = new double[][] {{1.0, 2.0}, {3.0, 4.0}}; + org.wololo.geojson.MultiPoint multiPoint = new org.wololo.geojson.MultiPoint(coords); + Geometry geometry = reader.read(multiPoint); + assertEquals(1.0, geometry.getCoordinates()[0].x, 0.0); + assertEquals(2.0, geometry.getCoordinates()[0].y, 0.0); + assertEquals(3.0, geometry.getCoordinates()[1].x, 0.0); + assertEquals(4.0, geometry.getCoordinates()[1].y, 0.0); + } + + @Test + public void testPointWithZConversion() { + GeoJSONReader reader = new GeoJSONReader(); + double[] coords = new double[] {1.0, 2.0, 3.0}; + Point point = new Point(coords); + Geometry geometry = reader.read(point); + assertEquals(1.0, geometry.getCoordinate().x, 0.0); + assertEquals(2.0, geometry.getCoordinate().y, 0.0); + assertEquals(3.0, geometry.getCoordinate().getZ(), 0.0); + } + + @Test + public void testMultiPointWithZConversion() { + GeoJSONReader reader = new GeoJSONReader(); + double[][] coords = new double[][] {{1.0, 2.0, 3.0}, {4.0, 5.0, 6.0}}; + org.wololo.geojson.MultiPoint multiPoint = new org.wololo.geojson.MultiPoint(coords); + Geometry geometry = reader.read(multiPoint); + assertEquals(1.0, geometry.getCoordinates()[0].x, 0.0); + assertEquals(2.0, geometry.getCoordinates()[0].y, 0.0); + assertEquals(3.0, geometry.getCoordinates()[0].getZ(), 0.0); + assertEquals(4.0, geometry.getCoordinates()[1].x, 0.0); + assertEquals(5.0, geometry.getCoordinates()[1].y, 0.0); + assertEquals(6.0, geometry.getCoordinates()[1].getZ(), 0.0); + } + + @Test + public void testPointWithXYZMConversion() { + GeoJSONReader reader = new GeoJSONReader(); + double[] coords = new double[] {1.0, 2.0, 3.0, 4.0}; + Point point = new Point(coords); + Geometry geometry = reader.read(point); + + assertEquals(1.0, geometry.getCoordinate().x, 0.0); + assertEquals(2.0, geometry.getCoordinate().y, 0.0); + assertEquals(3.0, geometry.getCoordinate().getZ(), 0.0); + assertEquals(4.0, geometry.getCoordinate().getM(), 0.0); + + // Verify the coordinate is an XYZM coordinate + assertTrue(geometry.getCoordinate() instanceof CoordinateXYZM); + } + + @Test + public void testMultiPointWithXYZMConversion() { + GeoJSONReader reader = new GeoJSONReader(); + double[][] coords = new double[][] {{1.0, 2.0, 3.0, 4.0}, {5.0, 6.0, 7.0, 8.0}}; + org.wololo.geojson.MultiPoint multiPoint = new org.wololo.geojson.MultiPoint(coords); + Geometry geometry = reader.read(multiPoint); + + assertEquals(1.0, geometry.getCoordinates()[0].x, 0.0); + assertEquals(2.0, geometry.getCoordinates()[0].y, 0.0); + assertEquals(3.0, geometry.getCoordinates()[0].getZ(), 0.0); + assertEquals(4.0, geometry.getCoordinates()[0].getM(), 0.0); + + assertEquals(5.0, geometry.getCoordinates()[1].x, 0.0); + assertEquals(6.0, geometry.getCoordinates()[1].y, 0.0); + assertEquals(7.0, geometry.getCoordinates()[1].getZ(), 0.0); + assertEquals(8.0, geometry.getCoordinates()[1].getM(), 0.0); + + // Verify the coordinates are XYZM coordinates + assertTrue(geometry.getCoordinates()[0] instanceof CoordinateXYZM); + assertTrue(geometry.getCoordinates()[1] instanceof CoordinateXYZM); + } + + @Test + public void testLineStringConversion() { + GeoJSONReader reader = new GeoJSONReader(); + double[][] coords = new double[][] {{1.0, 2.0}, {3.0, 4.0}, {5.0, 6.0}}; + org.wololo.geojson.LineString lineString = new org.wololo.geojson.LineString(coords); + Geometry geometry = reader.read(lineString); + + assertEquals("LineString", geometry.getGeometryType()); + assertEquals(3, geometry.getCoordinates().length); + assertEquals(1.0, geometry.getCoordinates()[0].x, 0.0); + assertEquals(2.0, geometry.getCoordinates()[0].y, 0.0); + assertEquals(3.0, geometry.getCoordinates()[1].x, 0.0); + assertEquals(4.0, geometry.getCoordinates()[1].y, 0.0); + assertEquals(5.0, geometry.getCoordinates()[2].x, 0.0); + assertEquals(6.0, geometry.getCoordinates()[2].y, 0.0); + } + + @Test + public void testLineStringWithZConversion() { + GeoJSONReader reader = new GeoJSONReader(); + double[][] coords = new double[][] {{1.0, 2.0, 3.0}, {4.0, 5.0, 6.0}, {7.0, 8.0, 9.0}}; + org.wololo.geojson.LineString lineString = new org.wololo.geojson.LineString(coords); + Geometry geometry = reader.read(lineString); + + assertEquals("LineString", geometry.getGeometryType()); + assertEquals(3, geometry.getCoordinates().length); + assertEquals(1.0, geometry.getCoordinates()[0].x, 0.0); + assertEquals(2.0, geometry.getCoordinates()[0].y, 0.0); + assertEquals(3.0, geometry.getCoordinates()[0].getZ(), 0.0); + assertEquals(4.0, geometry.getCoordinates()[1].x, 0.0); + assertEquals(5.0, geometry.getCoordinates()[1].y, 0.0); + assertEquals(6.0, geometry.getCoordinates()[1].getZ(), 0.0); + assertEquals(7.0, geometry.getCoordinates()[2].x, 0.0); + assertEquals(8.0, geometry.getCoordinates()[2].y, 0.0); + assertEquals(9.0, geometry.getCoordinates()[2].getZ(), 0.0); + } + + @Test + public void testPolygonConversion() { + GeoJSONReader reader = new GeoJSONReader(); + // Polygon with one exterior ring + double[][][] coords = + new double[][][] {{{0.0, 0.0}, {0.0, 10.0}, {10.0, 10.0}, {10.0, 0.0}, {0.0, 0.0}}}; + org.wololo.geojson.Polygon polygon = new org.wololo.geojson.Polygon(coords); + Geometry geometry = reader.read(polygon); + + assertEquals("Polygon", geometry.getGeometryType()); + assertEquals(5, geometry.getCoordinates().length); + assertEquals(0.0, geometry.getCoordinates()[0].x, 0.0); + assertEquals(0.0, geometry.getCoordinates()[0].y, 0.0); + assertEquals(0.0, geometry.getCoordinates()[1].x, 0.0); + assertEquals(10.0, geometry.getCoordinates()[1].y, 0.0); + } + + @Test + public void testPolygonWithHoleConversion() { + GeoJSONReader reader = new GeoJSONReader(); + // Polygon with exterior ring and one hole + double[][][] coords = + new double[][][] { + {{0.0, 0.0}, {0.0, 10.0}, {10.0, 10.0}, {10.0, 0.0}, {0.0, 0.0}}, + {{2.0, 2.0}, {2.0, 8.0}, {8.0, 8.0}, {8.0, 2.0}, {2.0, 2.0}} + }; + org.wololo.geojson.Polygon polygon = new org.wololo.geojson.Polygon(coords); + Geometry geometry = reader.read(polygon); + + assertEquals("Polygon", geometry.getGeometryType()); + assertEquals(1, geometry.getNumGeometries()); + assertEquals(1, ((org.locationtech.jts.geom.Polygon) geometry).getNumInteriorRing()); + + // Check exterior ring + assertEquals( + 5, + ((org.locationtech.jts.geom.Polygon) geometry).getExteriorRing().getCoordinates().length); + + // Check interior ring + assertEquals( + 5, + ((org.locationtech.jts.geom.Polygon) geometry).getInteriorRingN(0).getCoordinates().length); + assertEquals( + 2.0, + ((org.locationtech.jts.geom.Polygon) geometry).getInteriorRingN(0).getCoordinates()[0].x, + 0.0); + assertEquals( + 2.0, + ((org.locationtech.jts.geom.Polygon) geometry).getInteriorRingN(0).getCoordinates()[0].y, + 0.0); + } + + @Test + public void testMultiLineStringConversion() { + GeoJSONReader reader = new GeoJSONReader(); + double[][][] coords = + new double[][][] { + {{1.0, 2.0}, {3.0, 4.0}}, + {{5.0, 6.0}, {7.0, 8.0}} + }; + org.wololo.geojson.MultiLineString multiLineString = + new org.wololo.geojson.MultiLineString(coords); + Geometry geometry = reader.read(multiLineString); + + assertEquals("MultiLineString", geometry.getGeometryType()); + assertEquals(2, geometry.getNumGeometries()); + + // Check first linestring + assertEquals(2, geometry.getGeometryN(0).getCoordinates().length); + assertEquals(1.0, geometry.getGeometryN(0).getCoordinates()[0].x, 0.0); + assertEquals(2.0, geometry.getGeometryN(0).getCoordinates()[0].y, 0.0); + + // Check second linestring + assertEquals(2, geometry.getGeometryN(1).getCoordinates().length); + assertEquals(5.0, geometry.getGeometryN(1).getCoordinates()[0].x, 0.0); + assertEquals(6.0, geometry.getGeometryN(1).getCoordinates()[0].y, 0.0); + } + + @Test + public void testMultiPolygonConversion() { + GeoJSONReader reader = new GeoJSONReader(); + double[][][][] coords = + new double[][][][] { + // First polygon + {{{0.0, 0.0}, {0.0, 5.0}, {5.0, 5.0}, {5.0, 0.0}, {0.0, 0.0}}}, + // Second polygon + {{{10.0, 10.0}, {10.0, 15.0}, {15.0, 15.0}, {15.0, 10.0}, {10.0, 10.0}}} + }; + org.wololo.geojson.MultiPolygon multiPolygon = new org.wololo.geojson.MultiPolygon(coords); + Geometry geometry = reader.read(multiPolygon); + + assertEquals("MultiPolygon", geometry.getGeometryType()); + assertEquals(2, geometry.getNumGeometries()); + + // Check first polygon + assertEquals(5, geometry.getGeometryN(0).getCoordinates().length); + assertEquals(0.0, geometry.getGeometryN(0).getCoordinates()[0].x, 0.0); + assertEquals(0.0, geometry.getGeometryN(0).getCoordinates()[0].y, 0.0); + + // Check second polygon + assertEquals(5, geometry.getGeometryN(1).getCoordinates().length); + assertEquals(10.0, geometry.getGeometryN(1).getCoordinates()[0].x, 0.0); + assertEquals(10.0, geometry.getGeometryN(1).getCoordinates()[0].y, 0.0); + } + + @Test + public void testGeometryCollectionConversion() { + GeoJSONReader reader = new GeoJSONReader(); + + // Create a Point + double[] pointCoords = new double[] {1.0, 2.0}; + org.wololo.geojson.Point point = new org.wololo.geojson.Point(pointCoords); + + // Create a LineString + double[][] lineCoords = new double[][] {{3.0, 4.0}, {5.0, 6.0}}; + org.wololo.geojson.LineString lineString = new org.wololo.geojson.LineString(lineCoords); + + // Create a GeometryCollection with these geometries + org.wololo.geojson.Geometry[] geometries = + new org.wololo.geojson.Geometry[] {point, lineString}; + org.wololo.geojson.GeometryCollection geometryCollection = + new org.wololo.geojson.GeometryCollection(geometries); + + Geometry geometry = reader.read(geometryCollection); + + assertEquals("GeometryCollection", geometry.getGeometryType()); + assertEquals(2, geometry.getNumGeometries()); + + // Check first geometry (point) + assertEquals("Point", geometry.getGeometryN(0).getGeometryType()); + assertEquals(1.0, geometry.getGeometryN(0).getCoordinate().x, 0.0); + assertEquals(2.0, geometry.getGeometryN(0).getCoordinate().y, 0.0); + + // Check second geometry (linestring) + assertEquals("LineString", geometry.getGeometryN(1).getGeometryType()); + assertEquals(3.0, geometry.getGeometryN(1).getCoordinates()[0].x, 0.0); + assertEquals(4.0, geometry.getGeometryN(1).getCoordinates()[0].y, 0.0); + } +} diff --git a/common/src/test/java/org/wololo/jts2geojson/GeoJSONWriterTest.java b/common/src/test/java/org/wololo/jts2geojson/GeoJSONWriterTest.java new file mode 100644 index 0000000000..28e8038582 --- /dev/null +++ b/common/src/test/java/org/wololo/jts2geojson/GeoJSONWriterTest.java @@ -0,0 +1,636 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.wololo.jts2geojson; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import org.junit.Test; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.CoordinateXYM; +import org.locationtech.jts.geom.CoordinateXYZM; +import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.geom.LineString; +import org.locationtech.jts.geom.LinearRing; +import org.locationtech.jts.geom.MultiLineString; +import org.locationtech.jts.geom.MultiPoint; +import org.locationtech.jts.geom.MultiPolygon; +import org.locationtech.jts.geom.Point; +import org.locationtech.jts.geom.Polygon; +import org.wololo.geojson.Feature; +import org.wololo.geojson.FeatureCollection; + +public class GeoJSONWriterTest { + + @Test + public void testPointConversion() { + // Setup + GeoJSONReader reader = new GeoJSONReader(); + GeoJSONWriter writer = new GeoJSONWriter(); + GeometryFactory factory = new GeometryFactory(); + + // Test data + Point point = factory.createPoint(new Coordinate(1, 1)); + String expected = "{\"type\":\"Point\",\"coordinates\":[1.0,1.0]}"; + + // Test + org.wololo.geojson.Geometry json = writer.write(point); + assertEquals("Point should be correctly converted to GeoJSON", expected, json.toString()); + + org.locationtech.jts.geom.Geometry geometry = reader.read(json); + assertEquals( + "GeoJSON should be correctly converted back to geometry", + "POINT (1 1)", + geometry.toString()); + } + + @Test + public void testLineStringConversion() { + // Setup + GeoJSONReader reader = new GeoJSONReader(); + GeoJSONWriter writer = new GeoJSONWriter(); + GeometryFactory factory = new GeometryFactory(); + + // Test data + Coordinate[] coordinates = + new Coordinate[] { + new Coordinate(1, 1), new Coordinate(1, 2), new Coordinate(2, 2), new Coordinate(1, 1) + }; + LineString lineString = factory.createLineString(coordinates); + String expected = + "{\"type\":\"LineString\",\"coordinates\":[[1.0,1.0],[1.0,2.0],[2.0,2.0],[1.0,1.0]]}"; + + // Test + org.wololo.geojson.Geometry json = writer.write(lineString); + assertEquals("LineString should be correctly converted to GeoJSON", expected, json.toString()); + } + + @Test + public void testLineStringWithIdConversion() { + // Setup + GeoJSONWriter writer = new GeoJSONWriter(); + GeometryFactory factory = new GeometryFactory(); + + // Test data + Coordinate[] coordinates = + new Coordinate[] { + new Coordinate(1, 1), new Coordinate(1, 2), new Coordinate(2, 2), new Coordinate(1, 1) + }; + LineString lineString = factory.createLineString(coordinates); + String expected = + "{\"type\":\"LineString\",\"coordinates\":[[1.0,1.0],[1.0,2.0],[2.0,2.0],[1.0,1.0]]}"; + + // Test + org.wololo.geojson.Geometry json = writer.write(lineString); + assertEquals( + "LineString with ID should be correctly converted to GeoJSON", expected, json.toString()); + } + + @Test + public void testLinearRingConversion() { + // Setup + GeoJSONWriter writer = new GeoJSONWriter(); + GeometryFactory factory = new GeometryFactory(); + + // Test data + Coordinate[] coordinates = + new Coordinate[] { + new Coordinate(1, 1), new Coordinate(1, 2), new Coordinate(2, 2), new Coordinate(1, 1) + }; + LinearRing ring = factory.createLinearRing(coordinates); + String expected = + "{\"type\":\"LineString\",\"coordinates\":[[1.0,1.0],[1.0,2.0],[2.0,2.0],[1.0,1.0]]}"; + + // Test + org.wololo.geojson.Geometry json = writer.write(ring); + assertEquals("LinearRing should be correctly converted to GeoJSON", expected, json.toString()); + } + + @Test + public void testPolygonConversion() { + // Setup + GeoJSONWriter writer = new GeoJSONWriter(); + GeometryFactory factory = new GeometryFactory(); + + // Test data + Coordinate[] coordinates = + new Coordinate[] { + new Coordinate(1, 1), new Coordinate(1, 2), new Coordinate(2, 2), new Coordinate(1, 1) + }; + Polygon polygon = factory.createPolygon(coordinates); + String expected = + "{\"type\":\"Polygon\",\"coordinates\":[[[1.0,1.0],[1.0,2.0],[2.0,2.0],[1.0,1.0]]]}"; + + // Test + org.wololo.geojson.Geometry json = writer.write(polygon); + assertEquals("Polygon should be correctly converted to GeoJSON", expected, json.toString()); + } + + @Test + public void testMultiPointConversion() { + // Setup + GeoJSONWriter writer = new GeoJSONWriter(); + GeometryFactory factory = new GeometryFactory(); + + // Test data + Coordinate[] coordinates = + new Coordinate[] { + new Coordinate(1, 1), new Coordinate(1, 2), new Coordinate(2, 2), new Coordinate(1, 1) + }; + LineString lineString = factory.createLineString(coordinates); + MultiPoint multiPoint = factory.createMultiPointFromCoords(lineString.getCoordinates()); + String expected = + "{\"type\":\"MultiPoint\",\"coordinates\":[[1.0,1.0],[1.0,2.0],[2.0,2.0],[1.0,1.0]]}"; + + // Test + org.wololo.geojson.Geometry json = writer.write(multiPoint); + assertEquals("MultiPoint should be correctly converted to GeoJSON", expected, json.toString()); + } + + @Test + public void testMultiLineStringConversion() { + // Setup + GeoJSONWriter writer = new GeoJSONWriter(); + GeometryFactory factory = new GeometryFactory(); + + // Test data + Coordinate[] coordinates = + new Coordinate[] { + new Coordinate(1, 1), new Coordinate(1, 2), new Coordinate(2, 2), new Coordinate(1, 1) + }; + LineString lineString = factory.createLineString(coordinates); + MultiLineString multiLineString = + factory.createMultiLineString(new LineString[] {lineString, lineString}); + String expected = + "{\"type\":\"MultiLineString\",\"coordinates\":[[[1.0,1.0],[1.0,2.0],[2.0,2.0],[1.0,1.0]],[[1.0,1.0],[1.0,2.0],[2.0,2.0],[1.0,1.0]]]}"; + + // Test + org.wololo.geojson.Geometry json = writer.write(multiLineString); + assertEquals( + "MultiLineString should be correctly converted to GeoJSON", expected, json.toString()); + } + + @Test + public void testMultiPolygonConversion() { + // Setup + GeoJSONWriter writer = new GeoJSONWriter(); + GeometryFactory factory = new GeometryFactory(); + + // Test data + Coordinate[] coordinates = + new Coordinate[] { + new Coordinate(1, 1), new Coordinate(1, 2), new Coordinate(2, 2), new Coordinate(1, 1) + }; + LineString lineString = factory.createLineString(coordinates); + Polygon polygon = factory.createPolygon(lineString.getCoordinates()); + MultiPolygon multiPolygon = factory.createMultiPolygon(new Polygon[] {polygon, polygon}); + String expected = + "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[1.0,1.0],[1.0,2.0],[2.0,2.0],[1.0,1.0]]],[[[1.0,1.0],[1.0,2.0],[2.0,2.0],[1.0,1.0]]]]}"; + + // Test + org.wololo.geojson.Geometry json = writer.write(multiPolygon); + assertEquals( + "MultiPolygon should be correctly converted to GeoJSON", expected, json.toString()); + } + + @Test + public void testFeatureCollectionConversion() { + // Setup + GeoJSONWriter writer = new GeoJSONWriter(); + GeometryFactory factory = new GeometryFactory(); + + // Test data + Point point = factory.createPoint(new Coordinate(1, 1)); + org.wololo.geojson.Geometry json = writer.write(point); + Feature feature1 = new Feature(json, null); + Feature feature2 = new Feature(json, null); + FeatureCollection featureCollection = new FeatureCollection(new Feature[] {feature1, feature2}); + String expected = + "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[1.0,1.0]},\"properties\":null},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[1.0,1.0]},\"properties\":null}]}"; + + // Test + assertEquals( + "FeatureCollection should be correctly converted to GeoJSON", + expected, + featureCollection.toString()); + } + + // 3D Tests + + @Test + public void testPointWithZConversion() { + // Setup + GeoJSONReader reader = new GeoJSONReader(); + GeoJSONWriter writer = new GeoJSONWriter(); + GeometryFactory factory = new GeometryFactory(); + + // Test data + Point point = factory.createPoint(new Coordinate(1, 1, 1)); + String expected = "{\"type\":\"Point\",\"coordinates\":[1.0,1.0,1.0]}"; + + // Test + org.wololo.geojson.Geometry json = writer.write(point); + assertEquals("3D Point should be correctly converted to GeoJSON", expected, json.toString()); + + org.locationtech.jts.geom.Geometry geometry = reader.read(json); + assertEquals( + "GeoJSON should be correctly converted back to geometry", + "POINT (1 1)", + geometry.toString()); + } + + @Test + public void testLineStringWithZConversion() { + // Setup + GeoJSONWriter writer = new GeoJSONWriter(); + GeometryFactory factory = new GeometryFactory(); + + // Test data + Coordinate[] coordinates = + new Coordinate[] { + new Coordinate(1, 1, 1), + new Coordinate(1, 2, 1), + new Coordinate(2, 2, 2), + new Coordinate(1, 1, 1) + }; + LineString lineString = factory.createLineString(coordinates); + String expected = + "{\"type\":\"LineString\",\"coordinates\":[[1.0,1.0,1.0],[1.0,2.0,1.0],[2.0,2.0,2.0],[1.0,1.0,1.0]]}"; + + // Test + org.wololo.geojson.Geometry json = writer.write(lineString); + assertEquals( + "3D LineString should be correctly converted to GeoJSON", expected, json.toString()); + } + + @Test + public void testPolygonWithZConversion() { + // Setup + GeoJSONWriter writer = new GeoJSONWriter(); + GeometryFactory factory = new GeometryFactory(); + + // Test data + Coordinate[] coordinates = + new Coordinate[] { + new Coordinate(1, 1, 1), + new Coordinate(1, 2, 1), + new Coordinate(2, 2, 2), + new Coordinate(1, 1, 1) + }; + LineString lineString = factory.createLineString(coordinates); + Polygon polygon = factory.createPolygon(lineString.getCoordinates()); + String expected = + "{\"type\":\"Polygon\",\"coordinates\":[[[1.0,1.0,1.0],[1.0,2.0,1.0],[2.0,2.0,2.0],[1.0,1.0,1.0]]]}"; + + // Test + org.wololo.geojson.Geometry json = writer.write(polygon); + assertEquals("3D Polygon should be correctly converted to GeoJSON", expected, json.toString()); + } + + @Test + public void testMultiPointWithZConversion() { + // Setup + GeoJSONWriter writer = new GeoJSONWriter(); + GeometryFactory factory = new GeometryFactory(); + + // Test data + Coordinate[] coordinates = + new Coordinate[] { + new Coordinate(1, 1, 1), + new Coordinate(1, 2, 1), + new Coordinate(2, 2, 2), + new Coordinate(1, 1, 1) + }; + LineString lineString = factory.createLineString(coordinates); + MultiPoint multiPoint = factory.createMultiPointFromCoords(lineString.getCoordinates()); + String expected = + "{\"type\":\"MultiPoint\",\"coordinates\":[[1.0,1.0,1.0],[1.0,2.0,1.0],[2.0,2.0,2.0],[1.0,1.0,1.0]]}"; + + // Test + org.wololo.geojson.Geometry json = writer.write(multiPoint); + assertEquals( + "3D MultiPoint should be correctly converted to GeoJSON", expected, json.toString()); + } + + @Test + public void testMultiLineStringWithZConversion() { + // Setup + GeoJSONWriter writer = new GeoJSONWriter(); + GeometryFactory factory = new GeometryFactory(); + + // Test data + Coordinate[] coordinates = + new Coordinate[] { + new Coordinate(1, 1, 1), + new Coordinate(1, 2, 1), + new Coordinate(2, 2, 2), + new Coordinate(1, 1, 1) + }; + LineString lineString = factory.createLineString(coordinates); + MultiLineString multiLineString = + factory.createMultiLineString(new LineString[] {lineString, lineString}); + String expected = + "{\"type\":\"MultiLineString\",\"coordinates\":[[[1.0,1.0,1.0],[1.0,2.0,1.0],[2.0,2.0,2.0],[1.0,1.0,1.0]],[[1.0,1.0,1.0],[1.0,2.0,1.0],[2.0,2.0,2.0],[1.0,1.0,1.0]]]}"; + + // Test + org.wololo.geojson.Geometry json = writer.write(multiLineString); + assertEquals( + "3D MultiLineString should be correctly converted to GeoJSON", expected, json.toString()); + } + + @Test + public void testMultiPolygonWithZConversion() { + // Setup + GeoJSONWriter writer = new GeoJSONWriter(); + GeometryFactory factory = new GeometryFactory(); + + // Test data + Coordinate[] coordinates = + new Coordinate[] { + new Coordinate(1, 1, 1), + new Coordinate(1, 2, 1), + new Coordinate(2, 2, 2), + new Coordinate(1, 1, 1) + }; + LineString lineString = factory.createLineString(coordinates); + Polygon polygon = factory.createPolygon(lineString.getCoordinates()); + MultiPolygon multiPolygon = factory.createMultiPolygon(new Polygon[] {polygon, polygon}); + String expected = + "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[1.0,1.0,1.0],[1.0,2.0,1.0],[2.0,2.0,2.0],[1.0,1.0,1.0]]],[[[1.0,1.0,1.0],[1.0,2.0,1.0],[2.0,2.0,2.0],[1.0,1.0,1.0]]]]}"; + + // Test + org.wololo.geojson.Geometry json = writer.write(multiPolygon); + assertEquals( + "3D MultiPolygon should be correctly converted to GeoJSON", expected, json.toString()); + } + + @Test + public void testFeatureCollectionWithZConversion() { + // Setup + GeoJSONWriter writer = new GeoJSONWriter(); + GeometryFactory factory = new GeometryFactory(); + + // Test data + Point point = factory.createPoint(new Coordinate(1, 1, 1)); + org.wololo.geojson.Geometry json = writer.write(point); + Feature feature1 = new Feature(json, null); + Feature feature2 = new Feature(json, null); + FeatureCollection featureCollection = new FeatureCollection(new Feature[] {feature1, feature2}); + String expected = + "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[1.0,1.0,1.0]},\"properties\":null},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[1.0,1.0,1.0]},\"properties\":null}]}"; + + // Test + assertEquals( + "3D FeatureCollection should be correctly converted to GeoJSON", + expected, + featureCollection.toString()); + } + + // XYZM Tests + + @Test + public void testPointWithZMConversion() { + // Setup + GeoJSONWriter writer = new GeoJSONWriter(); + GeometryFactory factory = new GeometryFactory(); + + // Test data + Point point = factory.createPoint(new CoordinateXYZM(1, 1, 1, 2)); + String expected = "{\"type\":\"Point\",\"coordinates\":[1.0,1.0,1.0,2.0]}"; + + // Test + org.wololo.geojson.Geometry json = writer.write(point); + assertEquals("XYZM Point should be correctly converted to GeoJSON", expected, json.toString()); + } + + @Test + public void testLineStringWithZMConversion() { + // Setup + GeoJSONWriter writer = new GeoJSONWriter(); + GeometryFactory factory = new GeometryFactory(); + + // Test data + Coordinate[] coordinates = + new Coordinate[] { + new CoordinateXYZM(1, 1, 1, 10), + new CoordinateXYZM(1, 2, 1, 20), + new CoordinateXYZM(2, 2, 2, 30), + new CoordinateXYZM(1, 1, 1, 10) + }; + LineString lineString = factory.createLineString(coordinates); + String expected = + "{\"type\":\"LineString\",\"coordinates\":[[1.0,1.0,1.0,10.0],[1.0,2.0,1.0,20.0],[2.0,2.0,2.0,30.0],[1.0,1.0,1.0,10.0]]}"; + + // Test + org.wololo.geojson.Geometry json = writer.write(lineString); + assertEquals( + "XYZM LineString should be correctly converted to GeoJSON", expected, json.toString()); + } + + @Test + public void testPolygonWithZMConversion() { + // Setup + GeoJSONWriter writer = new GeoJSONWriter(); + GeometryFactory factory = new GeometryFactory(); + + // Test data + Coordinate[] coordinates = + new Coordinate[] { + new CoordinateXYZM(1, 1, 1, 10), + new CoordinateXYZM(1, 2, 1, 20), + new CoordinateXYZM(2, 2, 2, 30), + new CoordinateXYZM(1, 1, 1, 10) + }; + Polygon polygon = factory.createPolygon(coordinates); + String expected = + "{\"type\":\"Polygon\",\"coordinates\":[[[1.0,1.0,1.0,10.0],[1.0,2.0,1.0,20.0],[2.0,2.0,2.0,30.0],[1.0,1.0,1.0,10.0]]]}"; + + // Test + org.wololo.geojson.Geometry json = writer.write(polygon); + assertEquals( + "XYZM Polygon should be correctly converted to GeoJSON", expected, json.toString()); + } + + @Test + public void testMultiPointWithZMConversion() { + // Setup + GeoJSONWriter writer = new GeoJSONWriter(); + GeometryFactory factory = new GeometryFactory(); + + // Test data + Coordinate[] coordinates = + new Coordinate[] { + new CoordinateXYZM(1, 1, 1, 10), + new CoordinateXYZM(1, 2, 1, 20), + new CoordinateXYZM(2, 2, 2, 30) + }; + MultiPoint multiPoint = factory.createMultiPointFromCoords(coordinates); + String expected = + "{\"type\":\"MultiPoint\",\"coordinates\":[[1.0,1.0,1.0,10.0],[1.0,2.0,1.0,20.0],[2.0,2.0,2.0,30.0]]}"; + + // Test + org.wololo.geojson.Geometry json = writer.write(multiPoint); + assertEquals( + "XYZM MultiPoint should be correctly converted to GeoJSON", expected, json.toString()); + } + + @Test + public void testMultiLineStringWithZMConversion() { + // Setup + GeoJSONWriter writer = new GeoJSONWriter(); + GeometryFactory factory = new GeometryFactory(); + + // Test data + Coordinate[] coordinates1 = + new Coordinate[] {new CoordinateXYZM(1, 1, 1, 10), new CoordinateXYZM(1, 2, 1, 20)}; + Coordinate[] coordinates2 = + new Coordinate[] {new CoordinateXYZM(2, 2, 2, 30), new CoordinateXYZM(3, 3, 3, 40)}; + LineString lineString1 = factory.createLineString(coordinates1); + LineString lineString2 = factory.createLineString(coordinates2); + MultiLineString multiLineString = + factory.createMultiLineString(new LineString[] {lineString1, lineString2}); + String expected = + "{\"type\":\"MultiLineString\",\"coordinates\":[[[1.0,1.0,1.0,10.0],[1.0,2.0,1.0,20.0]],[[2.0,2.0,2.0,30.0],[3.0,3.0,3.0,40.0]]]}"; + + // Test + org.wololo.geojson.Geometry json = writer.write(multiLineString); + assertEquals( + "XYZM MultiLineString should be correctly converted to GeoJSON", expected, json.toString()); + } + + @Test + public void testMultiPolygonWithZMConversion() { + // Setup + GeoJSONWriter writer = new GeoJSONWriter(); + GeometryFactory factory = new GeometryFactory(); + + // Test data + Coordinate[] coordinates = + new Coordinate[] { + new CoordinateXYZM(1, 1, 1, 10), + new CoordinateXYZM(1, 2, 1, 20), + new CoordinateXYZM(2, 2, 2, 30), + new CoordinateXYZM(1, 1, 1, 10) + }; + Polygon polygon = factory.createPolygon(coordinates); + MultiPolygon multiPolygon = factory.createMultiPolygon(new Polygon[] {polygon, polygon}); + String expected = + "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[1.0,1.0,1.0,10.0],[1.0,2.0,1.0,20.0],[2.0,2.0,2.0,30.0],[1.0,1.0,1.0,10.0]]],[[[1.0,1.0,1.0,10.0],[1.0,2.0,1.0,20.0],[2.0,2.0,2.0,30.0],[1.0,1.0,1.0,10.0]]]]}"; + + // Test + org.wololo.geojson.Geometry json = writer.write(multiPolygon); + assertEquals( + "XYZM MultiPolygon should be correctly converted to GeoJSON", expected, json.toString()); + } + + @Test + public void testFeatureCollectionWithZMConversion() { + // Setup + GeoJSONWriter writer = new GeoJSONWriter(); + GeometryFactory factory = new GeometryFactory(); + + // Test data + Point point = factory.createPoint(new CoordinateXYZM(1, 1, 1, 10)); + org.wololo.geojson.Geometry json = writer.write(point); + Feature feature1 = new Feature(json, null); + Feature feature2 = new Feature(json, null); + FeatureCollection featureCollection = new FeatureCollection(new Feature[] {feature1, feature2}); + String expected = + "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[1.0,1.0,1.0,10.0]},\"properties\":null},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[1.0,1.0,1.0,10.0]},\"properties\":null}]}"; + + // Test + assertEquals( + "XYZM FeatureCollection should be correctly converted to GeoJSON", + expected, + featureCollection.toString()); + } + + // XYM Tests - These should fail as XYM is not supported + + @Test + public void testPointWithMConversionFails() { + // Setup + GeoJSONWriter writer = new GeoJSONWriter(); + GeometryFactory factory = new GeometryFactory(); + + // Test data - XYM coordinate + Point point = factory.createPoint(new CoordinateXYM(1, 1, 10)); + + // Test - should throw UnsupportedOperationException + try { + writer.write(point); + fail("XYM coordinates should not be supported"); + } catch (UnsupportedOperationException e) { + assertEquals( + "XYM coordinates are not supported. Please convert to XYZM coordinates by adding a Z value.", + e.getMessage()); + } + } + + @Test + public void testLineStringWithMConversionFails() { + // Setup + GeoJSONWriter writer = new GeoJSONWriter(); + GeometryFactory factory = new GeometryFactory(); + + // Test data - XYM coordinates + Coordinate[] coordinates = + new Coordinate[] { + new CoordinateXYM(1, 1, 10), new CoordinateXYM(1, 2, 20), new CoordinateXYM(2, 2, 30) + }; + LineString lineString = factory.createLineString(coordinates); + + // Test - should throw UnsupportedOperationException + try { + writer.write(lineString); + fail("XYM coordinates should not be supported"); + } catch (UnsupportedOperationException e) { + assertEquals( + "XYM coordinates are not supported. Please convert to XYZM coordinates by adding a Z value.", + e.getMessage()); + } + } + + @Test + public void testPolygonWithMConversionFails() { + // Setup + GeoJSONWriter writer = new GeoJSONWriter(); + GeometryFactory factory = new GeometryFactory(); + + // Test data - XYM coordinates + Coordinate[] coordinates = + new Coordinate[] { + new CoordinateXYM(1, 1, 10), + new CoordinateXYM(1, 2, 20), + new CoordinateXYM(2, 2, 30), + new CoordinateXYM(1, 1, 10) + }; + Polygon polygon = factory.createPolygon(coordinates); + + // Test - should throw UnsupportedOperationException + try { + writer.write(polygon); + fail("XYM coordinates should not be supported"); + } catch (UnsupportedOperationException e) { + assertEquals( + "XYM coordinates are not supported. Please convert to XYZM coordinates by adding a Z value.", + e.getMessage()); + } + } +} diff --git a/pom.xml b/pom.xml index 7865db02ed..f9bcc088d3 100644 --- a/pom.xml +++ b/pom.xml @@ -72,7 +72,6 @@ <hadoop.version>3.2.4</hadoop.version> <jackson.version>2.13.4</jackson.version> <jts.version>1.20.0</jts.version> - <jts2geojson.version>0.16.1</jts2geojson.version> <spatial4j.version>0.8</spatial4j.version> <jt-jiffle.version>1.1.24</jt-jiffle.version> @@ -139,17 +138,6 @@ <dependencyManagement> <dependencies> - <dependency> - <groupId>org.wololo</groupId> - <artifactId>jts2geojson</artifactId> - <version>${jts2geojson.version}</version> - <exclusions> - <exclusion> - <groupId>org.locationtech.jts</groupId> - <artifactId>jts-core</artifactId> - </exclusion> - </exclusions> - </dependency> <dependency> <groupId>org.locationtech.jts</groupId> <artifactId>jts-core</artifactId> diff --git a/spark/common/pom.xml b/spark/common/pom.xml index a910d74480..d02fbacb5a 100644 --- a/spark/common/pom.xml +++ b/spark/common/pom.xml @@ -174,16 +174,6 @@ <groupId>org.locationtech.jts</groupId> <artifactId>jts-core</artifactId> </dependency> - <dependency> - <groupId>org.wololo</groupId> - <artifactId>jts2geojson</artifactId> - <exclusions> - <exclusion> - <groupId>com.fasterxml.jackson.core</groupId> - <artifactId>*</artifactId> - </exclusion> - </exclusions> - </dependency> <dependency> <groupId>io.graphframes</groupId> <artifactId>graphframes-spark${spark.major.version}_${scala.compat.version}</artifactId> diff --git a/spark/spark-3.4/pom.xml b/spark/spark-3.4/pom.xml index 6b5fe78a4d..c809010258 100644 --- a/spark/spark-3.4/pom.xml +++ b/spark/spark-3.4/pom.xml @@ -106,16 +106,6 @@ <groupId>org.locationtech.jts</groupId> <artifactId>jts-core</artifactId> </dependency> - <dependency> - <groupId>org.wololo</groupId> - <artifactId>jts2geojson</artifactId> - <exclusions> - <exclusion> - <groupId>com.fasterxml.jackson.core</groupId> - <artifactId>*</artifactId> - </exclusion> - </exclusions> - </dependency> <dependency> <groupId>org.scala-lang</groupId> <artifactId>scala-library</artifactId> diff --git a/spark/spark-3.5/pom.xml b/spark/spark-3.5/pom.xml index f9478f1d96..6aafda49b5 100644 --- a/spark/spark-3.5/pom.xml +++ b/spark/spark-3.5/pom.xml @@ -106,16 +106,6 @@ <groupId>org.locationtech.jts</groupId> <artifactId>jts-core</artifactId> </dependency> - <dependency> - <groupId>org.wololo</groupId> - <artifactId>jts2geojson</artifactId> - <exclusions> - <exclusion> - <groupId>com.fasterxml.jackson.core</groupId> - <artifactId>*</artifactId> - </exclusion> - </exclusions> - </dependency> <dependency> <groupId>org.scala-lang</groupId> <artifactId>scala-library</artifactId> diff --git a/spark/spark-4.0/pom.xml b/spark/spark-4.0/pom.xml index ee4b30724c..d2635f0976 100644 --- a/spark/spark-4.0/pom.xml +++ b/spark/spark-4.0/pom.xml @@ -106,16 +106,6 @@ <groupId>org.locationtech.jts</groupId> <artifactId>jts-core</artifactId> </dependency> - <dependency> - <groupId>org.wololo</groupId> - <artifactId>jts2geojson</artifactId> - <exclusions> - <exclusion> - <groupId>com.fasterxml.jackson.core</groupId> - <artifactId>*</artifactId> - </exclusion> - </exclusions> - </dependency> <dependency> <groupId>org.scala-lang</groupId> <artifactId>scala-library</artifactId>