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

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

extract DownloadPolicy / UploadPolicy to separate classes

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