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

Last change on this file since 5299 was 4819, checked in by jttt, 12 years ago

Fix reading of incomplete gpx files

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