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

Last change on this file since 9669 was 9231, checked in by Don-vip, 8 years ago

javadoc update

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