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

Last change on this file since 11003 was 10852, checked in by Don-vip, 8 years ago

fix #13358 - GeoJSON no longer permits projections other than WGS84, see https://tools.ietf.org/html/rfc7946

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