/*
* This is part of Geomajas, a GIS framework, http://www.geomajas.org/.
*
* Copyright 2008-2011 Geosparc nv, http://www.geosparc.com/, Belgium.
*
* The program is available in open source according to the GNU Affero
* General Public License. All contributions in this program are covered
* by the Geomajas Contributors License Agreement. For full licensing
* details, see LICENSE.txt in the project root.
*/
package org.geomajas.dojo.server.json;
import com.metaparadigm.jsonrpc.AbstractSerializer;
import com.metaparadigm.jsonrpc.MarshallException;
import com.metaparadigm.jsonrpc.ObjectMatch;
import com.metaparadigm.jsonrpc.SerializerState;
import com.metaparadigm.jsonrpc.UnmarshallException;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
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.PrecisionModel;
import com.vividsolutions.jts.geom.impl.CoordinateArraySequence;
import org.json.JSONArray;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Serialize Geometry dtos to/from JSON.
*
* @author Jan De Moerloose
* @author Pieter De Graef
*/
public class GeometrySerializer extends AbstractSerializer {
private final Logger log = LoggerFactory.getLogger(GeometrySerializer.class);
private static String ATTRIBUTE_TYPE = "type";
private static String ATTRIBUTE_SRID = "srid";
private static String ATTRIBUTE_PRECISION = "precision";
private static String ATTRIBUTE_COORDINATES = "coordinates";
private static final long serialVersionUID = 1;
private static Class[] SERIALIZABLE_CLASSES = new Class[] {
Point.class,
LineString.class,
Polygon.class,
LinearRing.class,
};
private static Class[] JSON_CLASSES = new Class[] {JSONObject.class};
public Class[] getSerializableClasses() {
return SERIALIZABLE_CLASSES;
}
public Class[] getJSONClasses() {
return JSON_CLASSES;
}
public boolean canSerialize(Class clazz, Class jsonClazz) {
return (super.canSerialize(clazz, jsonClazz) ||
((jsonClazz == null || jsonClazz == JSONObject.class) && Geometry.class
.isAssignableFrom(clazz)));
}
public ObjectMatch tryUnmarshall(SerializerState state, Class clazz, Object o) throws UnmarshallException {
JSONObject jso = (JSONObject) o;
String type = jso.getString(ATTRIBUTE_TYPE);
if (type == null) {
throw new UnmarshallException("no type hint");
}
int srid = jso.getInt(ATTRIBUTE_SRID);
if (srid <= 0) {
throw new UnmarshallException("no srid");
}
int precision = jso.getInt(ATTRIBUTE_PRECISION);
if (precision <= 0) {
throw new UnmarshallException("no precision");
}
if (!(type.equals(org.geomajas.geometry.Geometry.POINT) ||
type.equals(org.geomajas.geometry.Geometry.LINE_STRING) ||
type.equals(org.geomajas.geometry.Geometry.POLYGON) ||
type.equals(org.geomajas.geometry.Geometry.LINEAR_RING) ||
type.equals(org.geomajas.geometry.Geometry.MULTI_LINE_STRING) ||
type.equals(org.geomajas.geometry.Geometry.MULTI_POLYGON))) {
throw new UnmarshallException(type + " is not a supported geometry");
}
JSONArray coordinates = jso.getJSONArray(ATTRIBUTE_COORDINATES);
if (coordinates == null) {
throw new UnmarshallException("coordinates missing");
}
return ObjectMatch.OKAY;
}
public Object unmarshall(SerializerState state, Class clazz, Object o) throws UnmarshallException {
JSONObject jso = (JSONObject) o;
String type = jso.getString(ATTRIBUTE_TYPE);
if (type == null) {
throw new UnmarshallException("no type hint");
}
int srid = jso.getInt(ATTRIBUTE_SRID);
int precision = jso.getInt(ATTRIBUTE_PRECISION);
GeometryFactory factory = new GeometryFactory(new PrecisionModel(Math.pow(10, precision)), srid);
Geometry geometry = null;
if (type.equals(org.geomajas.geometry.Geometry.POINT)) {
geometry = createPoint(factory, jso);
} else if (type.equals(org.geomajas.geometry.Geometry.LINE_STRING)) {
geometry = createLineString(factory, jso);
} else if (type.equals(org.geomajas.geometry.Geometry.POLYGON)) {
geometry = createPolygon(factory, jso);
} else if (type.equals(org.geomajas.geometry.Geometry.MULTI_LINE_STRING)) {
geometry = createMultiLineString(factory, jso);
} else if (type.equals(org.geomajas.geometry.Geometry.MULTI_POLYGON)) {
geometry = createMultiPolygon(factory, jso);
}
return geometry;
}
private Polygon createPolygon(GeometryFactory factory, JSONObject jso) throws UnmarshallException {
JSONObject jsoShell = jso.getJSONObject("shell");
LinearRing shell = createLinearRing(factory, jsoShell);
JSONArray holeArray = jso.getJSONArray("holes");
LinearRing[] holes = new LinearRing[holeArray.length()];
for (int i = 0; i < holeArray.length(); i++) {
JSONObject jsoHole = holeArray.getJSONObject(i);
holes[i] = createLinearRing(factory, jsoHole);
}
return factory.createPolygon(shell, holes);
}
private LinearRing createLinearRing(GeometryFactory factory, JSONObject jso) throws UnmarshallException {
JSONArray coords = jso.getJSONArray(ATTRIBUTE_COORDINATES);
Coordinate[] coordinates = new Coordinate[coords.length()];
for (int i = 0; i < coords.length(); i++) {
JSONObject nextCoord = coords.getJSONObject(i);
if (nextCoord == null) {
throw new UnmarshallException("inner coordinate missing");
}
coordinates[i] = new Coordinate(nextCoord.getDouble("x"), nextCoord.getDouble("y"));
}
coordinates = checkIfClosed(coordinates);
return factory.createLinearRing(coordinates);
}
private LineString createLineString(GeometryFactory factory, JSONObject jso) throws UnmarshallException {
LineString geometry;
JSONArray jsonCoords = jso.getJSONArray(ATTRIBUTE_COORDINATES);
if (jsonCoords == null) {
throw new UnmarshallException("coordinates missing");
}
Coordinate[] coordinates = new Coordinate[jsonCoords.length()];
for (int i = 0; i < jsonCoords.length(); i++) {
JSONObject nextCoord = jsonCoords.getJSONObject(i);
if (nextCoord == null) {
throw new UnmarshallException("inner coordinate missing");
}
coordinates[i] = new Coordinate(nextCoord.getDouble("x"), nextCoord.getDouble("y"));
}
geometry = new LineString(new CoordinateArraySequence(coordinates), factory);
return geometry;
}
private Point createPoint(GeometryFactory factory, JSONObject jso) throws UnmarshallException {
Point geometry;
JSONArray jsonCoords = jso.getJSONArray(ATTRIBUTE_COORDINATES);
if (jsonCoords == null) {
throw new UnmarshallException("coordinates missing");
}
if (jsonCoords.length() != 1) {
throw new UnmarshallException("wrong number of coordinates " + jsonCoords.length() + " for point");
}
JSONObject coord = jsonCoords.getJSONObject(0);
if (coord == null) {
throw new UnmarshallException("inner coordinate missing");
}
Coordinate coordinate = new Coordinate(coord.getDouble("x"), coord.getDouble("y"));
geometry = new Point(new CoordinateArraySequence(new Coordinate[] {coordinate}), factory);
return geometry;
}
private MultiPolygon createMultiPolygon(GeometryFactory factory, JSONObject jso)
throws UnmarshallException {
JSONArray polyArray = jso.getJSONArray("polygons");
Polygon[] polygons = new Polygon[polyArray.length()];
for (int i = 0; i < polygons.length; i++) {
polygons[i] = createPolygon(factory, polyArray.getJSONObject(i));
}
return factory.createMultiPolygon(polygons);
}
private Geometry createMultiLineString(GeometryFactory factory, JSONObject jso)
throws UnmarshallException {
JSONArray lineArray = jso.getJSONArray("lineStrings");
LineString[] lineStrings = new LineString[lineArray.length()];
for (int i = 0; i < lineArray.length(); i++) {
lineStrings[i] = createLineString(factory, lineArray.getJSONObject(i));
}
return factory.createMultiLineString(lineStrings);
}
public Object marshall(SerializerState state, Object o) throws MarshallException {
JSONObject obj;
Geometry geometry = (Geometry) o;
if (geometry instanceof Point) {
obj = fromPoint((Point) geometry);
} else if (geometry instanceof LineString) {
obj = fromLineString((LineString) geometry);
} else if (geometry instanceof Polygon) {
obj = fromPolygon((Polygon) geometry);
} else if (geometry instanceof MultiPolygon) {
obj = fromMultiPolygon((MultiPolygon) geometry);
} else if (geometry instanceof MultiLineString) {
obj = fromMultiLineString((MultiLineString) geometry);
} else if (geometry instanceof MultiPoint) {
obj = fromMultiPoint((MultiPoint) geometry);
} else {
throw new MarshallException("cannot marshal " + geometry.getClass());
}
return obj;
}
private void putBasics(JSONObject jso, Geometry geometry) {
jso.put(ATTRIBUTE_TYPE, getType(geometry));
jso.put(ATTRIBUTE_SRID, geometry.getSRID());
PrecisionModel precisionmodel = geometry.getPrecisionModel();
// floating or fixed, if floating put -1, if fixed the number of
// decimals
if (precisionmodel.isFloating()) {
jso.put(ATTRIBUTE_PRECISION, -1);
} else {
int precision = (int) Math.log10(precisionmodel.getScale());
jso.put(ATTRIBUTE_PRECISION, precision);
}
}
private JSONObject fromPoint(Point p) {
JSONObject jso = new JSONObject();
JSONArray coordinates = new JSONArray();
PrecisionModel precisionmodel = p.getPrecisionModel();
coordinates.put(precisionmodel.makePrecise(p.getX()));
coordinates.put(precisionmodel.makePrecise(p.getY()));
putBasics(jso, p);
jso.put(ATTRIBUTE_COORDINATES, coordinates);
return jso;
}
private JSONObject fromMultiPoint(MultiPoint mp) {
JSONObject jso = new JSONObject();
JSONArray polys = new JSONArray();
for (int i = 0; i < mp.getNumGeometries(); i++) {
polys.put(fromPoint((Point) mp.getGeometryN(i)));
}
jso.put("points", polys);
putBasics(jso, mp);
return jso;
}
private JSONObject fromLineString(LineString ls) {
PrecisionModel precisionmodel = ls.getPrecisionModel();
JSONObject jso = new JSONObject();
JSONArray coordinates = new JSONArray();
for (int i = 0; i < ls.getCoordinates().length; i++) {
coordinates.put(precisionmodel.makePrecise(ls.getCoordinates()[i].x));
coordinates.put(precisionmodel.makePrecise(ls.getCoordinates()[i].y));
}
putBasics(jso, ls);
jso.put(ATTRIBUTE_COORDINATES, coordinates);
return jso;
}
private JSONObject fromPolygon(Polygon pg) {
JSONObject jso = new JSONObject();
JSONObject shell = fromLineString(pg.getExteriorRing());
JSONArray holes = new JSONArray();
for (int i = 0; i < pg.getNumInteriorRing(); i++) {
holes.put(fromLineString(pg.getInteriorRingN(i)));
}
jso.put("shell", shell);
jso.put("holes", holes);
putBasics(jso, pg);
return jso;
}
private JSONObject fromMultiPolygon(MultiPolygon mp) {
JSONObject jso = new JSONObject();
JSONArray polys = new JSONArray();
for (int i = 0; i < mp.getNumGeometries(); i++) {
polys.put(fromPolygon((Polygon) mp.getGeometryN(i)));
}
jso.put("polygons", polys);
putBasics(jso, mp);
return jso;
}
private JSONObject fromMultiLineString(MultiLineString ml) {
JSONObject jso = new JSONObject();
JSONArray polys = new JSONArray();
for (int i = 0; i < ml.getNumGeometries(); i++) {
polys.put(fromLineString((LineString) ml.getGeometryN(i)));
}
jso.put("lineStrings", polys);
putBasics(jso, ml);
return jso;
}
private String getType(Geometry geom) {
if (geom instanceof Point) {
return org.geomajas.geometry.Geometry.POINT;
} else if (geom instanceof LineString) {
return org.geomajas.geometry.Geometry.LINE_STRING;
} else if (geom instanceof Polygon) {
return org.geomajas.geometry.Geometry.POLYGON;
} else if (geom instanceof MultiPolygon) {
return org.geomajas.geometry.Geometry.MULTI_POLYGON;
} else if (geom instanceof MultiLineString) {
return org.geomajas.geometry.Geometry.MULTI_LINE_STRING;
} else if (geom instanceof MultiPoint) {
return org.geomajas.geometry.Geometry.MULTI_POINT;
} else {
log.error("getType() : type " + geom.getClass().getName() + " unknown");
return "unknown";
}
}
private Coordinate[] checkIfClosed(Coordinate[] coords) {
int length = coords.length;
if (coords[0].equals(coords[length - 1])) {
return coords;
}
Coordinate[] newCoords = new Coordinate[length + 1];
System.arraycopy(coords, 0, newCoords, 0, length);
newCoords[length] = coords[0];
return newCoords;
}
}