source: josm/trunk/src/org/openstreetmap/josm/io/GpxReader.java@ 5684

Last change on this file since 5684 was 5684, checked in by bastiK, 11 years ago

add session support for marker layers (see #4029)

The data is exported to a separate GPX file that contains one waypoint for each marker.
This is not very elegant, because most of the time, all the info is already contained in the original GPX File.
However, when dealing with audio markers, they can be synchronized, or additional markers are added
at certain playback positions. This info must be retained.
Another complication is, that two or more MarkerLayers can be merged to one.

All these problems are avoided by explicitly exporting the markers to a separate file (as done in this commit).

  • Property svn:eol-style set to native
File size: 19.6 KB
Line 
1//License: GPL. Copyright 2007 by Immanuel Scholz and others
2
3//TODO: this is far from complete, but can emulate old RawGps behaviour
4package org.openstreetmap.josm.io;
5
6import static org.openstreetmap.josm.tools.I18n.tr;
7
8import java.io.IOException;
9import java.io.InputStream;
10import java.util.ArrayList;
11import java.util.Collection;
12import java.util.HashMap;
13import java.util.LinkedList;
14import java.util.List;
15import java.util.Map;
16import java.util.Stack;
17
18import javax.xml.parsers.ParserConfigurationException;
19import javax.xml.parsers.SAXParserFactory;
20
21import org.openstreetmap.josm.data.coor.LatLon;
22import org.openstreetmap.josm.data.gpx.Extensions;
23import org.openstreetmap.josm.data.gpx.GpxConstants;
24import org.openstreetmap.josm.data.gpx.GpxData;
25import org.openstreetmap.josm.data.gpx.GpxLink;
26import org.openstreetmap.josm.data.gpx.GpxRoute;
27import org.openstreetmap.josm.data.gpx.ImmutableGpxTrack;
28import org.openstreetmap.josm.data.gpx.WayPoint;
29import org.xml.sax.Attributes;
30import org.xml.sax.InputSource;
31import org.xml.sax.SAXException;
32import org.xml.sax.helpers.DefaultHandler;
33
34/**
35 * Read a gpx file.
36 *
37 * Bounds are not read, as we caluclate them. @see GpxData.recalculateBounds()
38 * Both GPX version 1.0 and 1.1 are supported.
39 *
40 * @author imi, ramack
41 */
42public class GpxReader implements GpxConstants {
43
44 private String version;
45 /**
46 * The resulting gpx data
47 */
48 private GpxData gpxData;
49 private enum State { init, gpx, metadata, wpt, rte, trk, ext, author, link, trkseg, copyright}
50 private InputSource inputSource;
51
52 private class Parser extends DefaultHandler {
53
54 private GpxData data;
55 private Collection<Collection<WayPoint>> currentTrack;
56 private Map<String, Object> currentTrackAttr;
57 private Collection<WayPoint> currentTrackSeg;
58 private GpxRoute currentRoute;
59 private WayPoint currentWayPoint;
60
61 private State currentState = State.init;
62
63 private GpxLink currentLink;
64 private Extensions currentExtensions;
65 private Stack<State> states;
66 private final Stack<String> elements = new Stack<String>();
67
68 private StringBuffer accumulator = new StringBuffer();
69
70 private boolean nokiaSportsTrackerBug = false;
71
72 @Override public void startDocument() {
73 accumulator = new StringBuffer();
74 states = new Stack<State>();
75 data = new GpxData();
76 }
77
78 private double parseCoord(String s) {
79 try {
80 return Double.parseDouble(s);
81 } catch (NumberFormatException ex) {
82 return Double.NaN;
83 }
84 }
85
86 private LatLon parseLatLon(Attributes atts) {
87 return new LatLon(
88 parseCoord(atts.getValue("lat")),
89 parseCoord(atts.getValue("lon")));
90 }
91
92 @Override public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
93 elements.push(localName);
94 switch(currentState) {
95 case init:
96 states.push(currentState);
97 currentState = State.gpx;
98 data.creator = atts.getValue("creator");
99 version = atts.getValue("version");
100 if (version != null && version.startsWith("1.0")) {
101 version = "1.0";
102 } else if (!"1.1".equals(version)) {
103 // unknown version, assume 1.1
104 version = "1.1";
105 }
106 break;
107 case gpx:
108 if (localName.equals("metadata")) {
109 states.push(currentState);
110 currentState = State.metadata;
111 } else if (localName.equals("wpt")) {
112 states.push(currentState);
113 currentState = State.wpt;
114 currentWayPoint = new WayPoint(parseLatLon(atts));
115 } else if (localName.equals("rte")) {
116 states.push(currentState);
117 currentState = State.rte;
118 currentRoute = new GpxRoute();
119 } else if (localName.equals("trk")) {
120 states.push(currentState);
121 currentState = State.trk;
122 currentTrack = new ArrayList<Collection<WayPoint>>();
123 currentTrackAttr = new HashMap<String, Object>();
124 } else if (localName.equals("extensions")) {
125 states.push(currentState);
126 currentState = State.ext;
127 currentExtensions = new Extensions();
128 } else if (localName.equals("gpx") && atts.getValue("creator") != null && atts.getValue("creator").startsWith("Nokia Sports Tracker")) {
129 nokiaSportsTrackerBug = true;
130 }
131 break;
132 case metadata:
133 if (localName.equals("author")) {
134 states.push(currentState);
135 currentState = State.author;
136 } else if (localName.equals("extensions")) {
137 states.push(currentState);
138 currentState = State.ext;
139 currentExtensions = new Extensions();
140 } else if (localName.equals("copyright")) {
141 states.push(currentState);
142 currentState = State.copyright;
143 data.attr.put(META_COPYRIGHT_AUTHOR, atts.getValue("author"));
144 } else if (localName.equals("link")) {
145 states.push(currentState);
146 currentState = State.link;
147 currentLink = new GpxLink(atts.getValue("href"));
148 }
149 break;
150 case author:
151 if (localName.equals("link")) {
152 states.push(currentState);
153 currentState = State.link;
154 currentLink = new GpxLink(atts.getValue("href"));
155 } else if (localName.equals("email")) {
156 data.attr.put(META_AUTHOR_EMAIL, atts.getValue("id") + "@" + atts.getValue("domain"));
157 }
158 break;
159 case trk:
160 if (localName.equals("trkseg")) {
161 states.push(currentState);
162 currentState = State.trkseg;
163 currentTrackSeg = new ArrayList<WayPoint>();
164 } else if (localName.equals("link")) {
165 states.push(currentState);
166 currentState = State.link;
167 currentLink = new GpxLink(atts.getValue("href"));
168 } else if (localName.equals("extensions")) {
169 states.push(currentState);
170 currentState = State.ext;
171 currentExtensions = new Extensions();
172 }
173 break;
174 case trkseg:
175 if (localName.equals("trkpt")) {
176 states.push(currentState);
177 currentState = State.wpt;
178 currentWayPoint = new WayPoint(parseLatLon(atts));
179 }
180 break;
181 case wpt:
182 if (localName.equals("link")) {
183 states.push(currentState);
184 currentState = State.link;
185 currentLink = new GpxLink(atts.getValue("href"));
186 } else if (localName.equals("extensions")) {
187 states.push(currentState);
188 currentState = State.ext;
189 currentExtensions = new Extensions();
190 }
191 break;
192 case rte:
193 if (localName.equals("link")) {
194 states.push(currentState);
195 currentState = State.link;
196 currentLink = new GpxLink(atts.getValue("href"));
197 } else if (localName.equals("rtept")) {
198 states.push(currentState);
199 currentState = State.wpt;
200 currentWayPoint = new WayPoint(parseLatLon(atts));
201 } else if (localName.equals("extensions")) {
202 states.push(currentState);
203 currentState = State.ext;
204 currentExtensions = new Extensions();
205 }
206 break;
207 }
208 accumulator.setLength(0);
209 }
210
211 @Override public void characters(char[] ch, int start, int length) {
212 /**
213 * Remove illegal characters generated by the Nokia Sports Tracker device.
214 * Don't do this crude substitution for all files, since it would destroy
215 * certain unicode characters.
216 */
217 if (nokiaSportsTrackerBug) {
218 for (int i=0; i<ch.length; ++i) {
219 if (ch[i] == 1) {
220 ch[i] = 32;
221 }
222 }
223 nokiaSportsTrackerBug = false;
224 }
225
226 accumulator.append(ch, start, length);
227 }
228
229 private Map<String, Object> getAttr() {
230 switch (currentState) {
231 case rte: return currentRoute.attr;
232 case metadata: return data.attr;
233 case wpt: return currentWayPoint.attr;
234 case trk: return currentTrackAttr;
235 default: return null;
236 }
237 }
238
239 @SuppressWarnings("unchecked")
240 @Override public void endElement(String namespaceURI, String localName, String qName) {
241 elements.pop();
242 switch (currentState) {
243 case gpx: // GPX 1.0
244 case metadata: // GPX 1.1
245 if (localName.equals("name")) {
246 data.attr.put(META_NAME, accumulator.toString());
247 } else if (localName.equals("desc")) {
248 data.attr.put(META_DESC, accumulator.toString());
249 } else if (localName.equals("time")) {
250 data.attr.put(META_TIME, accumulator.toString());
251 } else if (localName.equals("keywords")) {
252 data.attr.put(META_KEYWORDS, accumulator.toString());
253 } else if (version.equals("1.0") && localName.equals("author")) {
254 // author is a string in 1.0, but complex element in 1.1
255 data.attr.put(META_AUTHOR_NAME, accumulator.toString());
256 } else if (version.equals("1.0") && localName.equals("email")) {
257 data.attr.put(META_AUTHOR_EMAIL, accumulator.toString());
258 } else if (localName.equals("url") || localName.equals("urlname")) {
259 data.attr.put(localName, accumulator.toString());
260 } else if ((currentState == State.metadata && localName.equals("metadata")) ||
261 (currentState == State.gpx && localName.equals("gpx"))) {
262 convertUrlToLink(data.attr);
263 if (currentExtensions != null && !currentExtensions.isEmpty()) {
264 data.attr.put(META_EXTENSIONS, currentExtensions);
265 }
266 currentState = states.pop();
267 }
268 //TODO: parse bounds, extensions
269 break;
270 case author:
271 if (localName.equals("author")) {
272 currentState = states.pop();
273 } else if (localName.equals("name")) {
274 data.attr.put(META_AUTHOR_NAME, accumulator.toString());
275 } else if (localName.equals("email")) {
276 // do nothing, has been parsed on startElement
277 } else if (localName.equals("link")) {
278 data.attr.put(META_AUTHOR_LINK, currentLink);
279 }
280 break;
281 case copyright:
282 if (localName.equals("copyright")) {
283 currentState = states.pop();
284 } else if (localName.equals("year")) {
285 data.attr.put(META_COPYRIGHT_YEAR, accumulator.toString());
286 } else if (localName.equals("license")) {
287 data.attr.put(META_COPYRIGHT_LICENSE, accumulator.toString());
288 }
289 break;
290 case link:
291 if (localName.equals("text")) {
292 currentLink.text = accumulator.toString();
293 } else if (localName.equals("type")) {
294 currentLink.type = accumulator.toString();
295 } else if (localName.equals("link")) {
296 if (currentLink.uri == null && accumulator != null && accumulator.toString().length() != 0) {
297 currentLink = new GpxLink(accumulator.toString());
298 }
299 currentState = states.pop();
300 }
301 if (currentState == State.author) {
302 data.attr.put(META_AUTHOR_LINK, currentLink);
303 } else if (currentState != State.link) {
304 Map<String, Object> attr = getAttr();
305 if (!attr.containsKey(META_LINKS)) {
306 attr.put(META_LINKS, new LinkedList<GpxLink>());
307 }
308 ((Collection<GpxLink>) attr.get(META_LINKS)).add(currentLink);
309 }
310 break;
311 case wpt:
312 if ( localName.equals("ele") || localName.equals("magvar")
313 || localName.equals("name") || localName.equals("src")
314 || localName.equals("geoidheight") || localName.equals("type")
315 || localName.equals("sym") || localName.equals("url")
316 || localName.equals("urlname")) {
317 currentWayPoint.attr.put(localName, accumulator.toString());
318 } else if(localName.equals("hdop") || localName.equals("vdop") ||
319 localName.equals("pdop")) {
320 try {
321 currentWayPoint.attr.put(localName, Float.parseFloat(accumulator.toString()));
322 } catch(Exception e) {
323 currentWayPoint.attr.put(localName, new Float(0));
324 }
325 } else if (localName.equals("time")) {
326 currentWayPoint.attr.put(localName, accumulator.toString());
327 currentWayPoint.setTime();
328 } else if (localName.equals("cmt") || localName.equals("desc")) {
329 currentWayPoint.attr.put(localName, accumulator.toString());
330 currentWayPoint.setTime();
331 } else if (localName.equals("rtept")) {
332 currentState = states.pop();
333 convertUrlToLink(currentWayPoint.attr);
334 currentRoute.routePoints.add(currentWayPoint);
335 } else if (localName.equals("trkpt")) {
336 currentState = states.pop();
337 convertUrlToLink(currentWayPoint.attr);
338 currentTrackSeg.add(currentWayPoint);
339 } else if (localName.equals("wpt")) {
340 currentState = states.pop();
341 convertUrlToLink(currentWayPoint.attr);
342 if (currentExtensions != null && !currentExtensions.isEmpty()) {
343 currentWayPoint.attr.put(META_EXTENSIONS, currentExtensions);
344 }
345 data.waypoints.add(currentWayPoint);
346 }
347 break;
348 case trkseg:
349 if (localName.equals("trkseg")) {
350 currentState = states.pop();
351 currentTrack.add(currentTrackSeg);
352 }
353 break;
354 case trk:
355 if (localName.equals("trk")) {
356 currentState = states.pop();
357 convertUrlToLink(currentTrackAttr);
358 data.tracks.add(new ImmutableGpxTrack(currentTrack, currentTrackAttr));
359 } else if (localName.equals("name") || localName.equals("cmt")
360 || localName.equals("desc") || localName.equals("src")
361 || localName.equals("type") || localName.equals("number")
362 || localName.equals("url") || localName.equals("urlname")) {
363 currentTrackAttr.put(localName, accumulator.toString());
364 }
365 break;
366 case ext:
367 if (localName.equals("extensions")) {
368 currentState = states.pop();
369 // only interested in extensions written by JOSM
370 } else if (JOSM_EXTENSIONS_NAMESPACE_URI.equals(namespaceURI)) {
371 currentExtensions.put(localName, accumulator.toString());
372 }
373 break;
374 default:
375 if (localName.equals("wpt")) {
376 currentState = states.pop();
377 } else if (localName.equals("rte")) {
378 currentState = states.pop();
379 convertUrlToLink(currentRoute.attr);
380 data.routes.add(currentRoute);
381 }
382 }
383 }
384
385 @Override public void endDocument() throws SAXException {
386 if (!states.empty())
387 throw new SAXException(tr("Parse error: invalid document structure for GPX document."));
388 Extensions metaExt = (Extensions) data.attr.get(META_EXTENSIONS);
389 if (metaExt != null && "true".equals(metaExt.get("from-server"))) {
390 data.fromServer = true;
391 }
392 gpxData = data;
393 }
394
395 /**
396 * convert url/urlname to link element (GPX 1.0 -> GPX 1.1).
397 */
398 private void convertUrlToLink(Map<String, Object> attr) {
399 String url = (String) attr.get("url");
400 String urlname = (String) attr.get("urlname");
401 if (url != null) {
402 if (!attr.containsKey(META_LINKS)) {
403 attr.put(META_LINKS, new LinkedList<GpxLink>());
404 }
405 GpxLink link = new GpxLink(url);
406 link.text = urlname;
407 @SuppressWarnings("unchecked")
408 Collection<GpxLink> links = (Collection) attr.get(META_LINKS);
409 links.add(link);
410 }
411 }
412
413 public void tryToFinish() throws SAXException {
414 List<String> remainingElements = new ArrayList<String>(elements);
415 for (int i=remainingElements.size() - 1; i >= 0; i--) {
416 endElement(null, remainingElements.get(i), remainingElements.get(i));
417 }
418 endDocument();
419 }
420 }
421
422 /**
423 * Parse the input stream and store the result in trackData and markerData
424 *
425 */
426 public GpxReader(InputStream source) throws IOException {
427 this.inputSource = new InputSource(UTFInputStreamReader.create(source, "UTF-8"));
428 }
429
430 /**
431 *
432 * @return True if file was properly parsed, false if there was error during parsing but some data were parsed anyway
433 * @throws SAXException
434 * @throws IOException
435 */
436 public boolean parse(boolean tryToFinish) throws SAXException, IOException {
437 Parser parser = new Parser();
438 try {
439 SAXParserFactory factory = SAXParserFactory.newInstance();
440 factory.setNamespaceAware(true);
441 factory.newSAXParser().parse(inputSource, parser);
442 return true;
443 } catch (SAXException e) {
444 if (tryToFinish) {
445 parser.tryToFinish();
446 if (parser.data.isEmpty())
447 throw e;
448 return false;
449 } else
450 throw e;
451 } catch (ParserConfigurationException e) {
452 e.printStackTrace(); // broken SAXException chaining
453 throw new SAXException(e);
454 }
455 }
456
457 public GpxData getGpxData() {
458 return gpxData;
459 }
460}
Note: See TracBrowser for help on using the repository browser.