/* Copyright 2013 The jeo project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jeo.geojson; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; import java.util.Map; import io.jeo.data.Cursor; import io.jeo.geom.Geom; import io.jeo.vector.Feature; import io.jeo.json.encoder.JSONEncoder; import org.osgeo.proj4j.CoordinateReferenceSystem; 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; /** * Writes out GeoJSON objects as defined by the <a href="http://www.geojson.org/geojson-spec.html">GeoJSON Specification</a>. * <p> * Example: * <pre><code> * Writer w = ...; * Point p = ...; * * GeoJSONWriter writer = new GeoJSONWriter(w); * writer.object() * writer.key("location").point(p); * writer.endObject(); * </code></pre> * </p> * @author Justin Deoliveira, OpenGeo */ public class GeoJSONWriter extends JSONEncoder { /** * Encodes a geometry object to a GeoJSON string. * * @param g The geometry. * * @return A GeoJSON geometry string. * * @see <a href="http://www.geojson.org/geojson-spec.html#geometry-objects}">Geometry Objects</a> */ public static String toString(Geometry g) { StringWriter w = new StringWriter(); try { new GeoJSONWriter(w).geometry(g); } catch (IOException e) { throw new RuntimeException(e); } return w.toString(); } /** * Encodes a feature object to a GeoJSON string. * * @param f The feature. * * @return A GeoJSON feature string. * * @see <a href="http://www.geojson.org/geojson-spec.html#feature-objects}">Feature Objects</a> */ public static String toString(Feature f) { StringWriter w = new StringWriter(); try { new GeoJSONWriter(w).feature(f); } catch (IOException e) { throw new RuntimeException(e); } return w.toString(); } /** * Encodes a feature cursor object to a GeoJSON string. * * @param features The feature cursor. * * @return A GeoJSON feature collection string. * * @see <a href="http://www.geojson.org/geojson-spec.html#feature-collection-objects}">FeatureCollection Objects</a> */ public static String toString(Cursor<Feature> features) throws IOException { StringWriter w = new StringWriter(); new GeoJSONWriter(w).featureCollection(features); return w.toString(); } /** * Creates a new writer. * * @param out The writer to encode to. */ public GeoJSONWriter(Writer out) { super(out); } /** * Creates a new writer with formating. * * @param out The writer to encode to. * @param indentSize The number of spaces to use when indenting. */ public GeoJSONWriter(Writer out, int indentSize) { super(out, indentSize); } /** * Encodes a bounding box as a 4 element array. * * @param b The The bounding box. * @see <a href="http://www.geojson.org/geojson-spec.html#bounding-boxes}">Bounding Box Objects</a> */ public GeoJSONWriter bbox(Envelope b) throws IOException { array() .value(b.getMinX()).value(b.getMinY()).value(b.getMaxX()).value(b.getMaxY()) .endArray(); return this; } /** * Encodes a crs as a named crs. * * @param crs The crs object. * @see <a href="http://www.geojson.org/geojson-spec.html#named-crs}">CRS Objects</a> */ public GeoJSONWriter crs(CoordinateReferenceSystem crs) throws IOException { if (crs == null) { nul(); return this; } object() .key("type").value("name") .key("properties").object() .key("name").value(crs.getName()) .endObject() .endObject(); return this; } // geometry methods // /** * Encodes a geometry object. * * @param g The geometry. * * @see <a href="http://www.geojson.org/geojson-spec.html#geometry-objects}">Geometry Objects</a> */ public GeoJSONWriter geometry(Geometry g) throws IOException { if (g == null) { return (GeoJSONWriter) nul(); } switch (Geom.Type.from(g)) { case POINT: return point((Point) g); case LINESTRING: return lineString((LineString) g); case POLYGON: return polygon((Polygon) g); case MULTIPOINT: return multiPoint((MultiPoint) g); case MULTILINESTRING: return multiLineString((MultiLineString) g); case MULTIPOLYGON: return multiPolygon((MultiPolygon) g); case GEOMETRYCOLLECTION: return geometryCollection((GeometryCollection) g); } throw new IllegalArgumentException("Unable to encode " + g + " as GeoJSON"); } /** * Encodes a point object. * * @param p The point. * * @see <a href="http://www.geojson.org/geojson-spec.html#point}">Point Objects</a> */ public GeoJSONWriter point(Point p) throws IOException { if (p == null) { return (GeoJSONWriter) nul(); } object() .key("type").value("Point") .key("coordinates").array() .value(p.getX()) .value(p.getY()); if (!Double.isNaN(p.getCoordinate().z)) { value(p.getCoordinate().z); } endArray().endObject(); return this; } /** * Encodes a linestring object. * * @param l The linestring. * * @see <a href="http://www.geojson.org/geojson-spec.html#linestring}">LineString Objects</a> */ public GeoJSONWriter lineString(LineString l) throws IOException { if (l == null) { return (GeoJSONWriter) nul(); } object() .key("type").value("LineString") .key("coordinates").array(l.getCoordinateSequence()) .endObject(); return this; } /** * Encodes a polygon object. * * @param p The polygon * * @see <a href="http://www.geojson.org/geojson-spec.html#polygon}">Polygon Objects</a> */ public GeoJSONWriter polygon(Polygon p) throws IOException { if (p == null) { return (GeoJSONWriter) nul(); } object() .key("type").value("Polygon") .key("coordinates").array(p) .endObject(); return this; } /** * Encodes a multipoint object. * * @param mp The multipoint * * @see <a href="http://www.geojson.org/geojson-spec.html#multipoint}">MultiPoint Objects</a> */ public GeoJSONWriter multiPoint(MultiPoint mp) throws IOException { if (mp == null) { return (GeoJSONWriter) nul(); } object() .key("type").value("MultiPoint") .key("coordinates").array(mp.getCoordinates()) .endObject(); return this; } /** * Encodes a multilinestring object. * * @param ml The multilinestring * * @see <a href="http://www.geojson.org/geojson-spec.html#multilinestring}">MultiLineString Objects</a> */ public GeoJSONWriter multiLineString(MultiLineString ml) throws IOException { if (ml == null) { return (GeoJSONWriter) nul(); } object() .key("type").value("MultiLineString") .key("coordinates"); array(); for (LineString ls : Geom.iterate(ml)) { array(ls.getCoordinateSequence()); } endArray(); endObject(); return this; } /** * Encodes a multipolygon object. * * @param mp The multipolygon * * @see <a href="http://www.geojson.org/geojson-spec.html#multipolygon}">MultiPolygon Objects</a> */ public GeoJSONWriter multiPolygon(MultiPolygon mp) throws IOException { if (mp == null) { return (GeoJSONWriter) nul(); } object() .key("type").value("MultiPolygon") .key("coordinates"); array(); for (Polygon p : Geom.iterate(mp)) { array(p); } endArray(); endObject(); return this; } /** * Encodes a geometry collection object. * * @param gc The geometry collection. * * @see <a href="http://www.geojson.org/geojson-spec.html#geometry-collection}">GeometryCollection Objects</a> */ public GeoJSONWriter geometryCollection(GeometryCollection gc) throws IOException { if (gc == null) { return (GeoJSONWriter) nul(); } object() .key("type").value("GeometryCollection") .key("geometries"); array(); for (Geometry g : Geom.iterate(gc)) { geometry(g); } endArray(); endObject(); return this; } // feature methods /** * Encodes a feature object. * * @param f The feature. * * @see <a href="http://www.geojson.org/geojson-spec.html#feature-objects}">Feature Objects</a> */ public GeoJSONWriter feature(Feature f) throws IOException { if (f == null) { nul(); return this; } object() .key("type").value("Feature"); if (f.id() != null) { key("id").value(f.id()); } Geometry g = f.geometry(); if (g != null) { key("geometry").geometry(g); } key("properties").object(); for (Map.Entry<String, Object> p : f.map().entrySet()) { String key = p.getKey(); Object o = p.getValue(); if (o instanceof Geometry) { if (o == g) { continue; } key(key).geometry((Geometry)o); } else { key(key).value(o); } } endObject(); endObject(); return this; } /** * Encodes a feature collection. * * @see <a href="http://www.geojson.org/geojson-spec.html#feature-collection-objects}">FeatureCollection Objects</a> */ public GeoJSONWriter featureCollection(Cursor<Feature> features) throws IOException { featureCollection(); try (Cursor<Feature> c = features) { while (c.hasNext()) { feature(c.next()); } } return endFeatureCollection(); } /** * Starts the encoding of a feature collection. * <p> * This method should be followed by any number of calls to {@link #feature(Feature)} and * finally terminated with a call to {@link #endFeatureCollection()}. * </p> * * @see <a href="http://www.geojson.org/geojson-spec.html#feature-collection-objects}">FeatureCollection Objects</a> */ public GeoJSONWriter featureCollection() throws IOException { object() .key("type").value("FeatureCollection") .key("features").array(); return this; } /** * Finishes the encoding a feature collection previously started with * {@link #featureCollection()}. */ public GeoJSONWriter endFeatureCollection() throws IOException { return (GeoJSONWriter) endArray().endObject(); } // override for type narrowing // @Override public GeoJSONWriter object() throws IOException { return (GeoJSONWriter) super.object(); } @Override public GeoJSONWriter array() throws IOException { return (GeoJSONWriter) super.array(); } @Override public GeoJSONWriter key(String key) throws IOException { return (GeoJSONWriter) super.key(key); } @Override public GeoJSONWriter value(Number value) throws IOException { return (GeoJSONWriter) super.value(value); } @Override public GeoJSONWriter value(double value) throws IOException { return (GeoJSONWriter) super.value(value); } @Override public GeoJSONWriter value(long value) throws IOException { return (GeoJSONWriter) super.value(value); } @Override public GeoJSONWriter value(Object value) throws IOException { return (GeoJSONWriter) super.value(value); } @Override public GeoJSONWriter nul() throws IOException { return (GeoJSONWriter) super.nul(); } @Override public GeoJSONWriter value(String value) throws IOException { return (GeoJSONWriter) super.value(value); } @Override public GeoJSONWriter endObject() throws IOException { return (GeoJSONWriter) super.endObject(); } @Override public GeoJSONWriter endArray() throws IOException { return (GeoJSONWriter) super.endArray(); } @Override public GeoJSONWriter flush() throws IOException { return (GeoJSONWriter) super.flush(); } GeoJSONWriter array(Coordinate[] coords) throws IOException { return array(new CoordinateArraySequence(coords)); } GeoJSONWriter array(CoordinateSequence coordseq) throws IOException { array(); int dim = coordseq.getDimension(); for (int i = 0; i < coordseq.size(); i++) { array() .value(coordseq.getOrdinate(i, 0)) .value(coordseq.getOrdinate(i, 1)); if (dim > 2) { double v = coordseq.getOrdinate(i, 2); if (!Double.isNaN(v)) { value(v); } } endArray(); } endArray(); return this; } GeoJSONWriter array(Polygon p) throws IOException { array(); array(p.getExteriorRing().getCoordinateSequence()); for (int i = 0; i < p.getNumInteriorRing(); i++) { array(p.getInteriorRingN(i).getCoordinateSequence()); } endArray(); return this; } }