001 // License: GPL. Copyright 2007 by Immanuel Scholz and others 002 package org.openstreetmap.josm.io; 003 004 import static org.openstreetmap.josm.tools.I18n.tr; 005 006 import java.io.PrintWriter; 007 import java.util.ArrayList; 008 import java.util.Collection; 009 import java.util.Collections; 010 import java.util.Comparator; 011 import java.util.List; 012 import java.util.Map.Entry; 013 014 import org.openstreetmap.josm.data.coor.CoordinateFormat; 015 import org.openstreetmap.josm.data.osm.Changeset; 016 import org.openstreetmap.josm.data.osm.DataSet; 017 import org.openstreetmap.josm.data.osm.DataSource; 018 import org.openstreetmap.josm.data.osm.INode; 019 import org.openstreetmap.josm.data.osm.IPrimitive; 020 import org.openstreetmap.josm.data.osm.IRelation; 021 import org.openstreetmap.josm.data.osm.IWay; 022 import org.openstreetmap.josm.data.osm.Node; 023 import org.openstreetmap.josm.data.osm.OsmPrimitive; 024 import org.openstreetmap.josm.data.osm.Relation; 025 import org.openstreetmap.josm.data.osm.Tagged; 026 import org.openstreetmap.josm.data.osm.Way; 027 import org.openstreetmap.josm.data.osm.visitor.PrimitiveVisitor; 028 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 029 import org.openstreetmap.josm.tools.DateUtils; 030 031 /** 032 * Save the dataset into a stream as osm intern xml format. This is not using any 033 * xml library for storing. 034 * @author imi 035 */ 036 public class OsmWriter extends XmlWriter implements PrimitiveVisitor { 037 038 public static final String DEFAULT_API_VERSION = "0.6"; 039 040 private boolean osmConform; 041 private boolean withBody = true; 042 private String version; 043 private Changeset changeset; 044 045 /** 046 * Do not call this directly. Use OsmWriterFactory instead. 047 */ 048 protected OsmWriter(PrintWriter out, boolean osmConform, String version) { 049 super(out); 050 this.osmConform = osmConform; 051 this.version = (version == null ? DEFAULT_API_VERSION : version); 052 } 053 054 public void setWithBody(boolean wb) { 055 this.withBody = wb; 056 } 057 public void setChangeset(Changeset cs) { 058 this.changeset = cs; 059 } 060 public void setVersion(String v) { 061 this.version = v; 062 } 063 064 public void header() { 065 header(null); 066 } 067 public void header(Boolean upload) { 068 out.println("<?xml version='1.0' encoding='UTF-8'?>"); 069 out.print("<osm version='"); 070 out.print(version); 071 if (upload != null) { 072 out.print("' upload='"); 073 out.print(upload); 074 } 075 out.println("' generator='JOSM'>"); 076 } 077 public void footer() { 078 out.println("</osm>"); 079 } 080 081 protected static final Comparator<OsmPrimitive> byIdComparator = new Comparator<OsmPrimitive>() { 082 @Override public int compare(OsmPrimitive o1, OsmPrimitive o2) { 083 return (o1.getUniqueId()<o2.getUniqueId() ? -1 : (o1.getUniqueId()==o2.getUniqueId() ? 0 : 1)); 084 } 085 }; 086 087 protected Collection<OsmPrimitive> sortById(Collection<? extends OsmPrimitive> primitives) { 088 List<OsmPrimitive> result = new ArrayList<OsmPrimitive>(primitives.size()); 089 result.addAll(primitives); 090 Collections.sort(result, byIdComparator); 091 return result; 092 } 093 094 public void writeLayer(OsmDataLayer layer) { 095 header(!layer.isUploadDiscouraged()); 096 writeDataSources(layer.data); 097 writeContent(layer.data); 098 footer(); 099 } 100 101 public void writeContent(DataSet ds) { 102 for (OsmPrimitive n : sortById(ds.getNodes())) { 103 if (shouldWrite(n)) { 104 visit((Node)n); 105 } 106 } 107 for (OsmPrimitive w : sortById(ds.getWays())) { 108 if (shouldWrite(w)) { 109 visit((Way)w); 110 } 111 } 112 for (OsmPrimitive e: sortById(ds.getRelations())) { 113 if (shouldWrite(e)) { 114 visit((Relation)e); 115 } 116 } 117 } 118 119 protected boolean shouldWrite(OsmPrimitive osm) { 120 return !osm.isNewOrUndeleted() || !osm.isDeleted(); 121 } 122 123 public void writeDataSources(DataSet ds) { 124 for (DataSource s : ds.dataSources) { 125 out.println(" <bounds minlat='" 126 + s.bounds.getMin().lat()+"' minlon='" 127 + s.bounds.getMin().lon()+"' maxlat='" 128 + s.bounds.getMax().lat()+"' maxlon='" 129 + s.bounds.getMax().lon() 130 +"' origin='"+XmlWriter.encode(s.origin)+"' />"); 131 } 132 } 133 134 @Override 135 public void visit(INode n) { 136 if (n.isIncomplete()) return; 137 addCommon(n, "node"); 138 if (n.getCoor() != null) { 139 out.print(" lat='"+n.getCoor().lat()+"' lon='"+n.getCoor().lon()+"'"); 140 } 141 if (!withBody) { 142 out.println("/>"); 143 } else { 144 addTags(n, "node", true); 145 } 146 } 147 148 @Override 149 public void visit(IWay w) { 150 if (w.isIncomplete()) return; 151 addCommon(w, "way"); 152 if (!withBody) { 153 out.println("/>"); 154 } else { 155 out.println(">"); 156 for (int i=0; i<w.getNodesCount(); ++i) { 157 out.println(" <nd ref='"+w.getNodeId(i) +"' />"); 158 } 159 addTags(w, "way", false); 160 } 161 } 162 163 @Override 164 public void visit(IRelation e) { 165 if (e.isIncomplete()) return; 166 addCommon(e, "relation"); 167 if (!withBody) { 168 out.println("/>"); 169 } else { 170 out.println(">"); 171 for (int i=0; i<e.getMembersCount(); ++i) { 172 out.print(" <member type='"); 173 out.print(e.getMemberType(i).getAPIName()); 174 out.println("' ref='"+e.getMemberId(i)+"' role='" + 175 XmlWriter.encode(e.getRole(i)) + "' />"); 176 } 177 addTags(e, "relation", false); 178 } 179 } 180 181 public void visit(Changeset cs) { 182 out.print(" <changeset "); 183 out.print(" id='"+cs.getId()+"'"); 184 if (cs.getUser() != null) { 185 out.print(" user='"+cs.getUser().getName() +"'"); 186 out.print(" uid='"+cs.getUser().getId() +"'"); 187 } 188 if (cs.getCreatedAt() != null) { 189 out.print(" created_at='"+DateUtils.fromDate(cs.getCreatedAt()) +"'"); 190 } 191 if (cs.getClosedAt() != null) { 192 out.print(" closed_at='"+DateUtils.fromDate(cs.getClosedAt()) +"'"); 193 } 194 out.print(" open='"+ (cs.isOpen() ? "true" : "false") +"'"); 195 if (cs.getMin() != null) { 196 out.print(" min_lon='"+ cs.getMin().lonToString(CoordinateFormat.DECIMAL_DEGREES) +"'"); 197 out.print(" min_lat='"+ cs.getMin().latToString(CoordinateFormat.DECIMAL_DEGREES) +"'"); 198 } 199 if (cs.getMax() != null) { 200 out.print(" max_lon='"+ cs.getMin().lonToString(CoordinateFormat.DECIMAL_DEGREES) +"'"); 201 out.print(" max_lat='"+ cs.getMin().latToString(CoordinateFormat.DECIMAL_DEGREES) +"'"); 202 } 203 out.println(">"); 204 addTags(cs, "changeset", false); // also writes closing </changeset> 205 } 206 207 protected static final Comparator<Entry<String, String>> byKeyComparator = new Comparator<Entry<String,String>>() { 208 @Override public int compare(Entry<String, String> o1, Entry<String, String> o2) { 209 return o1.getKey().compareTo(o2.getKey()); 210 } 211 }; 212 213 protected void addTags(Tagged osm, String tagname, boolean tagOpen) { 214 if (osm.hasKeys()) { 215 if (tagOpen) { 216 out.println(">"); 217 } 218 List<Entry<String, String>> entries = new ArrayList<Entry<String,String>>(osm.getKeys().entrySet()); 219 Collections.sort(entries, byKeyComparator); 220 for (Entry<String, String> e : entries) { 221 if ((osm instanceof Changeset) || !("created_by".equals(e.getKey()))) { 222 out.println(" <tag k='"+ XmlWriter.encode(e.getKey()) + 223 "' v='"+XmlWriter.encode(e.getValue())+ "' />"); 224 } 225 } 226 out.println(" </" + tagname + ">"); 227 } else if (tagOpen) { 228 out.println(" />"); 229 } else { 230 out.println(" </" + tagname + ">"); 231 } 232 } 233 234 /** 235 * Add the common part as the form of the tag as well as the XML attributes 236 * id, action, user, and visible. 237 */ 238 protected void addCommon(IPrimitive osm, String tagname) { 239 out.print(" <"+tagname); 240 if (osm.getUniqueId() != 0) { 241 out.print(" id='"+ osm.getUniqueId()+"'"); 242 } else 243 throw new IllegalStateException(tr("Unexpected id 0 for osm primitive found")); 244 if (!osmConform) { 245 String action = null; 246 if (osm.isDeleted()) { 247 action = "delete"; 248 } else if (osm.isModified()) { 249 action = "modify"; 250 } 251 if (action != null) { 252 out.print(" action='"+action+"'"); 253 } 254 } 255 if (!osm.isTimestampEmpty()) { 256 out.print(" timestamp='"+DateUtils.fromDate(osm.getTimestamp())+"'"); 257 } 258 // user and visible added with 0.4 API 259 if (osm.getUser() != null) { 260 if(osm.getUser().isLocalUser()) { 261 out.print(" user='"+XmlWriter.encode(osm.getUser().getName())+"'"); 262 } else if (osm.getUser().isOsmUser()) { 263 // uid added with 0.6 264 out.print(" uid='"+ osm.getUser().getId()+"'"); 265 out.print(" user='"+XmlWriter.encode(osm.getUser().getName())+"'"); 266 } 267 } 268 out.print(" visible='"+osm.isVisible()+"'"); 269 if (osm.getVersion() != 0) { 270 out.print(" version='"+osm.getVersion()+"'"); 271 } 272 if (this.changeset != null && this.changeset.getId() != 0) { 273 out.print(" changeset='"+this.changeset.getId()+"'" ); 274 } else if (osm.getChangesetId() > 0 && !osm.isNew()) { 275 out.print(" changeset='"+osm.getChangesetId()+"'" ); 276 } 277 } 278 279 public void close() { 280 out.close(); 281 } 282 283 @Override 284 public void flush() { 285 out.flush(); 286 } 287 }