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

Last change on this file since 4153 was 3408, checked in by jttt, 14 years ago

Show only actions that can work on all selected layers in LayerListDialog popup menu

  • 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.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 qName, String rqName, 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 qName, String rqName) {
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), null);
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 factory.setNamespaceAware(true);
370 factory.newSAXParser().parse(inputSource, parser);
371 return true;
372 } catch (SAXException e) {
373 if (tryToFinish) {
374 parser.tryToFinish();
375 if (parser.currentData.isEmpty())
376 throw e;
377 return false;
378 } else
379 throw e;
380 } catch (ParserConfigurationException e) {
381 e.printStackTrace(); // broken SAXException chaining
382 throw new SAXException(e);
383 }
384 }
385}
Note: See TracBrowser for help on using the repository browser.