source: josm/trunk/src/org/openstreetmap/josm/io/GeoJSONWriter.java@ 13750

Last change on this file since 13750 was 13589, checked in by Don-vip, 6 years ago

fix #16138 - do not output empty GeoJson geometries but null instead (as per ​RFC7946)

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