View Javadoc
1   /*******************************************************************************
2    * Copyright (c) 2015 VoyagerSearch and others
3    * All rights reserved. This program and the accompanying materials
4    * are made available under the terms of the Apache License, Version 2.0 which
5    * accompanies this distribution and is available at
6    *    http://www.apache.org/licenses/LICENSE-2.0.txt
7    ******************************************************************************/
8   
9   package org.locationtech.spatial4j.io.jackson;
10  
11  import java.io.IOException;
12  import org.locationtech.spatial4j.context.SpatialContext;
13  import org.locationtech.spatial4j.context.jts.JtsSpatialContext;
14  import org.locationtech.spatial4j.distance.DistanceUtils;
15  import org.locationtech.spatial4j.shape.Point;
16  import org.locationtech.spatial4j.shape.Shape;
17  import org.locationtech.spatial4j.shape.ShapeFactory;
18  
19  import com.fasterxml.jackson.core.JsonParseException;
20  import com.fasterxml.jackson.core.JsonParser;
21  import com.fasterxml.jackson.core.JsonProcessingException;
22  import com.fasterxml.jackson.core.JsonToken;
23  import com.fasterxml.jackson.databind.DeserializationContext;
24  import com.fasterxml.jackson.databind.JsonDeserializer;
25  import com.fasterxml.jackson.databind.node.ArrayNode;
26  import com.fasterxml.jackson.databind.node.ObjectNode;
27  
28  public class ShapeDeserializer extends JsonDeserializer<Shape>
29  {
30    public final SpatialContext ctx;
31  
32    public ShapeDeserializer() {
33      this(JtsSpatialContext.GEO);
34    }
35    
36    public ShapeDeserializer(SpatialContext ctx) {
37      this.ctx = ctx;
38    }
39    
40    public Point readPoint(ArrayNode arr, ShapeFactory factory) {
41      if(arr.size()==0) {
42        return factory.pointXY(Double.NaN, Double.NaN);
43      }
44      double x = arr.get(0).asDouble();
45      double y = arr.get(1).asDouble();
46      if(arr.size()==3) {
47        double z = arr.get(3).asDouble();
48        return factory.pointXYZ(x, y, z);
49      }
50      return factory.pointXY(x, y);
51    }
52    
53    private void fillPoints( ShapeFactory.PointsBuilder b, ArrayNode arrs ) {
54      for(int i=0; i<arrs.size(); i++) {
55        ArrayNode arr = (ArrayNode)arrs.get(i);
56        
57        double x = arr.get(0).asDouble();
58        double y = arr.get(1).asDouble();
59        if(arr.size()==3) {
60          double z = arr.get(3).asDouble();
61          b.pointXYZ(x, y, z);
62        }
63        else {
64          b.pointXY(x, y);
65        }
66      }
67    }
68    
69    private void fillPolygon(ShapeFactory.PolygonBuilder b, ArrayNode arr ) {
70      ArrayNode coords = (ArrayNode)arr.get(0);
71      for(int i=0; i<coords.size(); i++) {
72        ArrayNode n = (ArrayNode)coords.get(i);
73        double x = n.get(0).asDouble();
74        double y = n.get(1).asDouble();
75        if(n.size()==3) {
76          double z = n.get(2).asDouble();
77          b.pointXYZ(x, y, z);
78        }
79        else {
80          b.pointXY(x, y);
81        }
82      }
83      
84      // Now add the holes
85      for(int h=1; h<arr.size(); h++) {
86        ShapeFactory.PolygonBuilder.HoleBuilder hole = b.hole();
87        coords = (ArrayNode)arr.get(h);
88        for(int i=0; i<coords.size(); i++) {
89          ArrayNode n = (ArrayNode)coords.get(i);
90          double x = n.get(0).asDouble();
91          double y = n.get(1).asDouble();
92          if(n.size()==3) {
93            double z = n.get(2).asDouble();
94            hole.pointXYZ(x, y, z);
95          }
96          else {
97            hole.pointXY(x, y);
98          }
99        }
100       hole.endHole();
101     }
102   }
103 
104   public Shape read(ObjectNode node, ShapeFactory factory) throws IOException {
105 
106     if(!node.has("type")) {
107       throw new IllegalArgumentException("Missing 'type'");
108     }
109     
110     String type = node.get("type").asText();
111     if(node.has("geometries")) {
112       if(!"GeometryCollection".equals(type)) {
113         throw new IllegalArgumentException("Geometries are only expected for GeometryCollections");
114       }
115 
116       ShapeFactory.MultiShapeBuilder<Shape> b = factory.multiShape(Shape.class);
117       ArrayNode arr = (ArrayNode)node.get("geometries");
118       for(int i=0; i<arr.size(); i++) {
119         b.add( read((ObjectNode)arr.get(i), factory ) );
120       }
121       return b.build();
122     }
123     
124     ObjectNode props = (ObjectNode)node.get("properties");
125     ArrayNode arr = (ArrayNode)node.get("coordinates");
126     
127     
128     if("Point".equals(type)) {
129       if(props!=null) {
130         throw new IllegalArgumentException("we don't support props on points...");
131       }
132       return readPoint(arr, factory);
133     }
134     if("MultiPoint".equals(type)) {
135       if(props!=null) {
136         throw new IllegalArgumentException("we don't support props on points...");
137       }
138       
139       ShapeFactory.MultiPointBuilder b = factory.multiPoint();
140       fillPoints(b, arr);
141       return b.build();
142     }
143     
144     boolean isMultiLine = "MultiLineString".equals(type);
145     if(isMultiLine || "LineString".equals(type)) {
146       double buffer = 0;
147       if(node.has(ShapeAsGeoJSONSerializer.BUFFER)) {
148         buffer = node.get(ShapeAsGeoJSONSerializer.BUFFER).asDouble();
149         if(props!=null) {
150           if("km".equals(props.get(ShapeAsGeoJSONSerializer.BUFFER_UNITS).asText())) {
151             buffer = DistanceUtils.dist2Degrees(buffer, DistanceUtils.EARTH_MEAN_RADIUS_KM);
152           }
153         }
154       }
155       if(isMultiLine) {
156         ShapeFactory.MultiLineStringBuilder builder = factory.multiLineString();
157         for(int i=0; i<arr.size(); i++) {
158           ShapeFactory.LineStringBuilder b = builder.lineString();
159           fillPoints(b, (ArrayNode)arr.get(i));
160           b.buffer(buffer);
161           builder.add(b);
162         }
163         return builder.build();
164       }
165       
166       ShapeFactory.LineStringBuilder builder = factory.lineString();
167       fillPoints(builder, arr);
168       builder.buffer(buffer);
169       return builder.build();
170     }
171     
172     if("Polygon".equals(type)) {
173       ShapeFactory.PolygonBuilder b = factory.polygon();
174       fillPolygon(b, arr);
175       return b.buildOrRect();
176     }
177 
178     if("MultiPolygon".equals(type)) {
179       ShapeFactory.MultiPolygonBuilder buildier = factory.multiPolygon();
180       for(int i=0; i<arr.size(); i++) {
181         ShapeFactory.PolygonBuilder b = buildier.polygon();
182         fillPolygon(b, (ArrayNode)arr.get(i));
183         buildier.add(b);
184       }
185       return buildier.build();
186     }
187 
188     if("Circle".equals(type)) {
189       double radius = 0;
190       if(node.has("radius")) {
191         radius = node.get("radius").asDouble();
192         if(props!=null) {
193           if("km".equals(props.get("radius_units").asText())) {
194             radius = DistanceUtils.dist2Degrees(radius, DistanceUtils.EARTH_MEAN_RADIUS_KM);
195           }
196         }
197       }
198       return factory.circle(readPoint(arr, factory), radius);
199     }
200 
201     throw new IllegalArgumentException("Unsupported type: "+type);
202   }
203   
204   
205   
206   public Shape read(JsonParser jp, ShapeFactory factory) throws IOException {
207     if(!jp.getCurrentToken().isStructStart()) {
208       throw new JsonParseException(jp, "Expect the start of GeoJSON Geometry object");
209     }
210     
211     return read( (ObjectNode)jp.getCodec().readTree(jp), factory );
212   }
213   
214   @Override
215   public Shape deserialize(JsonParser jp, DeserializationContext ctxt)
216       throws IOException, JsonProcessingException {
217     
218     JsonToken t = jp.getCurrentToken();
219     if(t.isStructStart()) {
220       return read( jp, ctx.getShapeFactory() );
221     }
222     if (t.isScalarValue()) {
223       String txt = jp.getValueAsString();
224       if(txt!=null && txt.length()>0) {
225         try {
226           return ctx.getFormats().read(txt);
227         } catch (Exception e) {
228           throw new JsonParseException(jp, "error reading shape", e);
229         }
230       }
231       return null; // empty string
232     }
233     throw new JsonParseException(jp, "can't read GeoJSON yet");
234   }
235 }