001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.io;
003
004import java.io.StringWriter;
005import java.util.HashMap;
006import java.util.Iterator;
007import java.util.Map;
008import java.util.Map.Entry;
009
010import javax.json.Json;
011import javax.json.JsonArrayBuilder;
012import javax.json.JsonObjectBuilder;
013import javax.json.JsonWriter;
014import javax.json.stream.JsonGenerator;
015
016import org.openstreetmap.josm.data.Bounds;
017import org.openstreetmap.josm.data.coor.LatLon;
018import org.openstreetmap.josm.data.osm.DataSet;
019import org.openstreetmap.josm.data.osm.INode;
020import org.openstreetmap.josm.data.osm.IRelation;
021import org.openstreetmap.josm.data.osm.IWay;
022import org.openstreetmap.josm.data.osm.Node;
023import org.openstreetmap.josm.data.osm.OsmPrimitive;
024import org.openstreetmap.josm.data.osm.Way;
025import org.openstreetmap.josm.data.osm.visitor.PrimitiveVisitor;
026import org.openstreetmap.josm.gui.layer.OsmDataLayer;
027
028/**
029 * Writes OSM data as a GeoJSON string, using JSR 353: Java API for JSON Processing (JSON-P).
030 */
031public class GeoJSONWriter {
032
033    private OsmDataLayer layer;
034    private static final boolean skipEmptyNodes = true;
035
036    /**
037     * Constructs a new {@code GeoJSONWriter}.
038     * @param layer The OSM data layer to save
039     */
040    public GeoJSONWriter(OsmDataLayer layer) {
041        this.layer = layer;
042    }
043
044    /**
045     * Writes OSM data as a GeoJSON string (prettified).
046     * @return The GeoJSON data
047     */
048    public String write() {
049        return write(true);
050    }
051
052    /**
053     * Writes OSM data as a GeoJSON string (prettified or not).
054     * @param pretty {@code true} to have pretty output, {@code false} otherwise
055     * @return The GeoJSON data
056     * @since 6756
057     */
058    public String write(boolean pretty) {
059        StringWriter stringWriter = new StringWriter();
060        Map<String, Object> config = new HashMap<>(1);
061        config.put(JsonGenerator.PRETTY_PRINTING, pretty);
062        try (JsonWriter writer = Json.createWriterFactory(config).createWriter(stringWriter)) {
063            JsonObjectBuilder object = Json.createObjectBuilder()
064                    .add("type", "FeatureCollection")
065                    .add("generator", "JOSM");
066            appendLayerBounds(layer.data, object);
067            appendLayerFeatures(layer.data, object);
068            writer.writeObject(object.build());
069            return stringWriter.toString();
070        }
071    }
072    
073    private static class GeometryPrimitiveVisitor implements PrimitiveVisitor {
074        
075        private final JsonObjectBuilder geomObj;
076        
077        public GeometryPrimitiveVisitor(JsonObjectBuilder geomObj) {
078            this.geomObj = geomObj;
079        }
080
081        @Override
082        public void visit(INode n) {
083            geomObj.add("type", "Point");
084            LatLon ll = n.getCoor();
085            if (ll != null) {
086                geomObj.add("coordinates", getCoorArray(n.getCoor()));
087            }
088        }
089
090        @Override
091        public void visit(IWay w) {
092            geomObj.add("type", "LineString");
093            if (w instanceof Way) {
094                JsonArrayBuilder array = Json.createArrayBuilder();
095                for (Node n : ((Way)w).getNodes()) {
096                    LatLon ll = n.getCoor();
097                    if (ll != null) {
098                        array.add(getCoorArray(ll));
099                    }
100                }
101                geomObj.add("coordinates", array);
102            }
103        }
104
105        @Override
106        public void visit(IRelation r) {
107        }
108
109        private JsonArrayBuilder getCoorArray(LatLon c) {
110            return Json.createArrayBuilder().add(c.lon()).add(c.lat());
111        }
112    }
113
114    protected static void appendPrimitive(OsmPrimitive p, JsonArrayBuilder array) {
115        if (p.isIncomplete()) {
116            return;
117        } else if (skipEmptyNodes && p instanceof Node && p.getKeys().isEmpty()) {
118            return;
119        }
120
121        // Properties
122        final JsonObjectBuilder propObj = Json.createObjectBuilder();
123        for (Entry<String, String> t : p.getKeys().entrySet()) {
124            propObj.add(t.getKey(), t.getValue());
125        }
126
127        // Geometry
128        final JsonObjectBuilder geomObj = Json.createObjectBuilder();
129        p.accept(new GeometryPrimitiveVisitor(geomObj));
130
131        // Build primitive JSON object
132        array.add(Json.createObjectBuilder()
133                .add("type", "Feature")
134                .add("properties", propObj)
135                .add("geometry", geomObj));
136    }
137
138    protected static void appendLayerBounds(DataSet ds, JsonObjectBuilder object) {
139        if (ds != null) {
140            Iterator<Bounds> it = ds.getDataSourceBounds().iterator();
141            if (it.hasNext()) {
142                Bounds b = new Bounds(it.next());
143                while (it.hasNext()) {
144                    b.extend(it.next());
145                }
146                appendBounds(b, object);
147            }
148        }
149    }
150
151    protected static void appendBounds(Bounds b, JsonObjectBuilder object) {
152        if (b != null) {
153            object.add("bbox", Json.createArrayBuilder()
154                    .add(b.getMinLon()).add(b.getMinLat())
155                    .add(b.getMaxLon()).add(b.getMaxLat()));
156        }
157    }
158
159    protected static void appendLayerFeatures(DataSet ds, JsonObjectBuilder object) {
160        JsonArrayBuilder array = Json.createArrayBuilder();
161        if (ds != null) {
162            for (Node n : ds.getNodes()) {
163                appendPrimitive(n, array);
164            }
165            for (Way w : ds.getWays()) {
166                appendPrimitive(w, array);
167            }
168        }
169        object.add("features", array);
170    }
171}