View Javadoc
1   /*******************************************************************************
2    * Copyright (c) 2015 VoyagerSearch
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;
10  
11  import org.locationtech.spatial4j.context.SpatialContext;
12  import org.locationtech.spatial4j.context.SpatialContextFactory;
13  import org.locationtech.spatial4j.distance.DistanceUtils;
14  import org.locationtech.spatial4j.shape.*;
15  import org.locationtech.spatial4j.shape.impl.BufferedLine;
16  import org.locationtech.spatial4j.shape.impl.BufferedLineString;
17  import org.locationtech.spatial4j.shape.impl.GeoCircle;
18  
19  import java.io.IOException;
20  import java.io.StringWriter;
21  import java.io.Writer;
22  import java.text.NumberFormat;
23  import java.util.Iterator;
24  
25  import static org.locationtech.spatial4j.io.GeoJSONReader.BUFFER;
26  import static org.locationtech.spatial4j.io.GeoJSONReader.BUFFER_UNITS;
27  
28  public class GeoJSONWriter implements ShapeWriter {
29  
30    public GeoJSONWriter(SpatialContext ctx, SpatialContextFactory factory) {
31  
32    }
33  
34    @Override
35    public String getFormatName() {
36      return ShapeIO.GeoJSON;
37    }
38  
39    protected void write(Writer output, NumberFormat nf, double... coords) throws IOException {
40      output.write('[');
41      for (int i = 0; i < coords.length; i++) {
42        if (Double.isNaN(coords[i])) {
43          break; // empty point or no more coordinates
44        }
45        if (i > 0) {
46          output.append(',');
47        }
48        output.append(nf.format(coords[i]));
49      }
50      output.write(']');
51    }
52  
53    @Override
54    public void write(Writer output, Shape shape) throws IOException {
55      if (shape == null) {
56        throw new NullPointerException("Shape can not be null");
57      }
58      NumberFormat nf = LegacyShapeWriter.makeNumberFormat(6);
59      if (shape instanceof Point) {
60        Point v = (Point) shape;
61        output.append("{\"type\":\"Point\",\"coordinates\":");
62        write(output, nf, v.getX(), v.getY());
63        output.append('}');
64        return;
65      }
66      if (shape instanceof Rectangle) {
67        Rectangle v = (Rectangle) shape;
68        output.append("{\"type\":\"Polygon\",\"coordinates\":[[");
69        write(output, nf, v.getMinX(), v.getMinY());
70        output.append(',');
71        write(output, nf, v.getMinX(), v.getMaxY());
72        output.append(',');
73        write(output, nf, v.getMaxX(), v.getMaxY());
74        output.append(',');
75        write(output, nf, v.getMaxX(), v.getMinY());
76        output.append(',');
77        write(output, nf, v.getMinX(), v.getMinY());
78        output.append("]]}");
79        return;
80      }
81      if (shape instanceof BufferedLine) {
82        BufferedLine v = (BufferedLine) shape;
83        output.append("{\"type\":\"LineString\",\"coordinates\":[");
84        write(output, nf, v.getA().getX(), v.getA().getY());
85        output.append(',');
86        write(output, nf, v.getB().getX(), v.getB().getY());
87        output.append(',');
88        output.append("]");
89        if (v.getBuf() > 0) {
90          output.append(',');
91          output.append("\"buffer\":");
92          output.append(nf.format(v.getBuf()));
93        }
94        output.append('}');
95        return;
96      }
97      if (shape instanceof BufferedLineString) {
98        BufferedLineString v = (BufferedLineString) shape;
99        output.append("{\"type\":\"LineString\",\"coordinates\":[");
100       BufferedLine last = null;
101       Iterator<BufferedLine> iter = v.getSegments().iterator();
102       while (iter.hasNext()) {
103         BufferedLine seg = iter.next();
104         if (last != null) {
105           output.append(',');
106         }
107         write(output, nf, seg.getA().getX(), seg.getA().getY());
108         last = seg;
109       }
110       if (last != null) {
111         output.append(',');
112         write(output, nf, last.getB().getX(), last.getB().getY());
113       }
114       output.append("]");
115       if (v.getBuf() > 0) {
116         writeDistance(output, nf, v.getBuf(), shape.getContext().isGeo(), BUFFER, BUFFER_UNITS);
117       }
118       output.append('}');
119       return;
120     }
121     if (shape instanceof Circle) {
122       // See: https://github.com/geojson/geojson-spec/wiki/Proposal---Circles-and-Ellipses-Geoms
123       Circle v = (Circle) shape;
124       Point center = v.getCenter();
125       output.append("{\"type\":\"Circle\",\"coordinates\":");
126       write(output, nf, center.getX(), center.getY());
127       writeDistance(output, nf, v.getRadius(), v instanceof GeoCircle, "radius", "radius_units");
128       output.append("}");
129       return;
130     }
131     if (shape instanceof ShapeCollection) {
132       ShapeCollection v = (ShapeCollection) shape;
133       output.append("{\"type\":\"GeometryCollection\",\"geometries\":[");
134       for (int i = 0; i < v.size(); i++) {
135         if (i > 0) {
136           output.append(',');
137         }
138         write(output, v.get(i));
139       }
140       output.append("]}");
141       return;
142     }
143     output.append("{\"type\":\"Unknown\",\"wkt\":\"");
144     output.append(LegacyShapeWriter.writeShape(shape));
145     output.append("\"}");
146   }
147 
148   /**
149    * Helper method to encode a distance property (with optional unit). 
150    * <p>
151    *  The distance unit is only encoded when <tt>isGeo</tt> is true, and it is converted to km. 
152    * </p>
153    * <p>
154    *  The distance unit is encoded within a properties object.
155    * </p>
156    * @param output The writer.
157    * @param nf The number format.
158    * @param dist The distance value to encode.
159    * @param isGeo The flag determining 
160    * @param distProperty The distance property name.
161    * @param distUnitsProperty The distance unit property name.
162    */
163   void writeDistance(Writer output, NumberFormat nf, double dist, boolean isGeo, String distProperty, String distUnitsProperty) 
164       throws IOException {
165     output.append(",\"").append(distProperty).append("\":");
166     if (isGeo) {
167       double distKm =
168           DistanceUtils.degrees2Dist(dist, DistanceUtils.EARTH_MEAN_RADIUS_KM);
169       output.append(nf.format(distKm));
170       output.append(",\"properties\":{");
171       output.append("\"").append(distUnitsProperty).append("\":\"km\"}");
172     } else {
173       output.append(nf.format(dist));
174     }
175   }
176 
177   @Override
178   public String toString(Shape shape) {
179     try {
180       StringWriter buffer = new StringWriter();
181       write(buffer, shape);
182       return buffer.toString();
183     } catch (IOException ex) {
184       throw new RuntimeException(ex);
185     }
186   }
187 }