1
2
3
4
5
6
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
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;
232 }
233 throw new JsonParseException(jp, "can't read GeoJSON yet");
234 }
235 }