/* (c) 2015 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.wms.topojson;
import java.awt.geom.AffineTransform;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nullable;
import org.geoserver.wms.topojson.TopoGeom.GeometryColleciton;
import org.geoserver.wms.topojson.TopoGeom.LineString;
import org.geoserver.wms.topojson.TopoGeom.MultiLineString;
import org.geoserver.wms.topojson.TopoGeom.MultiPoint;
import org.geoserver.wms.topojson.TopoGeom.MultiPolygon;
import org.geoserver.wms.topojson.TopoGeom.Point;
import org.geoserver.wms.topojson.TopoGeom.Polygon;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.CoordinateSequence;
import com.vividsolutions.jts.geom.PrecisionModel;
public class TopoJSONEncoder {
private static class TopologyAdapter
implements JsonSerializer<Topology>, JsonDeserializer<Topology> {
@Override
public JsonElement serialize(Topology topology, Type typeOfSrc,
JsonSerializationContext context) {
JsonObject root = new JsonObject();
root.addProperty("type", "Topology");
root.addProperty("count", topology.getArcs().size());
AffineTransform transform = topology.getScreenToWorldTransform();
if (!transform.isIdentity()) {
addTransform(root, transform);
}
addArcs(root, topology);
addLayers(root, topology.getLayers());
return root;
}
private void addLayers(JsonObject root, Map<String, GeometryColleciton> layers) {
JsonObject objects = new JsonObject();
root.add("objects", objects);
for (Map.Entry<String, GeometryColleciton> e : layers.entrySet()) {
String name = e.getKey();
GeometryColleciton geometries = e.getValue();
JsonObject layer = TopologyEncoder.encode(geometries);
objects.add(name, layer);
}
}
private void addTransform(JsonObject root, AffineTransform transform) {
if (transform.isIdentity()) {
return;
}
JsonObject tx = new JsonObject();
JsonArray scale = new JsonArray();
scale.add(new JsonPrimitive(transform.getScaleX()));
scale.add(new JsonPrimitive(transform.getScaleY()));
tx.add("scale", scale);
JsonArray translate = new JsonArray();
translate.add(new JsonPrimitive(transform.getTranslateX()));
translate.add(new JsonPrimitive(transform.getTranslateY()));
tx.add("translate", translate);
root.add("transform", tx);
}
private void addArcs(JsonObject root, Topology topology) {
JsonArray arcs = new JsonArray();
JsonArray jsonArc;
for (com.vividsolutions.jts.geom.LineString arc : topology.getArcs()) {
if (topology.getScreenToWorldTransform().isIdentity()) {
jsonArc = TopoJSONEncoder.serialize(arc.getCoordinateSequence());
} else {
jsonArc = TopoJSONEncoder.quantize(arc.getCoordinateSequence(),
arc.getFactory().getPrecisionModel());
}
arcs.add(jsonArc);
}
root.add("arcs", arcs);
}
@Override
public Topology deserialize(JsonElement arg0, Type arg1, JsonDeserializationContext arg2)
throws JsonParseException {
throw new UnsupportedOperationException();
}
}
private static final TopologyAdapter TOPOLOGY_ADAPTER = new TopologyAdapter();
private static final GsonBuilder gsonBuilder = new GsonBuilder();
static {
gsonBuilder.registerTypeAdapter(Topology.class, TOPOLOGY_ADAPTER);
}
public void encode(Topology topology, Writer writer) throws IOException {
Gson gson = gsonBuilder/* .setPrettyPrinting() */.create();
// gson.fromjson
gson.toJson(topology, writer);
writer.flush();
}
private static abstract class TopologyEncoder {
private static Map<String, TopologyEncoder> encoders = new HashMap<>();
static {
encoders.put("Point", new PointEncoder());
encoders.put("MultiPoint", new MultiPointEncoder());
encoders.put("LineString", new LineStringEncoder());
encoders.put("MultiLineString", new MultiLineStringEncoder());
encoders.put("Polygon", new PolygonEncoder());
encoders.put("MultiPolygon", new MultiPolygonEncoder());
encoders.put("GeometryCollection", new GeometryCollecitonEncoder());
}
public static JsonObject encode(TopoGeom geom) {
JsonObject obj = new JsonObject();
String geometryType = geom.getGeometryType();
TopologyEncoder encoder = encoders.get(geometryType);
encoder.encode(geom, obj);
return obj;
}
public void encode(TopoGeom geom, JsonObject target) {
target.addProperty("type", geom.getGeometryType());
if (geom.getId() != null) {
target.addProperty("id", geom.getId());
}
JsonObject properties = properties(geom.getProperties());
if (properties != null) {
target.add("properties", properties);
}
encodeInternal(geom, target);
}
protected abstract void encodeInternal(TopoGeom geom, JsonObject target);
@SuppressWarnings("unchecked")
@Nullable
private JsonObject properties(Map<String, Object> properties) {
if (properties.isEmpty()) {
return null;
}
JsonObject props = new JsonObject();
for (Map.Entry<String, Object> e : properties.entrySet()) {
String name = e.getKey();
Object value = e.getValue();
JsonElement jsonValue;
if (value instanceof Map) {
jsonValue = properties((Map<String, Object>) value);
} else if (value instanceof Boolean) {
jsonValue = new JsonPrimitive((Boolean) value);
} else if (value instanceof Number) {
Number n = (Number) value;
if (n instanceof Double && n.doubleValue() % 1 == 0) {
n = Long.valueOf(n.longValue());
} else if (n instanceof Float && n.floatValue() % 1 == 0) {
n = Integer.valueOf(n.intValue());
}
jsonValue = new JsonPrimitive(n);
} else {
jsonValue = new JsonPrimitive(String.valueOf(value));
}
props.add(name, jsonValue);
}
return props;
}
}
private static class GeometryCollecitonEncoder extends TopologyEncoder {
@Override
protected void encodeInternal(TopoGeom geom, JsonObject target) {
GeometryColleciton coll = (GeometryColleciton) geom;
JsonArray geoms = new JsonArray();
target.add("geometries", geoms);
for (TopoGeom obj : coll.getGeometries()) {
geoms.add(TopologyEncoder.encode(obj));
}
}
}
private static class PointEncoder extends TopologyEncoder {
@Override
protected void encodeInternal(TopoGeom geom, JsonObject target) {
TopoGeom.Point point = (Point) geom;
JsonArray coordinates = new JsonArray();
target.add("coordinates", coordinates);
coordinates.add(new JsonPrimitive(point.getX()));
coordinates.add(new JsonPrimitive(point.getY()));
}
}
private static class MultiPointEncoder extends TopologyEncoder {
@Override
protected void encodeInternal(TopoGeom geom, JsonObject target) {
TopoGeom.MultiPoint multipoint = (MultiPoint) geom;
JsonArray coordinates = new JsonArray();
target.add("coordinates", coordinates);
Iterable<Point> points = multipoint.getPoints();
for (Point p : points) {
JsonArray point = new JsonArray();
coordinates.add(point);
point.add(new JsonPrimitive(p.getX()));
point.add(new JsonPrimitive(p.getY()));
}
}
}
private static class LineStringEncoder extends TopologyEncoder {
@Override
protected void encodeInternal(TopoGeom geom, JsonObject target) {
TopoGeom.LineString arc = (LineString) geom;
target.add("arcs", LineStringEncoder.indexes(arc));
}
public static JsonArray indexes(LineString arc) {
JsonArray arcs = new JsonArray();
for (Integer index : arc.getIndexes()) {
arcs.add(new JsonPrimitive(index));
}
return arcs;
}
}
private static class MultiLineStringEncoder extends TopologyEncoder {
@Override
protected void encodeInternal(TopoGeom geom, JsonObject target) {
TopoGeom.MultiLineString marc = (MultiLineString) geom;
JsonArray arcs = new JsonArray();
target.add("arcs", arcs);
for (LineString arc : marc.getArcs()) {
arcs.add(LineStringEncoder.indexes(arc));
}
}
}
private static class PolygonEncoder extends TopologyEncoder {
@Override
protected void encodeInternal(TopoGeom geom, JsonObject target) {
TopoGeom.Polygon poly = (Polygon) geom;
target.add("arcs", PolygonEncoder.indexes(poly));
}
public static JsonArray indexes(TopoGeom.Polygon poly) {
JsonArray arcs = new JsonArray();
Iterable<LineString> rings = poly.getRings();
for (LineString ring : rings) {
arcs.add(LineStringEncoder.indexes(ring));
}
return arcs;
}
}
private static class MultiPolygonEncoder extends TopologyEncoder {
@Override
protected void encodeInternal(TopoGeom geom, JsonObject target) {
TopoGeom.MultiPolygon poly = (MultiPolygon) geom;
JsonArray polygons = new JsonArray();
target.add("arcs", polygons);
for (Polygon p : poly.getPolygons()) {
polygons.add(PolygonEncoder.indexes(p));
}
}
}
public static JsonArray serialize(final CoordinateSequence coords) {
JsonArray arc = new JsonArray();
final int size = coords.size();
if (size > 0) {
Coordinate buff = new Coordinate();
coords.getCoordinate(0, buff);
addCoordinate(arc, buff);// first coordinate as-is
for (int i = 0; i < size; i++) {// subsequent coordinates delta encoded
coords.getCoordinate(i, buff);
addCoordinate(arc, buff);
}
}
return arc;
}
public static JsonArray quantize(final CoordinateSequence coords,
PrecisionModel precisionModel) {
JsonArray arc = new JsonArray();
final int size = coords.size();
if (size > 0) {
Coordinate buff = new Coordinate();
coords.getCoordinate(0, buff);
precisionModel.makePrecise(buff);
addCoordinate(arc, buff);// first coordinate as-is
double lastX;
double lastY;
lastX = buff.x;
lastY = buff.y;
for (int i = 1; i < size; i++) {// subsequent coordinates delta encoded
coords.getCoordinate(i, buff);
precisionModel.makePrecise(buff);
double deltaX = buff.x - lastX;
double deltaY = buff.y - lastY;
lastX = buff.x;
lastY = buff.y;
buff.x = deltaX;
buff.y = deltaY;
precisionModel.makePrecise(buff);
if (buff.x == 0d && buff.y == 0d) {
continue;
}
addCoordinate(arc, buff);
}
}
return arc;
}
private static void addCoordinate(JsonArray arc, Coordinate c) {
JsonArray coord = new JsonArray();
double x = c.x;
double y = c.y;
Number X, Y;
if (x % 1 == 0) {
X = Integer.valueOf((int) x);
} else {
X = new Double(x);
}
if (y % 1 == 0) {
Y = Integer.valueOf((int) y);
} else {
Y = new Double(y);
}
coord.add(new JsonPrimitive(X));
coord.add(new JsonPrimitive(Y));
arc.add(coord);
}
}