diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonSerializersModule.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonSerializersModule.java new file mode 100644 index 0000000000..c6b12337ca --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonSerializersModule.java @@ -0,0 +1,156 @@ +/* + * Copyright 2015-2021 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.data.mongodb.core.geo; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.Module; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.module.SimpleModule; +import org.springframework.data.geo.Point; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * A Jackson {@link Module} to register custom {@link JsonSerializer}s for GeoJSON types. + * + * @author Bjorn Harvold + * @since + */ +public class GeoJsonSerializersModule extends SimpleModule { + + private static final long serialVersionUID = 1340494654898895610L; + + public GeoJsonSerializersModule() { + addSerializer(GeoJsonPoint.class, new GeoJsonPointSerializer()); + addSerializer(GeoJsonMultiPoint.class, new GeoJsonMultiPointSerializer()); + addSerializer(GeoJsonLineString.class, new GeoJsonLineStringSerializer()); + addSerializer(GeoJsonMultiLineString.class, new GeoJsonMultiLineStringSerializer()); + addSerializer(GeoJsonPolygon.class, new GeoJsonPolygonSerializer()); + addSerializer(GeoJsonMultiPolygon.class, new GeoJsonMultiPolygonSerializer()); + } + + public static class GeoJsonPointSerializer extends JsonSerializer { + @Override + public void serialize(GeoJsonPoint value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException { + gen.writeStartObject(); + gen.writeStringField("type", value.getType()); + gen.writeObjectField("coordinates", value.getCoordinates()); + gen.writeEndObject(); + } + + } + + public static class GeoJsonLineStringSerializer extends JsonSerializer { + + @Override + public void serialize(GeoJsonLineString value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException { + gen.writeStartObject(); + gen.writeStringField("type", value.getType()); + gen.writeArrayFieldStart("coordinates"); + for (Point p : value.getCoordinates()) { + gen.writeObject(new double[]{p.getX(), p.getY()}); + } + gen.writeEndArray(); + gen.writeEndObject(); + } + } + + public static class GeoJsonMultiPointSerializer extends JsonSerializer { + + @Override + public void serialize(GeoJsonMultiPoint value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException { + gen.writeStartObject(); + gen.writeStringField("type", value.getType()); + gen.writeArrayFieldStart("coordinates"); + for (Point p : value.getCoordinates()) { + gen.writeObject(new double[]{p.getX(), p.getY()}); + } + gen.writeEndArray(); + gen.writeEndObject(); + } + } + + public static class GeoJsonMultiLineStringSerializer extends JsonSerializer { + + @Override + public void serialize(GeoJsonMultiLineString value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException { + gen.writeStartObject(); + gen.writeStringField("type", value.getType()); + gen.writeArrayFieldStart("coordinates"); + for (GeoJsonLineString lineString : value.getCoordinates()) { + List arrayList = new ArrayList<>(); + for (Point p : lineString.getCoordinates()) { + arrayList.add(new double[]{p.getX(), p.getY()}); + } + double[][] doubles = arrayList.toArray(new double[0][0]); + gen.writeObject(doubles); + } + gen.writeEndArray(); + gen.writeEndObject(); + } + } + + public static class GeoJsonPolygonSerializer extends JsonSerializer { + + @Override + public void serialize(GeoJsonPolygon value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException { + gen.writeStartObject(); + gen.writeStringField("type", value.getType()); + gen.writeArrayFieldStart("coordinates"); + for (GeoJsonLineString ls : value.getCoordinates()) { + gen.writeStartArray(); + for (Point p : ls.getCoordinates()) { + gen.writeObject(new double[]{p.getX(), p.getY()}); + } + gen.writeEndArray(); + } + gen.writeEndArray(); + gen.writeEndObject(); + } + } + + public static class GeoJsonMultiPolygonSerializer extends JsonSerializer { + + @Override + public void serialize(GeoJsonMultiPolygon value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException { + gen.writeStartObject(); + gen.writeStringField("type", value.getType()); + gen.writeArrayFieldStart("coordinates"); + for (GeoJsonPolygon polygon : value.getCoordinates()) { + + gen.writeStartArray(); + + gen.writeStartArray(); + for (GeoJsonLineString lineString : polygon.getCoordinates()) { + + for (Point p : lineString.getCoordinates()) { + gen.writeObject(new double[]{p.getX(), p.getY()}); + } + + } + + gen.writeEndArray(); + gen.writeEndArray(); + } + gen.writeEndArray(); + gen.writeEndObject(); + } + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoJsonModuleRoundTripUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoJsonModuleRoundTripUnitTests.java new file mode 100644 index 0000000000..5ca4bc8a28 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoJsonModuleRoundTripUnitTests.java @@ -0,0 +1,151 @@ +/* + * Copyright 2015-2021 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.data.mongodb.core.geo; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.data.geo.Point; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Bjorn Harvold + */ +public class GeoJsonModuleRoundTripUnitTests { + + ObjectMapper mapper; + + @BeforeEach + public void setUp() { + + mapper = new ObjectMapper(); + mapper.registerModule(new GeoJsonModule()); + mapper.registerModule(new GeoJsonSerializersModule()); + } + + @Test + public void shouldMarshalJsonPointCorrectly() throws IOException { + + String json = "{\"type\":\"Point\",\"coordinates\":[10.0,20.0]}"; + GeoJsonPoint point = new GeoJsonPoint(10D, 20D); + + assertThat(mapper.readValue(json, GeoJsonPoint.class)).isEqualTo(point); + + String backToJSON = mapper.writeValueAsString(point); + + assertThat(backToJSON).isEqualTo(json); + } + + @Test + public void shouldMarshalGeoJsonLineStringCorrectly() + throws IOException { + + String json = "{\"type\":\"LineString\",\"coordinates\":[[10.0,20.0],[30.0,40.0],[50.0,60.0]]}"; + + GeoJsonLineString lineString = new GeoJsonLineString(Arrays.asList(new Point(10, 20), new Point(30, 40), new Point(50, 60))); + + assertThat(mapper.readValue(json, GeoJsonLineString.class)).isEqualTo(lineString); + + String backToJSON = mapper.writeValueAsString(lineString); + + assertThat(backToJSON).isEqualTo(json); + } + + @Test + public void shouldMarshalGeoJsonMultiPointCorrectly() throws IOException { + + String json = "{\"type\":\"MultiPoint\",\"coordinates\":[[10.0,20.0],[30.0,40.0],[50.0,60.0]]}"; + GeoJsonMultiPoint multiPoint = new GeoJsonMultiPoint(Arrays.asList(new Point(10, 20), new Point(30, 40), new Point(50, 60))); + + assertThat(mapper.readValue(json, GeoJsonMultiPoint.class)).isEqualTo(multiPoint); + + String backToJSON = mapper.writeValueAsString(multiPoint); + + assertThat(backToJSON).isEqualTo(json); + } + + @Test + @SuppressWarnings("unchecked") + public void shouldMarshalGeoJsonMultiLineStringCorrectly() throws IOException { + + String json = "{\"type\":\"MultiLineString\",\"coordinates\":[[[10.0,20.0],[30.0,40.0]],[[50.0,60.0],[70.0,80.0]]]}"; + GeoJsonMultiLineString multiLineString = new GeoJsonMultiLineString(Arrays.asList(new Point(10, 20), new Point(30, 40)), Arrays.asList(new Point(50, 60), new Point(70, 80))); + + assertThat(mapper.readValue(json, GeoJsonMultiLineString.class)).isEqualTo(multiLineString); + + String backToJSON = mapper.writeValueAsString(multiLineString); + + assertThat(backToJSON).isEqualTo(json); + } + + @Test + public void shouldMarshalGeoJsonPolygonCorrectly() throws IOException { + String json = "{\"type\":\"Polygon\",\"coordinates\":[[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0],[100.0,0.0]]]}"; + + List points = Arrays.asList(new Point(100, 0), new Point(101, 0), new Point(101, 1), new Point(100, 1), new Point(100, 0)); + GeoJsonPolygon polygon = new GeoJsonPolygon(points); + + assertThat(mapper.readValue(json, GeoJsonPolygon.class)).isEqualTo(polygon); + + String backToJSON = mapper.writeValueAsString(polygon); + + assertThat(backToJSON).isEqualTo(json); + } + + @Test + public void shouldMarshalGeoJsonMultiPolygonCorrectly() + throws IOException { + + String json = "{\"type\":\"MultiPolygon\",\"coordinates\":[" + + "[" + + "[" + + "[102.0,2.0],[103.0,2.0],[103.0,3.0],[102.0,3.0],[102.0,2.0]" + + "]" + + "]," + + "[" + + "[" + + "[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0],[100.0,0.0]" + + "]" + + "]," + + "[" + + "[" + + "[100.2,0.2],[100.8,0.2],[100.8,0.8],[100.2,0.8],[100.2,0.2]" + + "]" + + "]" + + "]" + + "}"; + + GeoJsonMultiPolygon multiPolygon = new GeoJsonMultiPolygon(Arrays.asList( + new GeoJsonPolygon(Arrays.asList(new Point(102, 2), new Point(103, 2), new Point(103, 3), new Point(102, 3), + new Point(102, 2))), + new GeoJsonPolygon(Arrays.asList(new Point(100, 0), new Point(101, 0), new Point(101, 1), new Point(100, 1), + new Point(100, 0))), + new GeoJsonPolygon(Arrays.asList(new Point(100.2, 0.2), new Point(100.8, 0.2), new Point(100.8, 0.8), + new Point(100.2, 0.8), new Point(100.2, 0.2))))); + + assertThat(mapper.readValue(json, GeoJsonMultiPolygon.class)).isEqualTo(multiPolygon); + + String backToJSON = mapper.writeValueAsString(multiPolygon); + + assertThat(backToJSON).isEqualTo(json); + + } +}