/*******************************************************************************
* Copyright (c) 2015 VoyagerSearch
* 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;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.context.SpatialContextFactory;
import org.locationtech.spatial4j.distance.DistanceUtils;
import org.locationtech.spatial4j.shape.*;
import org.locationtech.spatial4j.shape.impl.BufferedLine;
import org.locationtech.spatial4j.shape.impl.BufferedLineString;
import org.locationtech.spatial4j.shape.impl.GeoCircle;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.text.NumberFormat;
import java.util.Iterator;
import static org.locationtech.spatial4j.io.GeoJSONReader.BUFFER;
import static org.locationtech.spatial4j.io.GeoJSONReader.BUFFER_UNITS;
public class GeoJSONWriter implements ShapeWriter {
public GeoJSONWriter(SpatialContext ctx, SpatialContextFactory factory) {
}
@Override
public String getFormatName() {
return ShapeIO.GeoJSON;
}
protected void write(Writer output, NumberFormat nf, double... coords) throws IOException {
output.write('[');
for (int i = 0; i < coords.length; i++) {
if (i > 0) {
output.append(',');
}
output.append(nf.format(coords[i]));
}
output.write(']');
}
@Override
public void write(Writer output, Shape shape) throws IOException {
if (shape == null) {
throw new NullPointerException("Shape can not be null");
}
NumberFormat nf = LegacyShapeWriter.makeNumberFormat(6);
if (shape instanceof Point) {
Point v = (Point) shape;
output.append("{\"type\":\"Point\",\"coordinates\":");
write(output, nf, v.getX(), v.getY());
output.append('}');
return;
}
if (shape instanceof Rectangle) {
Rectangle v = (Rectangle) shape;
output.append("{\"type\":\"Polygon\",\"coordinates\": [[");
write(output, nf, v.getMinX(), v.getMinY());
output.append(',');
write(output, nf, v.getMinX(), v.getMaxY());
output.append(',');
write(output, nf, v.getMaxX(), v.getMaxY());
output.append(',');
write(output, nf, v.getMaxX(), v.getMinY());
output.append(',');
write(output, nf, v.getMinX(), v.getMinY());
output.append("]]}");
return;
}
if (shape instanceof BufferedLine) {
BufferedLine v = (BufferedLine) shape;
output.append("{\"type\":\"LineString\",\"coordinates\": [");
write(output, nf, v.getA().getX(), v.getA().getY());
output.append(',');
write(output, nf, v.getB().getX(), v.getB().getY());
output.append(',');
output.append("]");
if (v.getBuf() > 0) {
output.append(',');
output.append("\"buffer\":");
output.append(nf.format(v.getBuf()));
}
output.append('}');
return;
}
if (shape instanceof BufferedLineString) {
BufferedLineString v = (BufferedLineString) shape;
output.append("{\"type\":\"LineString\",\"coordinates\": [");
BufferedLine last = null;
Iterator<BufferedLine> iter = v.getSegments().iterator();
while (iter.hasNext()) {
BufferedLine seg = iter.next();
if (last != null) {
output.append(',');
}
write(output, nf, seg.getA().getX(), seg.getA().getY());
last = seg;
}
if (last != null) {
output.append(',');
write(output, nf, last.getB().getX(), last.getB().getY());
}
output.append("]");
if (v.getBuf() > 0) {
writeDistance(output, nf, v.getBuf(), shape.getContext().isGeo(), BUFFER, BUFFER_UNITS);
}
output.append('}');
return;
}
if (shape instanceof Circle) {
// See: https://github.com/geojson/geojson-spec/wiki/Proposal---Circles-and-Ellipses-Geoms
Circle v = (Circle) shape;
Point center = v.getCenter();
output.append("{\"type\":\"Circle\",\"coordinates\":");
write(output, nf, center.getX(), center.getY());
writeDistance(output, nf, v.getRadius(), v instanceof GeoCircle, "radius", "radius_units");
output.append("}");
return;
}
if (shape instanceof ShapeCollection) {
ShapeCollection v = (ShapeCollection) shape;
output.append("{\"type\":\"GeometryCollection\",\"geometries\": [");
for (int i = 0; i < v.size(); i++) {
if (i > 0) {
output.append(',');
}
write(output, v.get(i));
}
output.append("]}");
return;
}
output.append("{\"type\":\"Unknown\",\"wkt\":\"");
output.append(LegacyShapeWriter.writeShape(shape));
output.append("\"}");
}
/**
* Helper method to encode a distance property (with optional unit).
* <p>
* The distance unit is only encoded when <tt>isGeo</tt> is true, and it is converted to km.
* </p>
* <p>
* The distance unit is encoded within a properties object.
* </p>
* @param output The writer.
* @param nf The number format.
* @param dist The distance value to encode.
* @param isGeo The flag determining
* @param distProperty The distance property name.
* @param distUnitsProperty The distance unit property name.
*/
void writeDistance(Writer output, NumberFormat nf, double dist, boolean isGeo, String distProperty, String distUnitsProperty)
throws IOException {
output.append(",\"").append(distProperty).append("\":");
if (isGeo) {
double distKm =
DistanceUtils.degrees2Dist(dist, DistanceUtils.EARTH_MEAN_RADIUS_KM);
output.append(nf.format(distKm));
output.append(",\"properties\":{");
output.append("\"").append(distUnitsProperty).append("\":\"km\"}");
} else {
output.append(nf.format(dist));
}
}
@Override
public String toString(Shape shape) {
try {
StringWriter buffer = new StringWriter();
write(buffer, shape);
return buffer.toString();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}