ShapeDeserializer.java
/*******************************************************************************
* Copyright (c) 2015 VoyagerSearch and others
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Apache License, Version 2.0 which
* accompanies this distribution and is available at
* http://www.apache.org/licenses/LICENSE-2.0.txt
******************************************************************************/
package org.locationtech.spatial4j.io.jackson;
import java.io.IOException;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.context.jts.JtsSpatialContext;
import org.locationtech.spatial4j.distance.DistanceUtils;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Shape;
import org.locationtech.spatial4j.shape.ShapeFactory;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
public class ShapeDeserializer extends JsonDeserializer<Shape>
{
public final SpatialContext ctx;
public ShapeDeserializer() {
this(JtsSpatialContext.GEO);
}
public ShapeDeserializer(SpatialContext ctx) {
this.ctx = ctx;
}
public Point readPoint(ArrayNode arr, ShapeFactory factory) {
if(arr.size()==0) {
return factory.pointXY(Double.NaN, Double.NaN);
}
double x = arr.get(0).asDouble();
double y = arr.get(1).asDouble();
if(arr.size()==3) {
double z = arr.get(2).asDouble();
return factory.pointXYZ(x, y, z);
}
return factory.pointXY(x, y);
}
private void fillPoints( ShapeFactory.PointsBuilder b, ArrayNode arrs ) {
for(int i=0; i<arrs.size(); i++) {
ArrayNode arr = (ArrayNode)arrs.get(i);
double x = arr.get(0).asDouble();
double y = arr.get(1).asDouble();
if(arr.size()==3) {
double z = arr.get(2).asDouble();
b.pointXYZ(x, y, z);
}
else {
b.pointXY(x, y);
}
}
}
private void fillPolygon(ShapeFactory.PolygonBuilder b, ArrayNode arr ) {
ArrayNode coords = (ArrayNode)arr.get(0);
for(int i=0; i<coords.size(); i++) {
ArrayNode n = (ArrayNode)coords.get(i);
double x = n.get(0).asDouble();
double y = n.get(1).asDouble();
if(n.size()==3) {
double z = n.get(2).asDouble();
b.pointXYZ(x, y, z);
}
else {
b.pointXY(x, y);
}
}
// Now add the holes
for(int h=1; h<arr.size(); h++) {
ShapeFactory.PolygonBuilder.HoleBuilder hole = b.hole();
coords = (ArrayNode)arr.get(h);
for(int i=0; i<coords.size(); i++) {
ArrayNode n = (ArrayNode)coords.get(i);
double x = n.get(0).asDouble();
double y = n.get(1).asDouble();
if(n.size()==3) {
double z = n.get(2).asDouble();
hole.pointXYZ(x, y, z);
}
else {
hole.pointXY(x, y);
}
}
hole.endHole();
}
}
public Shape read(ObjectNode node, ShapeFactory factory) throws IOException {
if(!node.has("type")) {
throw new IllegalArgumentException("Missing 'type'");
}
String type = node.get("type").asText();
if(node.has("geometries")) {
if(!"GeometryCollection".equals(type)) {
throw new IllegalArgumentException("Geometries are only expected for GeometryCollections");
}
ShapeFactory.MultiShapeBuilder<Shape> b = factory.multiShape(Shape.class);
ArrayNode arr = (ArrayNode)node.get("geometries");
for(int i=0; i<arr.size(); i++) {
b.add( read((ObjectNode)arr.get(i), factory ) );
}
return b.build();
}
ObjectNode props = (ObjectNode)node.get("properties");
ArrayNode arr = (ArrayNode)node.get("coordinates");
if("Point".equals(type)) {
if(props!=null) {
throw new IllegalArgumentException("we don't support props on points...");
}
return readPoint(arr, factory);
}
if("MultiPoint".equals(type)) {
if(props!=null) {
throw new IllegalArgumentException("we don't support props on points...");
}
ShapeFactory.MultiPointBuilder b = factory.multiPoint();
fillPoints(b, arr);
return b.build();
}
boolean isMultiLine = "MultiLineString".equals(type);
if(isMultiLine || "LineString".equals(type)) {
double buffer = 0;
if(node.has(ShapeAsGeoJSONSerializer.BUFFER)) {
buffer = node.get(ShapeAsGeoJSONSerializer.BUFFER).asDouble();
if(props!=null) {
if("km".equals(props.get(ShapeAsGeoJSONSerializer.BUFFER_UNITS).asText())) {
buffer = DistanceUtils.dist2Degrees(buffer, DistanceUtils.EARTH_MEAN_RADIUS_KM);
}
}
}
if(isMultiLine) {
ShapeFactory.MultiLineStringBuilder builder = factory.multiLineString();
for(int i=0; i<arr.size(); i++) {
ShapeFactory.LineStringBuilder b = builder.lineString();
fillPoints(b, (ArrayNode)arr.get(i));
b.buffer(buffer);
builder.add(b);
}
return builder.build();
}
ShapeFactory.LineStringBuilder builder = factory.lineString();
fillPoints(builder, arr);
builder.buffer(buffer);
return builder.build();
}
if("Polygon".equals(type)) {
ShapeFactory.PolygonBuilder b = factory.polygon();
fillPolygon(b, arr);
return b.buildOrRect();
}
if("MultiPolygon".equals(type)) {
ShapeFactory.MultiPolygonBuilder buildier = factory.multiPolygon();
for(int i=0; i<arr.size(); i++) {
ShapeFactory.PolygonBuilder b = buildier.polygon();
fillPolygon(b, (ArrayNode)arr.get(i));
buildier.add(b);
}
return buildier.build();
}
if("Circle".equals(type)) {
double radius = 0;
if(node.has("radius")) {
radius = node.get("radius").asDouble();
if(props!=null) {
if("km".equals(props.get("radius_units").asText())) {
radius = DistanceUtils.dist2Degrees(radius, DistanceUtils.EARTH_MEAN_RADIUS_KM);
}
}
}
return factory.circle(readPoint(arr, factory), radius);
}
throw new IllegalArgumentException("Unsupported type: "+type);
}
public Shape read(JsonParser jp, ShapeFactory factory) throws IOException {
if(!jp.getCurrentToken().isStructStart()) {
throw new JsonParseException(jp, "Expect the start of GeoJSON Geometry object");
}
return read( (ObjectNode)jp.getCodec().readTree(jp), factory );
}
@Override
public Shape deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonToken t = jp.getCurrentToken();
if(t.isStructStart()) {
return read( jp, ctx.getShapeFactory() );
}
if (t.isScalarValue()) {
String txt = jp.getValueAsString();
if(txt!=null && txt.length()>0) {
try {
return ctx.getFormats().read(txt);
} catch (Exception e) {
throw new JsonParseException(jp, "error reading shape", e);
}
}
return null; // empty string
}
throw new JsonParseException(jp, "can't read GeoJSON yet");
}
}