DATAMONGO-1181 - Add Jackson Module for GeoJSON types.

Added GeoJsonModule providing JsonDeserializers for GeoJsonPoint, GeoJsonMultiPoint, GeoJsonLineString, GeoJsonMultiLineString, GeoJsonPolygon and GeoJsonMultiPolygon.

Original pull request: #283.
This commit is contained in:
Christoph Strobl
2015-03-17 14:35:59 +01:00
committed by Oliver Gierke
parent 37d53d936d
commit a1f6dc6db4
4 changed files with 481 additions and 0 deletions

View File

@@ -145,6 +145,13 @@
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>

View File

@@ -0,0 +1,336 @@
/*
* Copyright 2015 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
*
* 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.springframework.data.mongodb.core.geo;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.data.geo.GeoModule;
import org.springframework.data.geo.Point;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
/**
* @author Christoph Strobl
* @since 1.7
*/
public class GeoJsonModule extends GeoModule {
public GeoJsonModule() {
super();
addDeserializer(GeoJsonPoint.class, new GeoJsonPointDeserializer());
addDeserializer(GeoJsonMultiPoint.class, new GeoJsonMultiPointDeserializer());
addDeserializer(GeoJsonLineString.class, new GeoJsonLineStringDeserializer());
addDeserializer(GeoJsonMultiLineString.class, new GeoJsonMultiLineStringDeserializer());
addDeserializer(GeoJsonPolygon.class, new GeoJsonPolygonDeserializer());
addDeserializer(GeoJsonMultiPolygon.class, new GeoJsonMultiPolygonDeserializer());
}
/**
* @author Christoph Strobl
* @since 1.7
*/
static abstract class GeoJsonDeserializer<T extends GeoJson<?>> extends JsonDeserializer<T> {
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.JsonDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext)
*/
@Override
public T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode node = jp.readValueAsTree();
JsonNode coordinates = node.get("coordinates");
if (coordinates != null && coordinates.isArray()) {
return doDeserialize((ArrayNode) coordinates);
}
return null;
}
/**
* Perform the actual deserialization given the {@literal coordinates} as {@link ArrayNode}.
*
* @param coordinates
* @return
*/
protected abstract T doDeserialize(ArrayNode coordinates);
/**
* Get the {@link GeoJsonPoint} representation of given {@link ArrayNode} assuming {@code node.[0]} represents
* {@literal x - coordinate} and {@code node.[1]} is {@literal y}.
*
* @param node can be {@literal null}.
* @return {@literal null} when given a {@code null} value.
*/
protected GeoJsonPoint toGeoJsonPoint(ArrayNode node) {
if (node == null) {
return null;
}
return new GeoJsonPoint(node.get(0).asDouble(), node.get(1).asDouble());
}
/**
* Get the {@link Point} representation of given {@link ArrayNode} assuming {@code node.[0]} represents
* {@literal x - coordinate} and {@code node.[1]} is {@literal y}.
*
* @param node can be {@literal null}.
* @return {@literal null} when given a {@code null} value.
*/
protected Point toPoint(ArrayNode node) {
if (node == null) {
return null;
}
return new Point(node.get(0).asDouble(), node.get(1).asDouble());
}
/**
* Get the points nested within given {@link ArrayNode}.
*
* @param node can be {@literal null}.
* @return {@literal empty list} when given a {@code null} value.
*/
protected List<Point> toPoints(ArrayNode node) {
if (node == null) {
return Collections.emptyList();
}
List<Point> points = new ArrayList<Point>(node.size());
for (JsonNode coordinatePair : node) {
if (coordinatePair.isArray()) {
points.add(toPoint((ArrayNode) coordinatePair));
}
}
return points;
}
protected GeoJsonLineString toLineString(ArrayNode node) {
return new GeoJsonLineString(toPoints((ArrayNode) node));
}
}
/**
* {@link JsonDeserializer} converting GeoJSON representation of {@literal Point}.
*
* <pre>
* <code>
* { "type": "Point", "coordinates": [10.0, 20.0] }
* </code>
* </pre>
*
* @author Christoph Strobl
* @since 1.7
*/
static class GeoJsonPointDeserializer extends GeoJsonDeserializer<GeoJsonPoint> {
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.geo.GeoJsonModule.GeoJsonDeserializer#doDeserialize(com.fasterxml.jackson.databind.node.ArrayNode)
*/
@Override
protected GeoJsonPoint doDeserialize(ArrayNode coordinates) {
return toGeoJsonPoint(coordinates);
}
}
/**
* {@link JsonDeserializer} converting GeoJSON representation of {@literal LineString}.
*
* <pre>
* <code>
* {
* "type": "LineString",
* "coordinates": [
* [10.0, 20.0], [30.0, 40.0], [50.0, 60.0]
* ]
* }
* </code>
* </pre>
*
* @author Christoph Strobl
* @since 1.7
*/
static class GeoJsonLineStringDeserializer extends GeoJsonDeserializer<GeoJsonLineString> {
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.geo.GeoJsonModule.GeoJsonDeserializer#doDeserialize(com.fasterxml.jackson.databind.node.ArrayNode)
*/
@Override
protected GeoJsonLineString doDeserialize(ArrayNode coordinates) {
return new GeoJsonLineString(toPoints(coordinates));
}
}
/**
* {@link JsonDeserializer} converting GeoJSON representation of {@literal MultiPoint}.
*
* <pre>
* <code>
* {
* "type": "MultiPoint",
* "coordinates": [
* [10.0, 20.0], [30.0, 40.0], [50.0, 60.0]
* ]
* }
* </code>
* </pre>
*
* @author Christoph Strobl
* @since 1.7
*/
static class GeoJsonMultiPointDeserializer extends GeoJsonDeserializer<GeoJsonMultiPoint> {
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.geo.GeoJsonModule.GeoJsonDeserializer#doDeserialize(com.fasterxml.jackson.databind.node.ArrayNode)
*/
@Override
protected GeoJsonMultiPoint doDeserialize(ArrayNode coordinates) {
return new GeoJsonMultiPoint(toPoints(coordinates));
}
}
/**
* {@link JsonDeserializer} converting GeoJSON representation of {@literal MultiLineString}.
*
* <pre>
* <code>
* {
* "type": "MultiLineString",
* "coordinates": [
* [ [10.0, 20.0], [30.0, 40.0] ],
* [ [50.0, 60.0] , [70.0, 80.0] ]
* ]
* }
* </code>
* </pre>
*
* @author Christoph Strobl
* @since 1.7
*/
static class GeoJsonMultiLineStringDeserializer extends GeoJsonDeserializer<GeoJsonMultiLineString> {
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.geo.GeoJsonModule.GeoJsonDeserializer#doDeserialize(com.fasterxml.jackson.databind.node.ArrayNode)
*/
@Override
protected GeoJsonMultiLineString doDeserialize(ArrayNode coordinates) {
List<GeoJsonLineString> lines = new ArrayList<GeoJsonLineString>(coordinates.size());
for (JsonNode lineString : coordinates) {
if (lineString.isArray()) {
lines.add(toLineString((ArrayNode) lineString));
}
}
return new GeoJsonMultiLineString(lines);
}
}
/**
* {@link JsonDeserializer} converting GeoJSON representation of {@literal Polygon}.
*
* <pre>
* <code>
* {
* "type": "Polygon",
* "coordinates": [
* [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ]
* ]
* }
* </code>
* </pre>
*
* @author Christoph Strobl
* @since 1.7
*/
static class GeoJsonPolygonDeserializer extends GeoJsonDeserializer<GeoJsonPolygon> {
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.geo.GeoJsonModule.GeoJsonDeserializer#doDeserialize(com.fasterxml.jackson.databind.node.ArrayNode)
*/
@Override
protected GeoJsonPolygon doDeserialize(ArrayNode coordinates) {
for (JsonNode ring : coordinates) {
// currently we do not support holes in polygons.
GeoJsonPolygon polygon = new GeoJsonPolygon(toPoints((ArrayNode) ring));
return polygon;
}
return null;
}
}
/**
* {@link JsonDeserializer} converting GeoJSON representation of {@literal MultiPolygon}.
*
* <pre>
* <code>
* {
* "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]]]
* ]
* }
* </code>
* </pre>
*
* @author Christoph Strobl
* @since 1.7
*/
static class GeoJsonMultiPolygonDeserializer extends GeoJsonDeserializer<GeoJsonMultiPolygon> {
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.geo.GeoJsonModule.GeoJsonDeserializer#doDeserialize(com.fasterxml.jackson.databind.node.ArrayNode)
*/
@Override
protected GeoJsonMultiPolygon doDeserialize(ArrayNode coordinates) {
List<GeoJsonPolygon> polygones = new ArrayList<GeoJsonPolygon>(coordinates.size());
for (JsonNode polygon : coordinates) {
for (JsonNode ring : (ArrayNode) polygon) {
polygones.add(new GeoJsonPolygon(toPoints((ArrayNode) ring)));
}
}
return new GeoJsonMultiPolygon(polygones);
}
}
}

