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

Last change on this file since 2828 was 2801, checked in by stoecker, 14 years ago

fixed line endings of recent checkins

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