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    }