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

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

fixed #8893 - Cannot load GPX file

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