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