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

Last change on this file since 5395 was 5395, checked in by bastiK, 12 years ago

gpxreader: minor refactoring, read version & creator (see #7927)

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