[4886] | 1 | // License: GPL. For details, see LICENSE file.
|
---|
| 2 | package org.openstreetmap.josm.io;
|
---|
| 3 |
|
---|
[6756] | 4 | import java.io.StringWriter;
|
---|
[10165] | 5 | import java.math.BigDecimal;
|
---|
| 6 | import java.math.RoundingMode;
|
---|
[6756] | 7 | import java.util.HashMap;
|
---|
[6485] | 8 | import java.util.Iterator;
|
---|
[10817] | 9 | import java.util.List;
|
---|
[4886] | 10 | import java.util.Map;
|
---|
| 11 | import java.util.Map.Entry;
|
---|
[10817] | 12 | import java.util.stream.Stream;
|
---|
[6484] | 13 |
|
---|
[6756] | 14 | import javax.json.Json;
|
---|
| 15 | import javax.json.JsonArrayBuilder;
|
---|
| 16 | import javax.json.JsonObjectBuilder;
|
---|
| 17 | import javax.json.JsonWriter;
|
---|
| 18 | import javax.json.stream.JsonGenerator;
|
---|
| 19 |
|
---|
[11166] | 20 | import org.openstreetmap.josm.Main;
|
---|
[6485] | 21 | import org.openstreetmap.josm.data.Bounds;
|
---|
[8813] | 22 | import org.openstreetmap.josm.data.coor.EastNorth;
|
---|
[4886] | 23 | import org.openstreetmap.josm.data.coor.LatLon;
|
---|
[6756] | 24 | import org.openstreetmap.josm.data.osm.DataSet;
|
---|
[10817] | 25 | import org.openstreetmap.josm.data.osm.MultipolygonBuilder;
|
---|
| 26 | import org.openstreetmap.josm.data.osm.MultipolygonBuilder.JoinedPolygon;
|
---|
[4886] | 27 | import org.openstreetmap.josm.data.osm.Node;
|
---|
| 28 | import org.openstreetmap.josm.data.osm.OsmPrimitive;
|
---|
[10817] | 29 | import org.openstreetmap.josm.data.osm.Relation;
|
---|
[4886] | 30 | import org.openstreetmap.josm.data.osm.Way;
|
---|
[10817] | 31 | import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
|
---|
[8813] | 32 | import org.openstreetmap.josm.data.projection.Projection;
|
---|
[4886] | 33 | import org.openstreetmap.josm.gui.layer.OsmDataLayer;
|
---|
[10817] | 34 | import org.openstreetmap.josm.gui.mappaint.ElemStyles;
|
---|
[10852] | 35 | import org.openstreetmap.josm.gui.preferences.projection.ProjectionPreference;
|
---|
[10817] | 36 | import org.openstreetmap.josm.tools.Pair;
|
---|
[4886] | 37 |
|
---|
[6756] | 38 | /**
|
---|
| 39 | * Writes OSM data as a GeoJSON string, using JSR 353: Java API for JSON Processing (JSON-P).
|
---|
[10852] | 40 | * <p>
|
---|
| 41 | * See <a href="https://tools.ietf.org/html/rfc7946">RFC7946: The GeoJSON Format</a>
|
---|
[6756] | 42 | */
|
---|
| 43 | public class GeoJSONWriter {
|
---|
[4886] | 44 |
|
---|
[8813] | 45 | private final OsmDataLayer layer;
|
---|
| 46 | private final Projection projection;
|
---|
[6246] | 47 | private static final boolean skipEmptyNodes = true;
|
---|
[4886] | 48 |
|
---|
[6756] | 49 | /**
|
---|
| 50 | * Constructs a new {@code GeoJSONWriter}.
|
---|
| 51 | * @param layer The OSM data layer to save
|
---|
[10852] | 52 | * @since 10852
|
---|
[6756] | 53 | */
|
---|
[10852] | 54 | public GeoJSONWriter(OsmDataLayer layer) {
|
---|
[4886] | 55 | this.layer = layer;
|
---|
[10852] | 56 | this.projection = ProjectionPreference.wgs84.getProjection();
|
---|
[4886] | 57 | }
|
---|
| 58 |
|
---|
[6756] | 59 | /**
|
---|
| 60 | * Writes OSM data as a GeoJSON string (prettified).
|
---|
| 61 | * @return The GeoJSON data
|
---|
| 62 | */
|
---|
[4886] | 63 | public String write() {
|
---|
[6756] | 64 | return write(true);
|
---|
[4886] | 65 | }
|
---|
| 66 |
|
---|
[6756] | 67 | /**
|
---|
| 68 | * Writes OSM data as a GeoJSON string (prettified or not).
|
---|
| 69 | * @param pretty {@code true} to have pretty output, {@code false} otherwise
|
---|
| 70 | * @return The GeoJSON data
|
---|
| 71 | * @since 6756
|
---|
| 72 | */
|
---|
| 73 | public String write(boolean pretty) {
|
---|
| 74 | StringWriter stringWriter = new StringWriter();
|
---|
[7005] | 75 | Map<String, Object> config = new HashMap<>(1);
|
---|
[6756] | 76 | config.put(JsonGenerator.PRETTY_PRINTING, pretty);
|
---|
[7030] | 77 | try (JsonWriter writer = Json.createWriterFactory(config).createWriter(stringWriter)) {
|
---|
| 78 | JsonObjectBuilder object = Json.createObjectBuilder()
|
---|
| 79 | .add("type", "FeatureCollection")
|
---|
| 80 | .add("generator", "JOSM");
|
---|
| 81 | appendLayerBounds(layer.data, object);
|
---|
| 82 | appendLayerFeatures(layer.data, object);
|
---|
| 83 | writer.writeObject(object.build());
|
---|
| 84 | return stringWriter.toString();
|
---|
| 85 | }
|
---|
[4886] | 86 | }
|
---|
[7509] | 87 |
|
---|
[10817] | 88 | private class GeometryPrimitiveVisitor extends AbstractVisitor {
|
---|
[7509] | 89 |
|
---|
[6806] | 90 | private final JsonObjectBuilder geomObj;
|
---|
[7509] | 91 |
|
---|
[8836] | 92 | GeometryPrimitiveVisitor(JsonObjectBuilder geomObj) {
|
---|
[6806] | 93 | this.geomObj = geomObj;
|
---|
| 94 | }
|
---|
[4886] | 95 |
|
---|
[6806] | 96 | @Override
|
---|
[10817] | 97 | public void visit(Node n) {
|
---|
[6806] | 98 | geomObj.add("type", "Point");
|
---|
| 99 | LatLon ll = n.getCoor();
|
---|
| 100 | if (ll != null) {
|
---|
[10817] | 101 | geomObj.add("coordinates", getCoorArray(null, n.getCoor()));
|
---|
[6806] | 102 | }
|
---|
| 103 | }
|
---|
| 104 |
|
---|
| 105 | @Override
|
---|
[10817] | 106 | public void visit(Way w) {
|
---|
| 107 | if (w != null) {
|
---|
| 108 | final JsonArrayBuilder array = getCoorsArray(w.getNodes());
|
---|
| 109 | if (ElemStyles.hasAreaElemStyle(w, false)) {
|
---|
| 110 | final JsonArrayBuilder container = Json.createArrayBuilder().add(array);
|
---|
| 111 | geomObj.add("type", "Polygon");
|
---|
| 112 | geomObj.add("coordinates", container);
|
---|
| 113 | } else {
|
---|
| 114 | geomObj.add("type", "LineString");
|
---|
| 115 | geomObj.add("coordinates", array);
|
---|
[6806] | 116 | }
|
---|
| 117 | }
|
---|
| 118 | }
|
---|
| 119 |
|
---|
| 120 | @Override
|
---|
[10817] | 121 | public void visit(Relation r) {
|
---|
[11166] | 122 | if (r == null || !r.isMultipolygon() || r.hasIncompleteMembers()) {
|
---|
| 123 | return;
|
---|
| 124 | }
|
---|
| 125 | try {
|
---|
[10817] | 126 | final Pair<List<JoinedPolygon>, List<JoinedPolygon>> mp = MultipolygonBuilder.joinWays(r);
|
---|
| 127 | final JsonArrayBuilder polygon = Json.createArrayBuilder();
|
---|
| 128 | Stream.concat(mp.a.stream(), mp.b.stream())
|
---|
| 129 | .map(p -> getCoorsArray(p.getNodes())
|
---|
| 130 | // since first node is not duplicated as last node
|
---|
| 131 | .add(getCoorArray(null, p.getNodes().get(0).getCoor())))
|
---|
| 132 | .forEach(polygon::add);
|
---|
| 133 | geomObj.add("type", "MultiPolygon");
|
---|
| 134 | final JsonArrayBuilder multiPolygon = Json.createArrayBuilder().add(polygon);
|
---|
| 135 | geomObj.add("coordinates", multiPolygon);
|
---|
[11166] | 136 | } catch (MultipolygonBuilder.JoinedPolygonCreationException ex) {
|
---|
[11191] | 137 | Main.warn("GeoJSON: Failed to export multipolygon {0}", r.getUniqueId());
|
---|
[11166] | 138 | Main.warn(ex);
|
---|
[10817] | 139 | }
|
---|
[6806] | 140 | }
|
---|
[8813] | 141 | }
|
---|
[6806] | 142 |
|
---|
[8813] | 143 | private JsonArrayBuilder getCoorArray(JsonArrayBuilder builder, LatLon c) {
|
---|
| 144 | return getCoorArray(builder, projection.latlon2eastNorth(c));
|
---|
[6806] | 145 | }
|
---|
| 146 |
|
---|
[8839] | 147 | private static JsonArrayBuilder getCoorArray(JsonArrayBuilder builder, EastNorth c) {
|
---|
[10817] | 148 | return builder != null ? builder : Json.createArrayBuilder()
|
---|
[10165] | 149 | .add(BigDecimal.valueOf(c.getX()).setScale(11, RoundingMode.HALF_UP))
|
---|
[10171] | 150 | .add(BigDecimal.valueOf(c.getY()).setScale(11, RoundingMode.HALF_UP));
|
---|
[8813] | 151 | }
|
---|
| 152 |
|
---|
[10817] | 153 | private JsonArrayBuilder getCoorsArray(Iterable<Node> nodes) {
|
---|
| 154 | final JsonArrayBuilder builder = Json.createArrayBuilder();
|
---|
| 155 | for (Node n : nodes) {
|
---|
| 156 | LatLon ll = n.getCoor();
|
---|
| 157 | if (ll != null) {
|
---|
| 158 | builder.add(getCoorArray(null, ll));
|
---|
| 159 | }
|
---|
| 160 | }
|
---|
| 161 | return builder;
|
---|
| 162 | }
|
---|
| 163 |
|
---|
[8813] | 164 | protected void appendPrimitive(OsmPrimitive p, JsonArrayBuilder array) {
|
---|
[4886] | 165 | if (p.isIncomplete()) {
|
---|
| 166 | return;
|
---|
| 167 | } else if (skipEmptyNodes && p instanceof Node && p.getKeys().isEmpty()) {
|
---|
| 168 | return;
|
---|
| 169 | }
|
---|
[6756] | 170 |
|
---|
| 171 | // Properties
|
---|
| 172 | final JsonObjectBuilder propObj = Json.createObjectBuilder();
|
---|
| 173 | for (Entry<String, String> t : p.getKeys().entrySet()) {
|
---|
| 174 | propObj.add(t.getKey(), t.getValue());
|
---|
[4886] | 175 | }
|
---|
| 176 |
|
---|
[6756] | 177 | // Geometry
|
---|
| 178 | final JsonObjectBuilder geomObj = Json.createObjectBuilder();
|
---|
[6806] | 179 | p.accept(new GeometryPrimitiveVisitor(geomObj));
|
---|
[6756] | 180 |
|
---|
| 181 | // Build primitive JSON object
|
---|
| 182 | array.add(Json.createObjectBuilder()
|
---|
| 183 | .add("type", "Feature")
|
---|
| 184 | .add("properties", propObj)
|
---|
| 185 | .add("geometry", geomObj));
|
---|
[4886] | 186 | }
|
---|
[6485] | 187 |
|
---|
[8813] | 188 | protected void appendLayerBounds(DataSet ds, JsonObjectBuilder object) {
|
---|
[6756] | 189 | if (ds != null) {
|
---|
| 190 | Iterator<Bounds> it = ds.getDataSourceBounds().iterator();
|
---|
| 191 | if (it.hasNext()) {
|
---|
| 192 | Bounds b = new Bounds(it.next());
|
---|
| 193 | while (it.hasNext()) {
|
---|
| 194 | b.extend(it.next());
|
---|
| 195 | }
|
---|
| 196 | appendBounds(b, object);
|
---|
[6485] | 197 | }
|
---|
| 198 | }
|
---|
| 199 | }
|
---|
| 200 |
|
---|
[8813] | 201 | protected void appendBounds(Bounds b, JsonObjectBuilder object) {
|
---|
[6485] | 202 | if (b != null) {
|
---|
[8813] | 203 | JsonArrayBuilder builder = Json.createArrayBuilder();
|
---|
| 204 | getCoorArray(builder, b.getMin());
|
---|
| 205 | getCoorArray(builder, b.getMax());
|
---|
| 206 | object.add("bbox", builder);
|
---|
[6485] | 207 | }
|
---|
| 208 | }
|
---|
[6756] | 209 |
|
---|
[8813] | 210 | protected void appendLayerFeatures(DataSet ds, JsonObjectBuilder object) {
|
---|
[6756] | 211 | JsonArrayBuilder array = Json.createArrayBuilder();
|
---|
| 212 | if (ds != null) {
|
---|
[10817] | 213 | ds.allPrimitives().forEach(p -> appendPrimitive(p, array));
|
---|
[6756] | 214 | }
|
---|
| 215 | object.add("features", array);
|
---|
| 216 | }
|
---|
[4886] | 217 | }
|
---|