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

Last change on this file since 6985 was 6830, checked in by Don-vip, 10 years ago

javadoc fixes for jdk8 compatibility

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