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