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

Last change on this file since 3226 was 2907, checked in by jttt, 14 years ago

Gpx refactoring - GpxTrack and GpxTrackSegment is now interface, implementations for specific use can be provided (currently JOSM supports immutable gpx track, livegps plugin supports append only track).

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