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

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

fix element ordering when writing gpx (see #7927)

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