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

Last change on this file since 15735 was 15429, checked in by GerdP, 5 years ago

fix #17453: GeoJSON export error (17453-v2.patch)

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