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

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

fix bug when writing a locked OSM layer

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