source: josm/trunk/src/org/openstreetmap/josm/io/OsmWriter.java@ 9827

Last change on this file since 9827 was 9310, checked in by simon04, 8 years ago

fix #11773 - Saving osm files in JOSM will save the nodes in one of two orders

  • Property svn:eol-style set to native
File size: 11.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.io;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.io.PrintWriter;
7import java.util.ArrayList;
8import java.util.Collection;
9import java.util.Collections;
10import java.util.Comparator;
11import java.util.List;
12import java.util.Map.Entry;
13
14import org.openstreetmap.josm.data.DataSource;
15import org.openstreetmap.josm.data.coor.CoordinateFormat;
16import org.openstreetmap.josm.data.coor.LatLon;
17import org.openstreetmap.josm.data.osm.AbstractPrimitive;
18import org.openstreetmap.josm.data.osm.Changeset;
19import org.openstreetmap.josm.data.osm.DataSet;
20import org.openstreetmap.josm.data.osm.INode;
21import org.openstreetmap.josm.data.osm.IPrimitive;
22import org.openstreetmap.josm.data.osm.IRelation;
23import org.openstreetmap.josm.data.osm.IWay;
24import org.openstreetmap.josm.data.osm.Node;
25import org.openstreetmap.josm.data.osm.OsmPrimitive;
26import org.openstreetmap.josm.data.osm.Relation;
27import org.openstreetmap.josm.data.osm.Tagged;
28import org.openstreetmap.josm.data.osm.Way;
29import org.openstreetmap.josm.data.osm.visitor.PrimitiveVisitor;
30import org.openstreetmap.josm.gui.layer.OsmDataLayer;
31import org.openstreetmap.josm.tools.date.DateUtils;
32
33/**
34 * Save the dataset into a stream as osm intern xml format. This is not using any
35 * xml library for storing.
36 * @author imi
37 */
38public class OsmWriter extends XmlWriter implements PrimitiveVisitor {
39
40 public static final String DEFAULT_API_VERSION = "0.6";
41
42 private final boolean osmConform;
43 private boolean withBody = true;
44 private boolean isOsmChange;
45 private String version;
46 private Changeset changeset;
47
48 /**
49 * Constructs a new {@code OsmWriter}.
50 * Do not call this directly. Use {@link OsmWriterFactory} instead.
51 * @param out print writer
52 * @param osmConform if {@code true}, prevents modification attributes to be written to the common part
53 * @param version OSM API version (0.6)
54 */
55 protected OsmWriter(PrintWriter out, boolean osmConform, String version) {
56 super(out);
57 this.osmConform = osmConform;
58 this.version = (version == null ? DEFAULT_API_VERSION : version);
59 }
60
61 public void setWithBody(boolean wb) {
62 this.withBody = wb;
63 }
64
65 public void setIsOsmChange(boolean isOsmChange) {
66 this.isOsmChange = isOsmChange;
67 }
68
69 public void setChangeset(Changeset cs) {
70 this.changeset = cs;
71 }
72
73 public void setVersion(String v) {
74 this.version = v;
75 }
76
77 public void header() {
78 header(null);
79 }
80
81 public void header(Boolean upload) {
82 out.println("<?xml version='1.0' encoding='UTF-8'?>");
83 out.print("<osm version='");
84 out.print(version);
85 if (upload != null) {
86 out.print("' upload='");
87 out.print(upload);
88 }
89 out.println("' generator='JOSM'>");
90 }
91
92 public void footer() {
93 out.println("</osm>");
94 }
95
96 /**
97 * Sorts {@code -1} &rarr; {@code -infinity}, then {@code +1} &rarr; {@code +infinity}
98 */
99 protected static final Comparator<AbstractPrimitive> byIdComparator = new Comparator<AbstractPrimitive>() {
100 @Override public int compare(AbstractPrimitive o1, AbstractPrimitive o2) {
101 final long i1 = o1.getUniqueId();
102 final long i2 = o2.getUniqueId();
103 if (i1 < 0 && i2 < 0) {
104 return Long.compare(i2, i1);
105 } else {
106 return Long.compare(i1, i2);
107 }
108 }
109 };
110
111 protected <T extends OsmPrimitive> Collection<T> sortById(Collection<T> primitives) {
112 List<T> result = new ArrayList<>(primitives.size());
113 result.addAll(primitives);
114 Collections.sort(result, byIdComparator);
115 return result;
116 }
117
118 public void writeLayer(OsmDataLayer layer) {
119 header(!layer.isUploadDiscouraged());
120 writeDataSources(layer.data);
121 writeContent(layer.data);
122 footer();
123 }
124
125 /**
126 * Writes the contents of the given dataset (nodes, then ways, then relations)
127 * @param ds The dataset to write
128 */
129 public void writeContent(DataSet ds) {
130 writeNodes(ds.getNodes());
131 writeWays(ds.getWays());
132 writeRelations(ds.getRelations());
133 }
134
135 /**
136 * Writes the given nodes sorted by id
137 * @param nodes The nodes to write
138 * @since 5737
139 */
140 public void writeNodes(Collection<Node> nodes) {
141 for (Node n : sortById(nodes)) {
142 if (shouldWrite(n)) {
143 visit(n);
144 }
145 }
146 }
147
148 /**
149 * Writes the given ways sorted by id
150 * @param ways The ways to write
151 * @since 5737
152 */
153 public void writeWays(Collection<Way> ways) {
154 for (Way w : sortById(ways)) {
155 if (shouldWrite(w)) {
156 visit(w);
157 }
158 }
159 }
160
161 /**
162 * Writes the given relations sorted by id
163 * @param relations The relations to write
164 * @since 5737
165 */
166 public void writeRelations(Collection<Relation> relations) {
167 for (Relation r : sortById(relations)) {
168 if (shouldWrite(r)) {
169 visit(r);
170 }
171 }
172 }
173
174 protected boolean shouldWrite(OsmPrimitive osm) {
175 return !osm.isNewOrUndeleted() || !osm.isDeleted();
176 }
177
178 public void writeDataSources(DataSet ds) {
179 for (DataSource s : ds.dataSources) {
180 out.println(" <bounds minlat='"
181 + s.bounds.getMin().latToString(CoordinateFormat.DECIMAL_DEGREES)
182 +"' minlon='"
183 + s.bounds.getMin().lonToString(CoordinateFormat.DECIMAL_DEGREES)
184 +"' maxlat='"
185 + s.bounds.getMax().latToString(CoordinateFormat.DECIMAL_DEGREES)
186 +"' maxlon='"
187 + s.bounds.getMax().lonToString(CoordinateFormat.DECIMAL_DEGREES)
188 +"' origin='"+XmlWriter.encode(s.origin)+"' />");
189 }
190 }
191
192 @Override
193 public void visit(INode n) {
194 if (n.isIncomplete()) return;
195 addCommon(n, "node");
196 if (!withBody) {
197 out.println("/>");
198 } else {
199 if (n.getCoor() != null) {
200 out.print(" lat='"+LatLon.cDdHighPecisionFormatter.format(n.getCoor().lat())+
201 "' lon='"+LatLon.cDdHighPecisionFormatter.format(n.getCoor().lon())+'\'');
202 }
203 addTags(n, "node", true);
204 }
205 }
206
207 @Override
208 public void visit(IWay w) {
209 if (w.isIncomplete()) return;
210 addCommon(w, "way");
211 if (!withBody) {
212 out.println("/>");
213 } else {
214 out.println(">");
215 for (int i = 0; i < w.getNodesCount(); ++i) {
216 out.println(" <nd ref='"+w.getNodeId(i) +"' />");
217 }
218 addTags(w, "way", false);
219 }
220 }
221
222 @Override
223 public void visit(IRelation e) {
224 if (e.isIncomplete()) return;
225 addCommon(e, "relation");
226 if (!withBody) {
227 out.println("/>");
228 } else {
229 out.println(">");
230 for (int i = 0; i < e.getMembersCount(); ++i) {
231 out.print(" <member type='");
232 out.print(e.getMemberType(i).getAPIName());
233 out.println("' ref='"+e.getMemberId(i)+"' role='" +
234 XmlWriter.encode(e.getRole(i)) + "' />");
235 }
236 addTags(e, "relation", false);
237 }
238 }
239
240 public void visit(Changeset cs) {
241 out.print(" <changeset ");
242 out.print(" id='"+cs.getId()+'\'');
243 if (cs.getUser() != null) {
244 out.print(" user='"+cs.getUser().getName() +'\'');
245 out.print(" uid='"+cs.getUser().getId() +'\'');
246 }
247 if (cs.getCreatedAt() != null) {
248 out.print(" created_at='"+DateUtils.fromDate(cs.getCreatedAt()) +'\'');
249 }
250 if (cs.getClosedAt() != null) {
251 out.print(" closed_at='"+DateUtils.fromDate(cs.getClosedAt()) +'\'');
252 }
253 out.print(" open='"+ (cs.isOpen() ? "true" : "false") +'\'');
254 if (cs.getMin() != null) {
255 out.print(" min_lon='"+ cs.getMin().lonToString(CoordinateFormat.DECIMAL_DEGREES) +'\'');
256 out.print(" min_lat='"+ cs.getMin().latToString(CoordinateFormat.DECIMAL_DEGREES) +'\'');
257 }
258 if (cs.getMax() != null) {
259 out.print(" max_lon='"+ cs.getMin().lonToString(CoordinateFormat.DECIMAL_DEGREES) +'\'');
260 out.print(" max_lat='"+ cs.getMin().latToString(CoordinateFormat.DECIMAL_DEGREES) +'\'');
261 }
262 out.println(">");
263 addTags(cs, "changeset", false); // also writes closing </changeset>
264 }
265
266 protected static final Comparator<Entry<String, String>> byKeyComparator = new Comparator<Entry<String, String>>() {
267 @Override
268 public int compare(Entry<String, String> o1, Entry<String, String> o2) {
269 return o1.getKey().compareTo(o2.getKey());
270 }
271 };
272
273 protected void addTags(Tagged osm, String tagname, boolean tagOpen) {
274 if (osm.hasKeys()) {
275 if (tagOpen) {
276 out.println(">");
277 }
278 List<Entry<String, String>> entries = new ArrayList<>(osm.getKeys().entrySet());
279 Collections.sort(entries, byKeyComparator);
280 for (Entry<String, String> e : entries) {
281 out.println(" <tag k='"+ XmlWriter.encode(e.getKey()) +
282 "' v='"+XmlWriter.encode(e.getValue())+ "' />");
283 }
284 out.println(" </" + tagname + '>');
285 } else if (tagOpen) {
286 out.println(" />");
287 } else {
288 out.println(" </" + tagname + '>');
289 }
290 }
291
292 /**
293 * Add the common part as the form of the tag as well as the XML attributes
294 * id, action, user, and visible.
295 * @param osm osm primitive
296 * @param tagname XML tag matching osm primitive (node, way, relation)
297 */
298 protected void addCommon(IPrimitive osm, String tagname) {
299 out.print(" <"+tagname);
300 if (osm.getUniqueId() != 0) {
301 out.print(" id='"+ osm.getUniqueId()+'\'');
302 } else
303 throw new IllegalStateException(tr("Unexpected id 0 for osm primitive found"));
304 if (!isOsmChange) {
305 if (!osmConform) {
306 String action = null;
307 if (osm.isDeleted()) {
308 action = "delete";
309 } else if (osm.isModified()) {
310 action = "modify";
311 }
312 if (action != null) {
313 out.print(" action='"+action+'\'');
314 }
315 }
316 if (!osm.isTimestampEmpty()) {
317 out.print(" timestamp='"+DateUtils.fromTimestamp(osm.getRawTimestamp())+'\'');
318 }
319 // user and visible added with 0.4 API
320 if (osm.getUser() != null) {
321 if (osm.getUser().isLocalUser()) {
322 out.print(" user='"+XmlWriter.encode(osm.getUser().getName())+'\'');
323 } else if (osm.getUser().isOsmUser()) {
324 // uid added with 0.6
325 out.print(" uid='"+ osm.getUser().getId()+'\'');
326 out.print(" user='"+XmlWriter.encode(osm.getUser().getName())+'\'');
327 }
328 }
329 out.print(" visible='"+osm.isVisible()+'\'');
330 }
331 if (osm.getVersion() != 0) {
332 out.print(" version='"+osm.getVersion()+'\'');
333 }
334 if (this.changeset != null && this.changeset.getId() != 0) {
335 out.print(" changeset='"+this.changeset.getId()+'\'');
336 } else if (osm.getChangesetId() > 0 && !osm.isNew()) {
337 out.print(" changeset='"+osm.getChangesetId()+'\'');
338 }
339 }
340}
Note: See TracBrowser for help on using the repository browser.