| 1 | // License: GPL. Copyright 2007 by Immanuel Scholz and others |
|---|
| 2 | package org.openstreetmap.josm.io; |
|---|
| 3 | |
|---|
| 4 | import static org.openstreetmap.josm.tools.I18n.tr; |
|---|
| 5 | |
|---|
| 6 | import java.io.PrintWriter; |
|---|
| 7 | import java.util.ArrayList; |
|---|
| 8 | import java.util.Collection; |
|---|
| 9 | import java.util.Collections; |
|---|
| 10 | import java.util.Comparator; |
|---|
| 11 | import java.util.List; |
|---|
| 12 | import java.util.Map.Entry; |
|---|
| 13 | |
|---|
| 14 | import org.openstreetmap.josm.data.coor.CoordinateFormat; |
|---|
| 15 | import org.openstreetmap.josm.data.osm.Changeset; |
|---|
| 16 | import org.openstreetmap.josm.data.osm.DataSet; |
|---|
| 17 | import org.openstreetmap.josm.data.osm.DataSource; |
|---|
| 18 | import org.openstreetmap.josm.data.osm.INode; |
|---|
| 19 | import org.openstreetmap.josm.data.osm.IPrimitive; |
|---|
| 20 | import org.openstreetmap.josm.data.osm.IRelation; |
|---|
| 21 | import org.openstreetmap.josm.data.osm.IWay; |
|---|
| 22 | import org.openstreetmap.josm.data.osm.Node; |
|---|
| 23 | import org.openstreetmap.josm.data.osm.OsmPrimitive; |
|---|
| 24 | import org.openstreetmap.josm.data.osm.Relation; |
|---|
| 25 | import org.openstreetmap.josm.data.osm.Tagged; |
|---|
| 26 | import org.openstreetmap.josm.data.osm.Way; |
|---|
| 27 | import org.openstreetmap.josm.data.osm.visitor.PrimitiveVisitor; |
|---|
| 28 | import org.openstreetmap.josm.gui.layer.OsmDataLayer; |
|---|
| 29 | import org.openstreetmap.josm.tools.DateUtils; |
|---|
| 30 | |
|---|
| 31 | /** |
|---|
| 32 | * Save the dataset into a stream as osm intern xml format. This is not using any |
|---|
| 33 | * xml library for storing. |
|---|
| 34 | * @author imi |
|---|
| 35 | */ |
|---|
| 36 | public class OsmWriter extends XmlWriter implements PrimitiveVisitor { |
|---|
| 37 | |
|---|
| 38 | public static final String DEFAULT_API_VERSION = "0.6"; |
|---|
| 39 | |
|---|
| 40 | private boolean osmConform; |
|---|
| 41 | private boolean withBody = true; |
|---|
| 42 | private String version; |
|---|
| 43 | private Changeset changeset; |
|---|
| 44 | |
|---|
| 45 | /** |
|---|
| 46 | * Do not call this directly. Use OsmWriterFactory instead. |
|---|
| 47 | */ |
|---|
| 48 | protected OsmWriter(PrintWriter out, boolean osmConform, String version) { |
|---|
| 49 | super(out); |
|---|
| 50 | this.osmConform = osmConform; |
|---|
| 51 | this.version = (version == null ? DEFAULT_API_VERSION : version); |
|---|
| 52 | } |
|---|
| 53 | |
|---|
| 54 | public void setWithBody(boolean wb) { |
|---|
| 55 | this.withBody = wb; |
|---|
| 56 | } |
|---|
| 57 | public void setChangeset(Changeset cs) { |
|---|
| 58 | this.changeset = cs; |
|---|
| 59 | } |
|---|
| 60 | public void setVersion(String v) { |
|---|
| 61 | this.version = v; |
|---|
| 62 | } |
|---|
| 63 | |
|---|
| 64 | public void header() { |
|---|
| 65 | header(null); |
|---|
| 66 | } |
|---|
| 67 | public void header(Boolean upload) { |
|---|
| 68 | out.println("<?xml version='1.0' encoding='UTF-8'?>"); |
|---|
| 69 | out.print("<osm version='"); |
|---|
| 70 | out.print(version); |
|---|
| 71 | if (upload != null) { |
|---|
| 72 | out.print("' upload='"); |
|---|
| 73 | out.print(upload); |
|---|
| 74 | } |
|---|
| 75 | out.println("' generator='JOSM'>"); |
|---|
| 76 | } |
|---|
| 77 | public void footer() { |
|---|
| 78 | out.println("</osm>"); |
|---|
| 79 | } |
|---|
| 80 | |
|---|
| 81 | protected static final Comparator<OsmPrimitive> byIdComparator = new Comparator<OsmPrimitive>() { |
|---|
| 82 | @Override public int compare(OsmPrimitive o1, OsmPrimitive o2) { |
|---|
| 83 | return (o1.getUniqueId()<o2.getUniqueId() ? -1 : (o1.getUniqueId()==o2.getUniqueId() ? 0 : 1)); |
|---|
| 84 | } |
|---|
| 85 | }; |
|---|
| 86 | |
|---|
| 87 | protected Collection<OsmPrimitive> sortById(Collection<? extends OsmPrimitive> primitives) { |
|---|
| 88 | List<OsmPrimitive> result = new ArrayList<OsmPrimitive>(primitives.size()); |
|---|
| 89 | result.addAll(primitives); |
|---|
| 90 | Collections.sort(result, byIdComparator); |
|---|
| 91 | return result; |
|---|
| 92 | } |
|---|
| 93 | |
|---|
| 94 | public void writeLayer(OsmDataLayer layer) { |
|---|
| 95 | header(!layer.isUploadDiscouraged()); |
|---|
| 96 | writeDataSources(layer.data); |
|---|
| 97 | writeContent(layer.data); |
|---|
| 98 | footer(); |
|---|
| 99 | } |
|---|
| 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 | out.print(" lat='"+n.getCoor().lat()+"' lon='"+n.getCoor().lon()+"'"); |
|---|
| 139 | if (!withBody) { |
|---|
| 140 | out.println("/>"); |
|---|
| 141 | } else { |
|---|
| 142 | addTags(n, "node", true); |
|---|
| 143 | } |
|---|
| 144 | } |
|---|
| 145 | |
|---|
| 146 | @Override |
|---|
| 147 | public void visit(IWay w) { |
|---|
| 148 | if (w.isIncomplete()) return; |
|---|
| 149 | addCommon(w, "way"); |
|---|
| 150 | if (!withBody) { |
|---|
| 151 | out.println("/>"); |
|---|
| 152 | } else { |
|---|
| 153 | out.println(">"); |
|---|
| 154 | for (int i=0; i<w.getNodesCount(); ++i) { |
|---|
| 155 | out.println(" <nd ref='"+w.getNodeId(i) +"' />"); |
|---|
| 156 | } |
|---|
| 157 | addTags(w, "way", false); |
|---|
| 158 | } |
|---|
| 159 | } |
|---|
| 160 | |
|---|
| 161 | @Override |
|---|
| 162 | public void visit(IRelation e) { |
|---|
| 163 | if (e.isIncomplete()) return; |
|---|
| 164 | addCommon(e, "relation"); |
|---|
| 165 | if (!withBody) { |
|---|
| 166 | out.println("/>"); |
|---|
| 167 | } else { |
|---|
| 168 | out.println(">"); |
|---|
| 169 | for (int i=0; i<e.getMembersCount(); ++i) { |
|---|
| 170 | out.print(" <member type='"); |
|---|
| 171 | out.print(e.getMemberType(i).getAPIName()); |
|---|
| 172 | out.println("' ref='"+e.getMemberId(i)+"' role='" + |
|---|
| 173 | XmlWriter.encode(e.getRole(i)) + "' />"); |
|---|
| 174 | } |
|---|
| 175 | addTags(e, "relation", false); |
|---|
| 176 | } |
|---|
| 177 | } |
|---|
| 178 | |
|---|
| 179 | public void visit(Changeset cs) { |
|---|
| 180 | out.print(" <changeset "); |
|---|
| 181 | out.print(" id='"+cs.getId()+"'"); |
|---|
| 182 | if (cs.getUser() != null) { |
|---|
| 183 | out.print(" user='"+cs.getUser().getName() +"'"); |
|---|
| 184 | out.print(" uid='"+cs.getUser().getId() +"'"); |
|---|
| 185 | } |
|---|
| 186 | if (cs.getCreatedAt() != null) { |
|---|
| 187 | out.print(" created_at='"+DateUtils.fromDate(cs.getCreatedAt()) +"'"); |
|---|
| 188 | } |
|---|
| 189 | if (cs.getClosedAt() != null) { |
|---|
| 190 | out.print(" closed_at='"+DateUtils.fromDate(cs.getClosedAt()) +"'"); |
|---|
| 191 | } |
|---|
| 192 | out.print(" open='"+ (cs.isOpen() ? "true" : "false") +"'"); |
|---|
| 193 | if (cs.getMin() != null) { |
|---|
| 194 | out.print(" min_lon='"+ cs.getMin().lonToString(CoordinateFormat.DECIMAL_DEGREES) +"'"); |
|---|
| 195 | out.print(" min_lat='"+ cs.getMin().latToString(CoordinateFormat.DECIMAL_DEGREES) +"'"); |
|---|
| 196 | } |
|---|
| 197 | if (cs.getMax() != null) { |
|---|
| 198 | out.print(" max_lon='"+ cs.getMin().lonToString(CoordinateFormat.DECIMAL_DEGREES) +"'"); |
|---|
| 199 | out.print(" max_lat='"+ cs.getMin().latToString(CoordinateFormat.DECIMAL_DEGREES) +"'"); |
|---|
| 200 | } |
|---|
| 201 | out.println(">"); |
|---|
| 202 | addTags(cs, "changeset", false); // also writes closing </changeset> |
|---|
| 203 | } |
|---|
| 204 | |
|---|
| 205 | protected static final Comparator<Entry<String, String>> byKeyComparator = new Comparator<Entry<String,String>>() { |
|---|
| 206 | @Override public int compare(Entry<String, String> o1, Entry<String, String> o2) { |
|---|
| 207 | return o1.getKey().compareTo(o2.getKey()); |
|---|
| 208 | } |
|---|
| 209 | }; |
|---|
| 210 | |
|---|
| 211 | protected void addTags(Tagged osm, String tagname, boolean tagOpen) { |
|---|
| 212 | if (osm.hasKeys()) { |
|---|
| 213 | if (tagOpen) { |
|---|
| 214 | out.println(">"); |
|---|
| 215 | } |
|---|
| 216 | List<Entry<String, String>> entries = new ArrayList<Entry<String,String>>(osm.getKeys().entrySet()); |
|---|
| 217 | Collections.sort(entries, byKeyComparator); |
|---|
| 218 | for (Entry<String, String> e : entries) { |
|---|
| 219 | if ((osm instanceof Changeset) || !("created_by".equals(e.getKey()))) { |
|---|
| 220 | out.println(" <tag k='"+ XmlWriter.encode(e.getKey()) + |
|---|
| 221 | "' v='"+XmlWriter.encode(e.getValue())+ "' />"); |
|---|
| 222 | } |
|---|
| 223 | } |
|---|
| 224 | out.println(" </" + tagname + ">"); |
|---|
| 225 | } else if (tagOpen) { |
|---|
| 226 | out.println(" />"); |
|---|
| 227 | } else { |
|---|
| 228 | out.println(" </" + tagname + ">"); |
|---|
| 229 | } |
|---|
| 230 | } |
|---|
| 231 | |
|---|
| 232 | /** |
|---|
| 233 | * Add the common part as the form of the tag as well as the XML attributes |
|---|
| 234 | * id, action, user, and visible. |
|---|
| 235 | */ |
|---|
| 236 | protected void addCommon(IPrimitive osm, String tagname) { |
|---|
| 237 | out.print(" <"+tagname); |
|---|
| 238 | if (osm.getUniqueId() != 0) { |
|---|
| 239 | out.print(" id='"+ osm.getUniqueId()+"'"); |
|---|
| 240 | } else |
|---|
| 241 | throw new IllegalStateException(tr("Unexpected id 0 for osm primitive found")); |
|---|
| 242 | if (!osmConform) { |
|---|
| 243 | String action = null; |
|---|
| 244 | if (osm.isDeleted()) { |
|---|
| 245 | action = "delete"; |
|---|
| 246 | } else if (osm.isModified()) { |
|---|
| 247 | action = "modify"; |
|---|
| 248 | } |
|---|
| 249 | if (action != null) { |
|---|
| 250 | out.print(" action='"+action+"'"); |
|---|
| 251 | } |
|---|
| 252 | } |
|---|
| 253 | if (!osm.isTimestampEmpty()) { |
|---|
| 254 | out.print(" timestamp='"+DateUtils.fromDate(osm.getTimestamp())+"'"); |
|---|
| 255 | } |
|---|
| 256 | // user and visible added with 0.4 API |
|---|
| 257 | if (osm.getUser() != null) { |
|---|
| 258 | if(osm.getUser().isLocalUser()) { |
|---|
| 259 | out.print(" user='"+XmlWriter.encode(osm.getUser().getName())+"'"); |
|---|
| 260 | } else if (osm.getUser().isOsmUser()) { |
|---|
| 261 | // uid added with 0.6 |
|---|
| 262 | out.print(" uid='"+ osm.getUser().getId()+"'"); |
|---|
| 263 | out.print(" user='"+XmlWriter.encode(osm.getUser().getName())+"'"); |
|---|
| 264 | } |
|---|
| 265 | } |
|---|
| 266 | out.print(" visible='"+osm.isVisible()+"'"); |
|---|
| 267 | if (osm.getVersion() != 0) { |
|---|
| 268 | out.print(" version='"+osm.getVersion()+"'"); |
|---|
| 269 | } |
|---|
| 270 | if (this.changeset != null && this.changeset.getId() != 0) { |
|---|
| 271 | out.print(" changeset='"+this.changeset.getId()+"'" ); |
|---|
| 272 | } else if (osm.getChangesetId() > 0 && !osm.isNew()) { |
|---|
| 273 | out.print(" changeset='"+osm.getChangesetId()+"'" ); |
|---|
| 274 | } |
|---|
| 275 | } |
|---|
| 276 | |
|---|
| 277 | public void close() { |
|---|
| 278 | out.close(); |
|---|
| 279 | } |
|---|
| 280 | |
|---|
| 281 | @Override |
|---|
| 282 | public void flush() { |
|---|
| 283 | out.flush(); |
|---|
| 284 | } |
|---|
| 285 | } |
|---|