1 | package org.openstreetmap.josm.io;
|
---|
2 |
|
---|
3 | import java.io.IOException;
|
---|
4 | import java.io.Writer;
|
---|
5 | import java.util.HashMap;
|
---|
6 | import java.util.LinkedList;
|
---|
7 | import java.util.Map;
|
---|
8 | import java.util.StringTokenizer;
|
---|
9 | import java.util.Map.Entry;
|
---|
10 |
|
---|
11 | import org.jdom.Document;
|
---|
12 | import org.jdom.Element;
|
---|
13 | import org.jdom.Namespace;
|
---|
14 | import org.jdom.output.Format;
|
---|
15 | import org.jdom.output.XMLOutputter;
|
---|
16 | import org.openstreetmap.josm.Main;
|
---|
17 | import org.openstreetmap.josm.data.osm.Key;
|
---|
18 | import org.openstreetmap.josm.data.osm.LineSegment;
|
---|
19 | import org.openstreetmap.josm.data.osm.Node;
|
---|
20 | import org.openstreetmap.josm.data.osm.OsmPrimitive;
|
---|
21 | import org.openstreetmap.josm.data.osm.Track;
|
---|
22 |
|
---|
23 | /**
|
---|
24 | * Exports a dataset to GPX data. All information available are tried to store in
|
---|
25 | * the gpx. If no corresponding tag is available in GPX, use
|
---|
26 | * <code><extensions></code> instead.
|
---|
27 | *
|
---|
28 | * GPX-Track segments are stored as 2-node-pairs, so no <trkseg> with more
|
---|
29 | * or less than 2 <trkpt> are exported.
|
---|
30 | *
|
---|
31 | * @author imi
|
---|
32 | */
|
---|
33 | public class GpxWriter {
|
---|
34 |
|
---|
35 | /**
|
---|
36 | * The GPX namespace used.
|
---|
37 | */
|
---|
38 | public static final Namespace GPX = Namespace.getNamespace("http://www.topografix.com/GPX/1/0");
|
---|
39 | /**
|
---|
40 | * The OSM namespace used (for extensions).
|
---|
41 | */
|
---|
42 | public static final Namespace OSM = Namespace.getNamespace("osm", "http://www.openstreetmap.org");
|
---|
43 | /**
|
---|
44 | * The JOSM namespace (for JOSM-extensions).
|
---|
45 | */
|
---|
46 | public static final Namespace JOSM = Namespace.getNamespace("josm", "http://wiki.eigenheimstrasse.de/wiki/JOSM");
|
---|
47 |
|
---|
48 | /**
|
---|
49 | * This is the output writer to store the resulting data in.
|
---|
50 | */
|
---|
51 | private Writer out;
|
---|
52 |
|
---|
53 | /**
|
---|
54 | * Create a GpxWrite from an output writer. As example to write in a file,
|
---|
55 | * use FileWriter.
|
---|
56 | *
|
---|
57 | * @param out The Writer to store the result data in.
|
---|
58 | */
|
---|
59 | public GpxWriter(Writer out) {
|
---|
60 | this.out = out;
|
---|
61 | }
|
---|
62 |
|
---|
63 |
|
---|
64 | /**
|
---|
65 | * Do the output in the former set writer.
|
---|
66 | * @exception IOException In case of IO errors, throw this exception.
|
---|
67 | */
|
---|
68 | public void output() throws IOException {
|
---|
69 | Element root = parseDataSet();
|
---|
70 | root.addNamespaceDeclaration(OSM);
|
---|
71 | root.addNamespaceDeclaration(JOSM);
|
---|
72 | Document d = new Document(root);
|
---|
73 | XMLOutputter xmlOut = new XMLOutputter(Format.getPrettyFormat());
|
---|
74 | xmlOut.output(d, out);
|
---|
75 | }
|
---|
76 |
|
---|
77 |
|
---|
78 | /**
|
---|
79 | * Write the whole DataSet in an JDom-Element and return the new element.
|
---|
80 | * @return The created element, out of the dataset.
|
---|
81 | */
|
---|
82 | @SuppressWarnings("unchecked")
|
---|
83 | private Element parseDataSet() {
|
---|
84 | Element e = new Element("gpx", GPX);
|
---|
85 | e.setAttribute("version", "1.0");
|
---|
86 | e.setAttribute("creator", "JOSM");
|
---|
87 | // for getting all unreferenced waypoints in the wpt-list
|
---|
88 | LinkedList<Node> unrefNodes = new LinkedList<Node>(Main.main.ds.nodes);
|
---|
89 | // for getting all unreferenced line segments
|
---|
90 | LinkedList<LineSegment> unrefLs = new LinkedList<LineSegment>(Main.main.ds.lineSegments);
|
---|
91 |
|
---|
92 | // tracks
|
---|
93 | for (Track t : Main.main.ds.tracks) {
|
---|
94 | Element tElem = new Element("trk", GPX);
|
---|
95 | HashMap<Key, String> keys = null;
|
---|
96 | if (t.keys != null) {
|
---|
97 | keys = new HashMap<Key, String>(t.keys);
|
---|
98 | addAndRemovePropertyTag("name", tElem, keys);
|
---|
99 | addAndRemovePropertyTag("cmt", tElem, keys);
|
---|
100 | addAndRemovePropertyTag("desc", tElem, keys);
|
---|
101 | addAndRemovePropertyTag("src", tElem, keys);
|
---|
102 | addAndRemovePropertyLinkTag(tElem, keys);
|
---|
103 | addAndRemovePropertyTag("number", tElem, keys);
|
---|
104 | addAndRemovePropertyTag("type", tElem, keys);
|
---|
105 | }
|
---|
106 | addPropertyExtensions(tElem, keys, t);
|
---|
107 |
|
---|
108 | // line segments
|
---|
109 | for (LineSegment ls : t.segments) {
|
---|
110 | tElem.getChildren().add(parseLineSegment(ls));
|
---|
111 | unrefNodes.remove(ls.start);
|
---|
112 | unrefNodes.remove(ls.end);
|
---|
113 | unrefLs.remove(ls);
|
---|
114 | }
|
---|
115 |
|
---|
116 | e.getChildren().add(tElem);
|
---|
117 | }
|
---|
118 |
|
---|
119 | // encode pending line segments as tracks
|
---|
120 | for (LineSegment ls : unrefLs) {
|
---|
121 | Element t = new Element("trk", GPX);
|
---|
122 | t.getChildren().add(parseLineSegment(ls));
|
---|
123 | unrefNodes.remove(ls.start);
|
---|
124 | unrefNodes.remove(ls.end);
|
---|
125 | Element ext = new Element("extensions", GPX);
|
---|
126 | ext.getChildren().add(new Element("segment", JOSM));
|
---|
127 | t.getChildren().add(ext);
|
---|
128 | e.getChildren().add(t);
|
---|
129 | }
|
---|
130 |
|
---|
131 | // waypoints (missing nodes)
|
---|
132 | for (Node n : unrefNodes)
|
---|
133 | e.getChildren().add(parseWaypoint(n, "wpt"));
|
---|
134 |
|
---|
135 | return e;
|
---|
136 | }
|
---|
137 |
|
---|
138 |
|
---|
139 | /**
|
---|
140 | * Parse a line segment and store it into a JDOM-Element. Return that element.
|
---|
141 | */
|
---|
142 | @SuppressWarnings("unchecked")
|
---|
143 | private Element parseLineSegment(LineSegment ls) {
|
---|
144 | Element lsElem = new Element("trkseg", GPX);
|
---|
145 | addPropertyExtensions(lsElem, ls.keys, ls);
|
---|
146 | lsElem.getChildren().add(parseWaypoint(ls.start, "trkpt"));
|
---|
147 | lsElem.getChildren().add(parseWaypoint(ls.end, "trkpt"));
|
---|
148 | return lsElem;
|
---|
149 | }
|
---|
150 |
|
---|
151 | /**
|
---|
152 | * Parse a waypoint (node) and store it into an JDOM-Element. Return that
|
---|
153 | * element.
|
---|
154 | *
|
---|
155 | * @param n The Node to parse and store
|
---|
156 | * @param name The name of the tag (different names for nodes in GPX)
|
---|
157 | * @return The resulting GPX-Element
|
---|
158 | */
|
---|
159 | private Element parseWaypoint(Node n, String name) {
|
---|
160 | Element e = new Element(name, GPX);
|
---|
161 | e.setAttribute("lat", Double.toString(n.coor.lat));
|
---|
162 | e.setAttribute("lon", Double.toString(n.coor.lon));
|
---|
163 | HashMap<Key, String> keys = null;
|
---|
164 | if (n.keys != null) {
|
---|
165 | keys = new HashMap<Key, String>(n.keys);
|
---|
166 | addAndRemovePropertyTag("ele", e, keys);
|
---|
167 | addAndRemovePropertyTag("time", e, keys);
|
---|
168 | addAndRemovePropertyTag("magvar", e, keys);
|
---|
169 | addAndRemovePropertyTag("geoidheight", e, keys);
|
---|
170 | addAndRemovePropertyTag("name", e, keys);
|
---|
171 | addAndRemovePropertyTag("cmt", e, keys);
|
---|
172 | addAndRemovePropertyTag("desc", e, keys);
|
---|
173 | addAndRemovePropertyTag("src", e, keys);
|
---|
174 | addAndRemovePropertyLinkTag(e, keys);
|
---|
175 | addAndRemovePropertyTag("sym", e, keys);
|
---|
176 | addAndRemovePropertyTag("type", e, keys);
|
---|
177 | addAndRemovePropertyTag("fix", e, keys);
|
---|
178 | addAndRemovePropertyTag("sat", e, keys);
|
---|
179 | addAndRemovePropertyTag("hdop", e, keys);
|
---|
180 | addAndRemovePropertyTag("vdop", e, keys);
|
---|
181 | addAndRemovePropertyTag("pdop", e, keys);
|
---|
182 | addAndRemovePropertyTag("ageofdgpsdata", e, keys);
|
---|
183 | addAndRemovePropertyTag("dgpsid", e, keys);
|
---|
184 | }
|
---|
185 | addPropertyExtensions(e, keys, n);
|
---|
186 | return e;
|
---|
187 | }
|
---|
188 |
|
---|
189 |
|
---|
190 | /**
|
---|
191 | * Add a link-tag to the element, if the property list contain a value named
|
---|
192 | * "link". The property is removed from the map afterwards.
|
---|
193 | *
|
---|
194 | * For the format, @see GpxReader#parseKeyValueLink(OsmPrimitive, Element).
|
---|
195 | * @param e The element to add the link to.
|
---|
196 | * @param keys The map containing the link property.
|
---|
197 | */
|
---|
198 | @SuppressWarnings("unchecked")
|
---|
199 | private void addAndRemovePropertyLinkTag(Element e, Map<Key, String> keys) {
|
---|
200 | Key key = Key.get("link");
|
---|
201 | String value = keys.get(key);
|
---|
202 | if (value != null) {
|
---|
203 | StringTokenizer st = new StringTokenizer(value, ";");
|
---|
204 | if (st.countTokens() != 2)
|
---|
205 | return;
|
---|
206 | Element link = new Element("link", GPX);
|
---|
207 | link.getChildren().add(new Element("type", GPX).setText(st.nextToken()));
|
---|
208 | link.getChildren().add(0,new Element("text", GPX).setText(st.nextToken()));
|
---|
209 | e.getChildren().add(link);
|
---|
210 | keys.remove(key);
|
---|
211 | }
|
---|
212 | }
|
---|
213 |
|
---|
214 |
|
---|
215 | /**
|
---|
216 | * Helper to add a property with a given name as tag to the element. This
|
---|
217 | * will look like <name><i>keys.get(name)</i></name>
|
---|
218 | *
|
---|
219 | * After adding, the property is removed from the map.
|
---|
220 | *
|
---|
221 | * If the property does not exist, nothing is done.
|
---|
222 | *
|
---|
223 | * @param name The properties name
|
---|
224 | * @param e The element to add the tag to.
|
---|
225 | * @param osm The data to get the property from.
|
---|
226 | */
|
---|
227 | @SuppressWarnings("unchecked")
|
---|
228 | private void addAndRemovePropertyTag(String name, Element e, Map<Key, String> keys) {
|
---|
229 | Key key = Key.get(name);
|
---|
230 | String value = keys.get(key);
|
---|
231 | if (value != null) {
|
---|
232 | e.getChildren().add(new Element(name, GPX).setText(value));
|
---|
233 | keys.remove(key);
|
---|
234 | }
|
---|
235 | }
|
---|
236 |
|
---|
237 | /**
|
---|
238 | * Add the property in the entry as <extensions> to the element
|
---|
239 | * @param e The element to add the property to.
|
---|
240 | * @param prop The property to add.
|
---|
241 | */
|
---|
242 | @SuppressWarnings("unchecked")
|
---|
243 | private void addPropertyExtensions(Element e, Map<Key, String> keys, OsmPrimitive osm) {
|
---|
244 | if ((keys == null || keys.isEmpty()) && osm.id == 0 && !osm.modified && !osm.modifiedProperties)
|
---|
245 | return;
|
---|
246 | Element extensions = e.getChild("extensions", GPX);
|
---|
247 | if (extensions == null)
|
---|
248 | e.getChildren().add(extensions = new Element("extensions", GPX));
|
---|
249 | if (keys != null && !keys.isEmpty()) {
|
---|
250 | for (Entry<Key, String> prop : keys.entrySet()) {
|
---|
251 | Element propElement = new Element("property", OSM);
|
---|
252 | propElement.setAttribute("key", prop.getKey().name);
|
---|
253 | propElement.setAttribute("value", prop.getValue());
|
---|
254 | extensions.getChildren().add(propElement);
|
---|
255 | }
|
---|
256 | }
|
---|
257 | if (osm.id != 0) {
|
---|
258 | Element propElement = new Element("uid", JOSM);
|
---|
259 | propElement.setText(""+osm.id);
|
---|
260 | extensions.getChildren().add(propElement);
|
---|
261 | }
|
---|
262 | if (osm.modified) {
|
---|
263 | Element modElement = new Element("modified", JOSM);
|
---|
264 | extensions.getChildren().add(modElement);
|
---|
265 | }
|
---|
266 | if (osm.modifiedProperties) {
|
---|
267 | Element modElement = new Element("modifiedProperties", JOSM);
|
---|
268 | extensions.getChildren().add(modElement);
|
---|
269 | }
|
---|
270 | }
|
---|
271 | }
|
---|