/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.wfs.json;
import java.io.Writer;
import java.util.Calendar;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import org.geotools.geometry.jts.coordinatesequence.CoordinateSequences;
import org.geotools.referencing.CRS;
import org.geotools.util.Converters;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.CoordinateSequence;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryCollection;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPoint;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.geom.impl.CoordinateArraySequence;
import net.sf.json.JSONException;
import net.sf.json.util.JSONBuilder;
/**
* This class extends the JSONBuilder to be able to write out geometric types. It is coded
* against the draft 5 version of the spec on http://geojson.org
*
* @author Chris Holmes, The Open Planning Project
* @version $Id$
*
*/
public class GeoJSONBuilder extends JSONBuilder {
private final Logger LOGGER = org.geotools.util.logging.Logging
.getLogger(this.getClass());
private CRS.AxisOrder axisOrder = CRS.AxisOrder.EAST_NORTH;
private int numDecimals = 6;
public GeoJSONBuilder(Writer w) {
super(w);
}
/**
* Writes any geometry object. This class figures out which geometry representation to write
* and calls subclasses to actually write the object.
* @param geometry The geometry to be encoded
* @return The JSONBuilder with the new geometry
* @throws JSONException If anything goes wrong
*/
public JSONBuilder writeGeom(Geometry geometry) throws JSONException {
this.object();
this.key("type");
this.value(getGeometryName(geometry));
final int geometryType = getGeometryType(geometry);
if (geometryType != MULTIGEOMETRY) {
this.key("coordinates");
switch (geometryType) {
case POINT:
Point point = (Point) geometry;
Coordinate c = point.getCoordinate();
writeCoordinate(c.x, c.y, c.z);
break;
case LINESTRING:
writeCoordinates(((LineString)geometry).getCoordinateSequence());
break;
case MULTIPOINT:
writeCoordinates(geometry.getCoordinates());
break;
case POLYGON:
writePolygon((Polygon) geometry);
break;
case MULTILINESTRING:
this.array();
for (int i = 0, n = geometry.getNumGeometries(); i < n; i++) {
writeCoordinates(((LineString)geometry.getGeometryN(i)).getCoordinateSequence());
}
this.endArray();
break;
case MULTIPOLYGON:
this.array();
for (int i = 0, n = geometry.getNumGeometries(); i < n; i++) {
writePolygon((Polygon) geometry.getGeometryN(i));
}
this.endArray();
break;
}
} else {
writeGeomCollection((GeometryCollection) geometry);
}
return this.endObject();
}
private JSONBuilder writeGeomCollection(GeometryCollection collection) {
this.key("geometries");
this.array();
for (int i = 0, n = collection.getNumGeometries(); i < n; i++) {
writeGeom(collection.getGeometryN(i));
}
return this.endArray();
}
private JSONBuilder writeCoordinates(Coordinate[] coords)
throws JSONException {
return writeCoordinates(new CoordinateArraySequence(coords));
}
/**
* Write the coordinates of a geometry
* @param coords The coordinates to write
* @return this
* @throws JSONException
*/
private JSONBuilder writeCoordinates(CoordinateSequence coords)
throws JSONException {
this.array();
// guess the dimension of the coordinate sequence
int dim = CoordinateSequences.coordinateDimension(coords);
final int coordCount = coords.size();
for (int i = 0; i < coordCount; i++) {
if(dim > 2) {
writeCoordinate(coords.getX(i), coords.getY(i), coords.getOrdinate(i, 2));
} else {
writeCoordinate(coords.getX(i), coords.getY(i));
}
}
return this.endArray();
}
private JSONBuilder writeCoordinate(double x, double y) {
return writeCoordinate(x, y, Double.NaN);
}
private JSONBuilder writeCoordinate(double x, double y, double z) {
this.array();
if(axisOrder==CRS.AxisOrder.NORTH_EAST){
roundedValue(y);
roundedValue(x);
} else {
roundedValue(x);
roundedValue(y);
}
if(!Double.isNaN(z)) {
roundedValue(z);
}
return this.endArray();
}
private void roundedValue(double value) {
super.value(RoundingUtil.round(value, numDecimals));
}
/**
* Turns an envelope into an array [minX,minY,maxX,maxY]
* @param env envelope representing bounding box
* @return this
*/
protected JSONBuilder writeBoundingBox(Envelope env) {
this.key("bbox");
this.array();
if(axisOrder==CRS.AxisOrder.NORTH_EAST) {
roundedValue(env.getMinY());
roundedValue(env.getMinX());
roundedValue(env.getMaxY());
roundedValue(env.getMaxX());
} else {
roundedValue(env.getMinX());
roundedValue(env.getMinY());
roundedValue(env.getMaxX());
roundedValue(env.getMaxY());
}
return this.endArray();
}
/**
* Writes a polygon
* @param geometry The polygon to write
* @throws JSONException
*/
private void writePolygon(Polygon geometry) throws JSONException {
this.array();
writeCoordinates(geometry.getExteriorRing().getCoordinateSequence());
for (int i = 0, ii = geometry.getNumInteriorRing(); i < ii; i++) {
writeCoordinates(geometry.getInteriorRingN(i).getCoordinateSequence());
}
this.endArray(); //end the linear ring
//this.endObject(); //end the
}
/** Internal representation of OGC SF Point */
protected static final int POINT = 1;
/** Internal representation of OGC SF LineString */
protected static final int LINESTRING = 2;
/** Internal representation of OGC SF Polygon */
protected static final int POLYGON = 3;
/** Internal representation of OGC SF MultiPoint */
protected static final int MULTIPOINT = 4;
/** Internal representation of OGC SF MultiLineString */
protected static final int MULTILINESTRING = 5;
/** Internal representation of OGC SF MultiPolygon */
protected static final int MULTIPOLYGON = 6;
/** Internal representation of OGC SF MultiGeometry */
protected static final int MULTIGEOMETRY = 7;
public static String getGeometryName(Geometry geometry) {
if (geometry instanceof Point) {
return "Point";
} else if (geometry instanceof LineString) {
return "LineString";
} else if (geometry instanceof Polygon) {
return "Polygon";
} else if (geometry instanceof MultiPoint) {
return "MultiPoint";
} else if (geometry instanceof MultiLineString) {
return "MultiLineString";
} else if (geometry instanceof MultiPolygon) {
return "MultiPolygon";
} else if (geometry instanceof GeometryCollection) {
return "GeometryCollection";
} else {
throw new IllegalArgumentException("Unknown geometry type " + geometry.getClass());
}
}
/**
* Gets the internal representation for the given Geometry
*
* @param geometry a Geometry
*
* @return int representation of Geometry
*/
public static int getGeometryType(Geometry geometry) {
//LOGGER.entering("GMLUtils", "getGeometryType", geometry);
if (geometry instanceof Point) {
//LOGGER.finest("found point");
return POINT;
} else if (geometry instanceof LineString) {
//LOGGER.finest("found linestring");
return LINESTRING;
} else if (geometry instanceof Polygon) {
//LOGGER.finest("found polygon");
return POLYGON;
} else if (geometry instanceof MultiPoint) {
//LOGGER.finest("found multiPoint");
return MULTIPOINT;
} else if (geometry instanceof MultiLineString) {
return MULTILINESTRING;
} else if (geometry instanceof MultiPolygon) {
return MULTIPOLYGON;
} else if (geometry instanceof GeometryCollection) {
return MULTIGEOMETRY;
} else {
throw new IllegalArgumentException(
"Unable to determine geometry type " + geometry.getClass());
}
}
/**
* Write a java.util.List out as a JSON Array. The values of the array will be converted
* using ike standard primitive conversions. If the list contains List or Map objects, they
* will be serialized as JSON Arrays and JSON Objects respectively.
*
* @param list a java.util.List to be serialized as JSON Array
*/
public JSONBuilder writeList(final List list) {
this.array();
for (final Object o: list) {
this.value(o);
}
return this.endArray();
}
/**
* Write a java.util.Map out as a JSON Object. Keys are serialized using the toString method
* of the object and values are serialized using primitives conversions. If a value in the map
* is a List or Map object, it will be serialized as JSON Array or JSON Object respectively.
*
* @param map a java.util.Map object to be serialized as a JSON Object
*/
public JSONBuilder writeMap(final Map map) {
this.object();
for (final Object k: map.keySet()) {
this.key(k.toString());
this.value(map.get(k));
}
return this.endObject();
}
/**
* Overrides handling of specialized types.
*
* Overrides the encoding {@code java.util.Date} and its date/time/timestamp
* descendants, as well as {@code java.util.Calendar} instances as ISO 8601 strings.
* In addition handles rounding numbers to the specified number of decimal points.
*
* Overrides the handling of java.util.Map, java.util.List, and Geometry objects
* as well.
*
* @see net.sf.json.util.JSONBuilder#value(java.lang.Object)
*/
@Override
public GeoJSONBuilder value(Object value) {
if (value == null) {
super.value(value);
} else if (value instanceof Geometry) {
this.writeGeom((Geometry) value);
} else if (value instanceof List) {
this.writeList((List)value);
} else if (value instanceof Map) {
this.writeMap((Map)value);
} else {
if (value instanceof java.util.Date || value instanceof Calendar) {
value = Converters.convert(value, String.class);
}
super.value(value);
}
return this;
}
/**
* Set the axis order to assume all input will be provided in. Has no effect on geometries
* that have already been written.
* @param axisOrder
*/
public void setAxisOrder(CRS.AxisOrder axisOrder) {
this.axisOrder = axisOrder;
}
public void setNumberOfDecimals(int numberOfDecimals) {
this.numDecimals = numberOfDecimals;
}
}