/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2002-2010, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.geojson.geom; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.geotools.geojson.GeoJSONUtil; import org.geotools.geojson.IContentHandler; import org.json.simple.JSONArray; import org.json.simple.JSONAware; import org.opengis.geometry.BoundingBox; 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.GeometryFactory; 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; /** * Reads and writes geometry objects to and from geojson. * <p> * <pre> * Point point = new Point(1,2); * * GeometryJSON g = new GeometryJSON(); * g.writePoint(point, "point.json")); * Point point2 = g.readPoint("point.json"); * * Geometry geometry = ...; * g.write(geometry, new File("geometry.json")); * geometry = g.read("geometry.json"); * * </pre> * </p> * @author Justin Deoliveira, OpenGeo * * * * @source $URL: http://svn.osgeo.org/geotools/trunk/modules/unsupported/geojson/src/main/java/org/geotools/geojson/geom/GeometryJSON.java $ */ public class GeometryJSON { GeometryFactory factory = new GeometryFactory(); boolean trace = false; int decimals; double scale; /** * Constructs a geometry json instance. */ public GeometryJSON() { this(4); } /** * Constructs a geometry json instance specifying the number of decimals * to use when encoding floating point numbers. */ public GeometryJSON(int decimals) { this.decimals = decimals; this.scale = Math.pow(10, decimals); } /** * Sets trace flag. */ public void setTrace(boolean trace) { this.trace = trace; } /** * Tracing flag. * <p> * When this flag is set parsed documents will be echoed to stdout * during parsing. * </p> */ public boolean isTrace() { return trace; } /** * Writes a Geometry instance as GeoJSON. * * @param geometry The geometry. * @param output The output. See {@link GeoJSONUtil#toWriter(Object)} for details. */ public void write(Geometry geometry, Object output) throws IOException { GeoJSONUtil.encode(create(geometry), output); } /** * Writes a Geometry instance as GeoJSON returning the result as a string. * * @param geometry The geometry. * * @return The geometry encoded as GeoJSON */ public String toString(Geometry geometry) { StringWriter w = new StringWriter(); try { write(geometry, w); return w.toString(); } catch (IOException e) { throw new RuntimeException(e); } } Map<String,Object> create(Geometry geometry) { if (geometry instanceof Point) { return createPoint((Point)geometry); } if (geometry instanceof LineString) { return createLine((LineString)geometry); } if (geometry instanceof Polygon) { return createPolygon((Polygon)geometry); } if (geometry instanceof MultiPoint) { return createMultiPoint((MultiPoint)geometry); } if (geometry instanceof MultiLineString) { return createMultiLine((MultiLineString)geometry); } if (geometry instanceof MultiPolygon) { return createMultiPolygon((MultiPolygon)geometry); } if (geometry instanceof GeometryCollection) { return createGeometryCollection((GeometryCollection)geometry); } throw new IllegalArgumentException("Unable to encode object " + geometry); } /** * Reads a Geometry instance from GeoJSON. * * @param input The input. See {@link GeoJSONUtil#toReader(Object)} for details. * * @return The geometry instance. */ public Geometry read(Object input) throws IOException { return parse(new GeometryHandler(factory), input); } /** * Writes a Point as GeoJSON. * * @param point The point. * @param output The output. See {@link GeoJSONUtil#toWriter(Object)} for details. * * @throws IOException In the event of a parsing error or if the input json is invalid. */ public void writePoint(Point point, Object output) throws IOException { encode(createPoint(point), output); } Map<String,Object> createPoint(Point point) { LinkedHashMap obj = new LinkedHashMap(); obj.put("type", "Point"); obj.put("coordinates", new CoordinateSequenceEncoder(point.getCoordinateSequence(), scale)); return obj; } /** * Reads a Point from GeoJSON. * * @param input The input. See {@link GeoJSONUtil#toReader(Object)} for details. * * @return The point. * * @throws IOException In the event of a parsing error or if the input json is invalid. */ public Point readPoint(Object input) throws IOException { return parse(new PointHandler(factory), input); } /** * Writes a LineString as GeoJSON. * * @param line The line string. * @param output The output. See {@link GeoJSONUtil#toWriter(Object)} for details. */ public void writeLine(LineString line, Object output) throws IOException { encode(createLine(line), output); } Map<String,Object> createLine(LineString line) { LinkedHashMap obj = new LinkedHashMap(); obj.put("type", "LineString"); obj.put("coordinates", new CoordinateSequenceEncoder(line.getCoordinateSequence(), scale)); return obj; } /** * Reads a LineString from GeoJSON. * * @param input The input. See {@link GeoJSONUtil#toReader(Object)} for details. * * @return The line string. * * @throws IOException In the event of a parsing error or if the input json is invalid. */ public LineString readLine(Object input) throws IOException { return parse(new LineHandler(factory), input); } /** * Writes a Polygon as GeoJSON. * * @param poly The polygon. * @param output The output. See {@link GeoJSONUtil#toWriter(Object)} for details. */ public void writePolygon(Polygon poly, Object output) throws IOException { encode(createPolygon(poly), output); } Map<String,Object> createPolygon(Polygon poly) { LinkedHashMap obj = new LinkedHashMap(); obj.put("type", "Polygon"); obj.put("coordinates", toList(poly)); return obj; } /** * Reads a Polygon from GeoJSON. * * @param input The input. See {@link GeoJSONUtil#toReader(Object)} for details. * * @return The polygon. * * @throws IOException In the event of a parsing error or if the input json is invalid. */ public Polygon readPolygon(Object input) throws IOException { return parse(new PolygonHandler(factory), input); } /** * Writes a MultiPoint as GeoJSON. * * @param mpoint The multi point. * @param output The output. See {@link GeoJSONUtil#toWriter(Object)} for details. */ public void writeMultiPoint(MultiPoint mpoint, Object output) throws IOException { encode(createMultiPoint(mpoint), output); } Map<String,Object> createMultiPoint(MultiPoint mpoint) { LinkedHashMap obj = new LinkedHashMap(); obj.put("type", "MultiPoint"); obj.put("coordinates", toList(mpoint)); return obj; } /** * Reads a MultiPoint from GeoJSON. * * @param input The input. See {@link GeoJSONUtil#toReader(Object)} for details. * * @return The multi point. * * @throws IOException In the event of a parsing error or if the input json is invalid. */ public MultiPoint readMultiPoint(Object input) throws IOException { return parse(new MultiPointHandler(factory), input); } /** * Writes a MultiLineString as GeoJSON. * * @param mline The multi line string. * @param output The output. See {@link GeoJSONUtil#toWriter(Object)} for details. */ public void writeMultiLine(MultiLineString mline, Object output) throws IOException { encode(createMultiLine(mline), output); } Map<String,Object> createMultiLine(MultiLineString mline) { LinkedHashMap obj = new LinkedHashMap(); obj.put("type", "MultiLineString"); obj.put("coordinates", toList(mline)); return obj; } /** * Reads a MultiLineString from GeoJSON. * * @param input The input. See {@link GeoJSONUtil#toReader(Object)} for details. * * @return The multi line string. * * @throws IOException In the event of a parsing error or if the input json is invalid. */ public MultiLineString readMultiLine(Object input) throws IOException { return parse(new MultiLineHandler(factory), input); } /** * Writes a MultiPolygon as GeoJSON. * * @param mpoly The multi polygon. * @param output The output. See {@link GeoJSONUtil#toWriter(Object)} for details. */ public void writeMultiPolygon(MultiPolygon mpoly, Object output) throws IOException { encode(createMultiPolygon(mpoly), output); } Map<String,Object> createMultiPolygon(MultiPolygon mpoly) { LinkedHashMap obj = new LinkedHashMap(); obj.put("type", "MultiPolygon"); obj.put("coordinates", toList(mpoly)); return obj; } /** * Reads a MultiPolygon from GeoJSON. * * @param input The input. See {@link GeoJSONUtil#toReader(Object)} for details. * * @return The multi polygon. * * @throws IOException In the event of a parsing error or if the input json is invalid. */ public MultiPolygon readMultiPolygon(Object input) throws IOException { return parse(new MultiPolygonHandler(factory), input); } /** * Writes a GeometryCollection as GeoJSON. * * @param gcol The geometry collection. * @param output The output. See {@link GeoJSONUtil#toWriter(Object)} for details. */ public void writeGeometryCollection(GeometryCollection gcol, Object output) throws IOException { encode(createGeometryCollection(gcol), output); } Map<String,Object> createGeometryCollection(GeometryCollection gcol) { LinkedHashMap obj = new LinkedHashMap(); ArrayList geoms = new ArrayList(gcol.getNumGeometries()); for (int i = 0; i < gcol.getNumGeometries(); i++) { geoms.add(create(gcol.getGeometryN(i))); } obj.put("type", "GeometryCollection"); obj.put("geometries", geoms); return obj; } /** * Reads a GeometryCollection from GeoJSON. * * @param input The input. See {@link GeoJSONUtil#toReader(Object)} for details. * * @return The geometry collection. * * @throws IOException In the event of a parsing error or if the input json is invalid. */ public GeometryCollection readGeometryCollection(Object input) throws IOException { return parse(new GeometryCollectionHandler(factory), input); } /** * Writes an BoundingBox instance as GeoJSON returning the result as a string. * * @param bbox The bounding box. * * @return The bounding box encoded as GeoJSON */ public String toString(BoundingBox bbox) { return new StringBuffer().append("[").append(bbox.getMinX()).append(",") .append(bbox.getMinY()).append(",").append(bbox.getMaxX()).append(",") .append(bbox.getMaxY()).append("]").toString(); } /** * Writes an Envelope instance as GeoJSON returning the result as a string. * * @param e The envelope * * @return The envelope encoded as GeoJSON */ public String toString(Envelope e) { return new StringBuffer().append("[").append(e.getMinX()).append(",") .append(e.getMinY()).append(",").append(e.getMaxX()).append(",") .append(e.getMaxY()).append("]").toString(); } <G extends Geometry> G parse(IContentHandler<G> handler, Object input) throws IOException { return GeoJSONUtil.parse(handler, input, trace); } void encode(Map<String,Object> obj, Object output) throws IOException { GeoJSONUtil.encode(obj, output); } List toList(Polygon poly) { ArrayList list = new ArrayList(); list.add(new CoordinateSequenceEncoder(poly.getExteriorRing().getCoordinateSequence(), scale)); for (int i = 0; i < poly.getNumInteriorRing(); i++) { list.add(new CoordinateSequenceEncoder(poly.getInteriorRingN(i).getCoordinateSequence(), scale)); } return list; } List toList(GeometryCollection mgeom) { ArrayList list = new ArrayList(mgeom.getNumGeometries()); for (int i = 0; i < mgeom.getNumGeometries(); i++) { Geometry g = mgeom.getGeometryN(i); if (g instanceof Polygon) { list.add(toList((Polygon)g)); } else if (g instanceof LineString){ list.add(new CoordinateSequenceEncoder(((LineString)g).getCoordinateSequence(), scale)); } else if (g instanceof Point) { list.add(new CoordinateSequenceEncoder(((Point)g).getCoordinateSequence(), scale)); } } return list; } static class CoordinateSequenceEncoder implements JSONAware /*, JSONStreamAware*/ { /** * The min value at which the decimal notation is used * (below it, the computerized scientific one is used instead) */ private static final double DECIMAL_MIN = Math.pow(10, -3); /** * The max value at which the decimal notation is used * (above it, the computerized scientific one is used instead) */ private static final double DECIMAL_MAX = Math.pow(10, 7); CoordinateSequence seq; double scale; CoordinateSequenceEncoder(CoordinateSequence seq, double scale) { this.seq = seq; this.scale = scale; } public String toJSONString() { int size = seq.size(); StringBuilder sb = new StringBuilder(); if (size > 1) { sb.append("["); } for (int i = 0; i < seq.size(); i++) { sb.append("["); formatDecimal(seq.getX(i), sb); sb.append(","); formatDecimal(seq.getY(i), sb); sb.append("],"); } sb.setLength(sb.length()-1); if (size > 1) { sb.append("]"); } return sb.toString(); } public void writeJSONString(Writer out) throws IOException { int size = seq.size(); if (size > 1) { out.write("["); } for (int i = 0; i < seq.size(); i++) { out.write("["); out.write(String.valueOf(seq.getX(i))); out.write(","); out.write(String.valueOf(seq.getY(i))); out.write("]"); if (i < seq.size()-1) { out.write(","); } } if (size > 1) { out.write("]"); } } private void formatDecimal(double x, StringBuilder sb) { if(Math.abs(x) >= DECIMAL_MIN && x < DECIMAL_MAX) { x = Math.floor(x * scale + 0.5) / scale; long lx = (long) x; if(lx == x) sb.append(lx); else sb.append(x); } else { sb.append(x); } } } }