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

Last change on this file since 5854 was 5854, checked in by bastiK, 11 years ago

fixed #7648 - GPS-Download in JOSM in specific area not possible

  • Property svn:eol-style set to native
File size: 20.0 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.Extensions;
23import org.openstreetmap.josm.data.gpx.GpxConstants;
24import org.openstreetmap.josm.data.gpx.GpxData;
25import org.openstreetmap.josm.data.gpx.GpxLink;
26import org.openstreetmap.josm.data.gpx.GpxRoute;
27import org.openstreetmap.josm.data.gpx.ImmutableGpxTrack;
28import org.openstreetmap.josm.data.gpx.WayPoint;
29import org.xml.sax.Attributes;
30import org.xml.sax.InputSource;
31import org.xml.sax.SAXException;
32import org.xml.sax.helpers.DefaultHandler;
33
34/**
35 * Read a gpx file.
36 *
37 * Bounds are not read, as we caluclate them. @see GpxData.recalculateBounds()
38 * Both GPX version 1.0 and 1.1 are supported.
39 *
40 * @author imi, ramack
41 */
42public class GpxReader implements GpxConstants {
43
44 private String version;
45 /**
46 * The resulting gpx data
47 */
48 private GpxData gpxData;
49 private enum State { init, gpx, metadata, wpt, rte, trk, ext, author, link, trkseg, copyright}
50 private InputSource inputSource;
51
52 private class Parser extends DefaultHandler {
53
54 private GpxData data;
55 private Collection<Collection<WayPoint>> currentTrack;
56 private Map<String, Object> currentTrackAttr;
57 private Collection<WayPoint> currentTrackSeg;
58 private GpxRoute currentRoute;
59 private WayPoint currentWayPoint;
60
61 private State currentState = State.init;
62
63 private GpxLink currentLink;
64 private Extensions currentExtensions;
65 private Stack<State> states;
66 private final Stack<String> elements = new Stack<String>();
67
68 private StringBuffer accumulator = new StringBuffer();
69
70 private boolean nokiaSportsTrackerBug = false;
71
72 @Override public void startDocument() {
73 accumulator = new StringBuffer();
74 states = new Stack<State>();
75 data = new GpxData();
76 }
77
78 private double parseCoord(String s) {
79 try {
80 return Double.parseDouble(s);
81 } catch (NumberFormatException ex) {
82 return Double.NaN;
83 }
84 }
85
86 private LatLon parseLatLon(Attributes atts) {
87 return new LatLon(
88 parseCoord(atts.getValue("lat")),
89 parseCoord(atts.getValue("lon")));
90 }
91
92 @Override public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
93 elements.push(localName);
94 switch(currentState) {
95 case init:
96 states.push(currentState);
97 currentState = State.gpx;
98 data.creator = atts.getValue("creator");
99 version = atts.getValue("version");
100 if (version != null && version.startsWith("1.0")) {
101 version = "1.0";
102 } else if (!"1.1".equals(version)) {
103 // unknown version, assume 1.1
104 version = "1.1";
105 }
106 break;
107 case gpx:
108 if (localName.equals("metadata")) {
109 states.push(currentState);
110 currentState = State.metadata;
111 } else if (localName.equals("wpt")) {
112 states.push(currentState);
113 currentState = State.wpt;
114 currentWayPoint = new WayPoint(parseLatLon(atts));
115 } else if (localName.equals("rte")) {
116 states.push(currentState);
117 currentState = State.rte;
118 currentRoute = new GpxRoute();
119 } else if (localName.equals("trk")) {
120 states.push(currentState);
121 currentState = State.trk;
122 currentTrack = new ArrayList<Collection<WayPoint>>();
123 currentTrackAttr = new HashMap<String, Object>();
124 } else if (localName.equals("extensions")) {
125 states.push(currentState);
126 currentState = State.ext;
127 currentExtensions = new Extensions();
128 } else if (localName.equals("gpx") && atts.getValue("creator") != null && atts.getValue("creator").startsWith("Nokia Sports Tracker")) {
129 nokiaSportsTrackerBug = true;
130 }
131 break;
132 case metadata:
133 if (localName.equals("author")) {
134 states.push(currentState);
135 currentState = State.author;
136 } else if (localName.equals("extensions")) {
137 states.push(currentState);
138 currentState = State.ext;
139 currentExtensions = new Extensions();
140 } else if (localName.equals("copyright")) {
141 states.push(currentState);
142 currentState = State.copyright;
143 data.attr.put(META_COPYRIGHT_AUTHOR, atts.getValue("author"));
144 } else if (localName.equals("link")) {
145 states.push(currentState);
146 currentState = State.link;
147 currentLink = new GpxLink(atts.getValue("href"));
148 }
149 break;
150 case author:
151 if (localName.equals("link")) {
152 states.push(currentState);
153 currentState = State.link;
154 currentLink = new GpxLink(atts.getValue("href"));
155 } else if (localName.equals("email")) {
156 data.attr.put(META_AUTHOR_EMAIL, atts.getValue("id") + "@" + atts.getValue("domain"));
157 }
158 break;
159 case trk:
160 if (localName.equals("trkseg")) {
161 states.push(currentState);
162 currentState = State.trkseg;
163 currentTrackSeg = new ArrayList<WayPoint>();
164 } else if (localName.equals("link")) {
165 states.push(currentState);
166 currentState = State.link;
167 currentLink = new GpxLink(atts.getValue("href"));
168 } else if (localName.equals("extensions")) {
169 states.push(currentState);
170 currentState = State.ext;
171 currentExtensions = new Extensions();
172 }
173 break;
174 case trkseg:
175 if (localName.equals("trkpt")) {
176 states.push(currentState);
177 currentState = State.wpt;
178 currentWayPoint = new WayPoint(parseLatLon(atts));
179 }
180 break;
181 case wpt:
182 if (localName.equals("link")) {
183 states.push(currentState);
184 currentState = State.link;
185 currentLink = new GpxLink(atts.getValue("href"));
186 } else if (localName.equals("extensions")) {
187 states.push(currentState);
188 currentState = State.ext;
189 currentExtensions = new Extensions();
190 }
191 break;
192 case rte:
193 if (localName.equals("link")) {
194 states.push(currentState);
195 currentState = State.link;
196 currentLink = new GpxLink(atts.getValue("href"));
197 } else if (localName.equals("rtept")) {
198 states.push(currentState);
199 currentState = State.wpt;
200 currentWayPoint = new WayPoint(parseLatLon(atts));
201 } else if (localName.equals("extensions")) {
202 states.push(currentState);
203 currentState = State.ext;
204 currentExtensions = new Extensions();
205 }
206 break;
207 }
208 accumulator.setLength(0);
209 }
210
211 @Override public void characters(char[] ch, int start, int length) {
212 /**
213 * Remove illegal characters generated by the Nokia Sports Tracker device.
214 * Don't do this crude substitution for all files, since it would destroy
215 * certain unicode characters.
216 */
217 if (nokiaSportsTrackerBug) {
218 for (int i=0; i<ch.length; ++i) {
219 if (ch[i] == 1) {
220 ch[i] = 32;
221 }
222 }
223 nokiaSportsTrackerBug = false;
224 }
225
226 accumulator.append(ch, start, length);
227 }
228
229 private Map<String, Object> getAttr() {
230 switch (currentState) {
231 case rte: return currentRoute.attr;
232 case metadata: return data.attr;
233 case wpt: return currentWayPoint.attr;
234 case trk: return currentTrackAttr;
235 default: return null;
236 }
237 }
238
239 @SuppressWarnings("unchecked")
240 @Override public void endElement(String namespaceURI, String localName, String qName) {
241 elements.pop();
242 switch (currentState) {
243 case gpx: // GPX 1.0
244 case metadata: // GPX 1.1
245 if (localName.equals("name")) {
246 data.attr.put(META_NAME, accumulator.toString());
247 } else if (localName.equals("desc")) {
248 data.attr.put(META_DESC, accumulator.toString());
249 } else if (localName.equals("time")) {
250 data.attr.put(META_TIME, accumulator.toString());
251 } else if (localName.equals("keywords")) {
252 data.attr.put(META_KEYWORDS, accumulator.toString());
253 } else if (version.equals("1.0") && localName.equals("author")) {
254 // author is a string in 1.0, but complex element in 1.1
255 data.attr.put(META_AUTHOR_NAME, accumulator.toString());
256 } else if (version.equals("1.0") && localName.equals("email")) {
257 data.attr.put(META_AUTHOR_EMAIL, accumulator.toString());
258 } else if (localName.equals("url") || localName.equals("urlname")) {
259 data.attr.put(localName, accumulator.toString());
260 } else if ((currentState == State.metadata && localName.equals("metadata")) ||
261 (currentState == State.gpx && localName.equals("gpx"))) {
262 convertUrlToLink(data.attr);
263 if (currentExtensions != null && !currentExtensions.isEmpty()) {
264 data.attr.put(META_EXTENSIONS, currentExtensions);
265 }
266 currentState = states.pop();
267 }
268 //TODO: parse bounds, extensions
269 break;
270 case author:
271 if (localName.equals("author")) {
272 currentState = states.pop();
273 } else if (localName.equals("name")) {
274 data.attr.put(META_AUTHOR_NAME, accumulator.toString());
275 } else if (localName.equals("email")) {
276 // do nothing, has been parsed on startElement
277 } else if (localName.equals("link")) {
278 data.attr.put(META_AUTHOR_LINK, currentLink);
279 }
280 break;
281 case copyright:
282 if (localName.equals("copyright")) {
283 currentState = states.pop();
284 } else if (localName.equals("year")) {
285 data.attr.put(META_COPYRIGHT_YEAR, accumulator.toString());
286 } else if (localName.equals("license")) {
287 data.attr.put(META_COPYRIGHT_LICENSE, accumulator.toString());
288 }
289 break;
290 case link:
291 if (localName.equals("text")) {
292 currentLink.text = accumulator.toString();
293 } else if (localName.equals("type")) {
294 currentLink.type = accumulator.toString();
295 } else if (localName.equals("link")) {
296 if (currentLink.uri == null && accumulator != null && accumulator.toString().length() != 0) {
297 currentLink = new GpxLink(accumulator.toString());
298 }
299 currentState = states.pop();
300 }
301 if (currentState == State.author) {
302 data.attr.put(META_AUTHOR_LINK, currentLink);
303 } else if (currentState != State.link) {
304 Map<String, Object> attr = getAttr();
305 if (!attr.containsKey(META_LINKS)) {
306 attr.put(META_LINKS, new LinkedList<GpxLink>());
307 }
308 ((Collection<GpxLink>) attr.get(META_LINKS)).add(currentLink);
309 }
310 break;
311 case wpt:
312 if ( localName.equals("ele") || localName.equals("magvar")
313 || localName.equals("name") || localName.equals("src")
314 || localName.equals("geoidheight") || localName.equals("type")
315 || localName.equals("sym") || localName.equals("url")
316 || localName.equals("urlname")) {
317 currentWayPoint.attr.put(localName, accumulator.toString());
318 } else if(localName.equals("hdop") || localName.equals("vdop") ||
319 localName.equals("pdop")) {
320 try {
321 currentWayPoint.attr.put(localName, Float.parseFloat(accumulator.toString()));
322 } catch(Exception e) {
323 currentWayPoint.attr.put(localName, new Float(0));
324 }
325 } else if (localName.equals("time")) {
326 currentWayPoint.attr.put(localName, accumulator.toString());
327 currentWayPoint.setTime();
328 } else if (localName.equals("cmt") || localName.equals("desc")) {
329 currentWayPoint.attr.put(localName, accumulator.toString());
330 currentWayPoint.setTime();
331 } else if (localName.equals("rtept")) {
332 currentState = states.pop();
333 convertUrlToLink(currentWayPoint.attr);
334 currentRoute.routePoints.add(currentWayPoint);
335 } else if (localName.equals("trkpt")) {
336 currentState = states.pop();
337 convertUrlToLink(currentWayPoint.attr);
338 currentTrackSeg.add(currentWayPoint);
339 } else if (localName.equals("wpt")) {
340 currentState = states.pop();
341 convertUrlToLink(currentWayPoint.attr);
342 if (currentExtensions != null && !currentExtensions.isEmpty()) {
343 currentWayPoint.attr.put(META_EXTENSIONS, currentExtensions);
344 }
345 data.waypoints.add(currentWayPoint);
346 }
347 break;
348 case trkseg:
349 if (localName.equals("trkseg")) {
350 currentState = states.pop();
351 currentTrack.add(currentTrackSeg);
352 }
353 break;
354 case trk:
355 if (localName.equals("trk")) {
356 currentState = states.pop();
357 convertUrlToLink(currentTrackAttr);
358 data.tracks.add(new ImmutableGpxTrack(currentTrack, currentTrackAttr));
359 } else if (localName.equals("name") || localName.equals("cmt")
360 || localName.equals("desc") || localName.equals("src")
361 || localName.equals("type") || localName.equals("number")
362 || localName.equals("url") || localName.equals("urlname")) {
363 currentTrackAttr.put(localName, accumulator.toString());
364 }
365 break;
366 case ext:
367 if (localName.equals("extensions")) {
368 currentState = states.pop();
369 // only interested in extensions written by JOSM
370 } else if (JOSM_EXTENSIONS_NAMESPACE_URI.equals(namespaceURI)) {
371 currentExtensions.put(localName, accumulator.toString());
372 }
373 break;
374 default:
375 if (localName.equals("wpt")) {
376 currentState = states.pop();
377 } else if (localName.equals("rte")) {
378 currentState = states.pop();
379 convertUrlToLink(currentRoute.attr);
380 data.routes.add(currentRoute);
381 }
382 }
383 }
384
385 @Override public void endDocument() throws SAXException {
386 if (!states.empty())
387 throw new SAXException(tr("Parse error: invalid document structure for GPX document."));
388 Extensions metaExt = (Extensions) data.attr.get(META_EXTENSIONS);
389 if (metaExt != null && "true".equals(metaExt.get("from-server"))) {
390 data.fromServer = true;
391 }
392 gpxData = data;
393 }
394
395 /**
396 * convert url/urlname to link element (GPX 1.0 -> GPX 1.1).
397 */
398 private void convertUrlToLink(Map<String, Object> attr) {
399 String url = (String) attr.get("url");
400 String urlname = (String) attr.get("urlname");
401 if (url != null) {
402 if (!attr.containsKey(META_LINKS)) {
403 attr.put(META_LINKS, new LinkedList<GpxLink>());
404 }
405 GpxLink link = new GpxLink(url);
406 link.text = urlname;
407 @SuppressWarnings("unchecked")
408 Collection<GpxLink> links = (Collection) attr.get(META_LINKS);
409 links.add(link);
410 }
411 }
412
413 public void tryToFinish() throws SAXException {
414 List<String> remainingElements = new ArrayList<String>(elements);
415 for (int i=remainingElements.size() - 1; i >= 0; i--) {
416 endElement(null, remainingElements.get(i), remainingElements.get(i));
417 }
418 endDocument();
419 }
420 }
421
422 /**
423 * Parse the input stream and store the result in trackData and markerData
424 *
425 * @param source the source input stream
426 * @throws IOException if an IO error occurs, e.g. the input stream is closed.
427 */
428 public GpxReader(InputStream source) throws IOException {
429 InputStream filtered = new InvalidXmlCharacterFilter(source);
430 this.inputSource = new InputSource(UTFInputStreamReader.create(filtered, "UTF-8"));
431 }
432
433 /**
434 * Parse the GPX data.
435 *
436 * @param tryToFinish true, if the reader should return at least part of the GPX
437 * data in case of an error.
438 * @return true if file was properly parsed, false if there was error during
439 * parsing but some data were parsed anyway
440 * @throws SAXException
441 * @throws IOException
442 */
443 public boolean parse(boolean tryToFinish) throws SAXException, IOException {
444 Parser parser = new Parser();
445 try {
446 SAXParserFactory factory = SAXParserFactory.newInstance();
447 factory.setNamespaceAware(true);
448 factory.newSAXParser().parse(inputSource, parser);
449 return true;
450 } catch (SAXException e) {
451 if (tryToFinish) {
452 parser.tryToFinish();
453 if (parser.data.isEmpty())
454 throw e;
455 return false;
456 } else
457 throw e;
458 } catch (ParserConfigurationException e) {
459 e.printStackTrace(); // broken SAXException chaining
460 throw new SAXException(e);
461 }
462 }
463
464 public GpxData getGpxData() {
465 return gpxData;
466 }
467}
Note: See TracBrowser for help on using the repository browser.