View File

@@ -0,0 +1,137 @@
/*
* Copyright 2015 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
*
* 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.springframework.data.mongodb.core.geo;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.io.IOException;
import java.util.Arrays;
import org.junit.Before;
import org.junit.Test;
import org.springframework.data.geo.Point;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* @author Christoph Strobl
*/
public class GeoJsonModuleUnitTests {
ObjectMapper mapper;
@Before
public void setUp() {
mapper = new ObjectMapper();
mapper.registerModule(new GeoJsonModule());
}
/**
* @see DATAMONGO-1181
*/
@Test
public void shouldDeserializeJsonPointCorrectly() throws JsonParseException, JsonMappingException, IOException {
String json = "{ \"type\": \"Point\", \"coordinates\": [10.0, 20.0] }";
assertThat(mapper.readValue(json, GeoJsonPoint.class), is(new GeoJsonPoint(10D, 20D)));
}
/**
* @see DATAMONGO-1181
*/
@Test
public void shouldDeserializeGeoJsonLineStringCorrectly() throws JsonParseException, JsonMappingException,
IOException {
String json = "{ \"type\": \"LineString\", \"coordinates\": [ [10.0, 20.0], [30.0, 40.0], [50.0, 60.0] ]}";
assertThat(mapper.readValue(json, GeoJsonLineString.class),
is(new GeoJsonLineString(Arrays.asList(new Point(10, 20), new Point(30, 40), new Point(50, 60)))));
}
/**
* @see DATAMONGO-1181
*/
@Test
public void shouldDeserializeGeoJsonMultiPointCorrectly() throws JsonParseException, JsonMappingException,
IOException {
String json = "{ \"type\": \"MultiPoint\", \"coordinates\": [ [10.0, 20.0], [30.0, 40.0], [50.0, 60.0] ]}";
assertThat(mapper.readValue(json, GeoJsonLineString.class),
is(new GeoJsonMultiPoint(Arrays.asList(new Point(10, 20), new Point(30, 40), new Point(50, 60)))));
}
/**
* @see DATAMONGO-1181
*/
@Test
@SuppressWarnings("unchecked")
public void shouldDeserializeGeoJsonMultiLineStringCorrectly() throws JsonParseException, JsonMappingException,
IOException {
String json = "{ \"type\": \"MultiLineString\", \"coordinates\": [ [ [10.0, 20.0], [30.0, 40.0] ], [ [50.0, 60.0] , [70.0, 80.0] ] ]}";
assertThat(
mapper.readValue(json, GeoJsonMultiLineString.class),
is(new GeoJsonMultiLineString(Arrays.asList(new Point(10, 20), new Point(30, 40)), Arrays.asList(new Point(50,
60), new Point(70, 80)))));
}
/**
* @see DATAMONGO-1181
*/
@Test
public void shouldDeserializeGeoJsonPolygonCorrectly() throws JsonParseException, JsonMappingException, 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] ] ]}";
assertThat(
mapper.readValue(json, GeoJsonPolygon.class),
is(new GeoJsonPolygon(Arrays.asList(new Point(100, 0), new Point(101, 0), new Point(101, 1), new Point(100, 1),
new Point(100, 0)))));
}
/**
* @see DATAMONGO-1181
*/
@Test
public void shouldDeserializeGeoJsonMultiPolygonCorrectly() throws JsonParseException, JsonMappingException,
IOException {
String json = "{ \"type\": \"Polygon\", \"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]]]"//
+ "]}";
assertThat(
mapper.readValue(json, GeoJsonMultiPolygon.class),
is(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)))))));
}
}

View File

@@ -7,6 +7,7 @@ Import-Package:
Export-Template:
org.springframework.data.mongodb.*;version="${project.version}"
Import-Template:
com.fasterxml.jackson.*;version="${jackson:[=.=.=,+1.0.0)}";resolution:=optional,
com.google.common.base.*;version="[11.0.0,14.0.0)";resolution:=optional,
com.mongodb.*;version="${mongo.osgi:[=.=.=,+1.0.0)}",
com.mysema.query.*;version="[2.1.1, 3.0.0)";resolution:=optional,