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

Last change on this file since 12150 was 11675, checked in by Don-vip, 7 years ago

update to error_prone 2.0.18

  • Property svn:eol-style set to native
File size: 22.1 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 break;
154 default: // Do nothing
155 }
156 break;
157 case METADATA:
158 switch (localName) {
159 case "author":
160 states.push(currentState);
161 currentState = State.AUTHOR;
162 break;
163 case "extensions":
164 states.push(currentState);
165 currentState = State.EXT;
166 currentExtensions = new Extensions();
167 break;
168 case "copyright":
169 states.push(currentState);
170 currentState = State.COPYRIGHT;
171 data.put(META_COPYRIGHT_AUTHOR, atts.getValue("author"));
172 break;
173 case "link":
174 states.push(currentState);
175 currentState = State.LINK;
176 currentLink = new GpxLink(atts.getValue("href"));
177 break;
178 case "bounds":
179 data.put(META_BOUNDS, new Bounds(
180 parseCoord(atts.getValue("minlat")),
181 parseCoord(atts.getValue("minlon")),
182 parseCoord(atts.getValue("maxlat")),
183 parseCoord(atts.getValue("maxlon"))));
184 break;
185 default: // Do nothing
186 }
187 break;
188 case AUTHOR:
189 switch (localName) {
190 case "link":
191 states.push(currentState);
192 currentState = State.LINK;
193 currentLink = new GpxLink(atts.getValue("href"));
194 break;
195 case "email":
196 data.put(META_AUTHOR_EMAIL, atts.getValue("id") + '@' + atts.getValue("domain"));
197 break;
198 default: // Do nothing
199 }
200 break;
201 case TRK:
202 switch (localName) {
203 case "trkseg":
204 states.push(currentState);
205 currentState = State.TRKSEG;
206 currentTrackSeg = new ArrayList<>();
207 break;
208 case "link":
209 states.push(currentState);
210 currentState = State.LINK;
211 currentLink = new GpxLink(atts.getValue("href"));
212 break;
213 case "extensions":
214 states.push(currentState);
215 currentState = State.EXT;
216 currentExtensions = new Extensions();
217 break;
218 default: // Do nothing
219 }
220 break;
221 case TRKSEG:
222 if ("trkpt".equals(localName)) {
223 states.push(currentState);
224 currentState = State.WPT;
225 currentWayPoint = new WayPoint(parseLatLon(atts));
226 }
227 break;
228 case WPT:
229 switch (localName) {
230 case "link":
231 states.push(currentState);
232 currentState = State.LINK;
233 currentLink = new GpxLink(atts.getValue("href"));
234 break;
235 case "extensions":
236 states.push(currentState);
237 currentState = State.EXT;
238 currentExtensions = new Extensions();
239 break;
240 default: // Do nothing
241 }
242 break;
243 case RTE:
244 switch (localName) {
245 case "link":
246 states.push(currentState);
247 currentState = State.LINK;
248 currentLink = new GpxLink(atts.getValue("href"));
249 break;
250 case "rtept":
251 states.push(currentState);
252 currentState = State.WPT;
253 currentWayPoint = new WayPoint(parseLatLon(atts));
254 break;
255 case "extensions":
256 states.push(currentState);
257 currentState = State.EXT;
258 currentExtensions = new Extensions();
259 break;
260 default: // Do nothing
261 }
262 break;
263 default: // Do nothing
264 }
265 accumulator.setLength(0);
266 }
267
268 @Override
269 public void characters(char[] ch, int start, int length) {
270 /**
271 * Remove illegal characters generated by the Nokia Sports Tracker device.
272 * Don't do this crude substitution for all files, since it would destroy
273 * certain unicode characters.
274 */
275 if (nokiaSportsTrackerBug) {
276 for (int i = 0; i < ch.length; ++i) {
277 if (ch[i] == 1) {
278 ch[i] = 32;
279 }
280 }
281 nokiaSportsTrackerBug = false;
282 }
283
284 accumulator.append(ch, start, length);
285 }
286
287 private Map<String, Object> getAttr() {
288 switch (currentState) {
289 case RTE: return currentRoute.attr;
290 case METADATA: return data.attr;
291 case WPT: return currentWayPoint.attr;
292 case TRK: return currentTrackAttr;
293 default: return null;
294 }
295 }
296
297 @SuppressWarnings("unchecked")
298 @Override
299 public void endElement(String namespaceURI, String localName, String qName) {
300 elements.pop();
301 switch (currentState) {
302 case GPX: // GPX 1.0
303 case METADATA: // GPX 1.1
304 switch (localName) {
305 case "name":
306 data.put(META_NAME, accumulator.toString());
307 break;
308 case "desc":
309 data.put(META_DESC, accumulator.toString());
310 break;
311 case "time":
312 data.put(META_TIME, accumulator.toString());
313 break;
314 case "keywords":
315 data.put(META_KEYWORDS, accumulator.toString());
316 break;
317 case "author":
318 if ("1.0".equals(version)) {
319 // author is a string in 1.0, but complex element in 1.1
320 data.put(META_AUTHOR_NAME, accumulator.toString());
321 }
322 break;
323 case "email":
324 if ("1.0".equals(version)) {
325 data.put(META_AUTHOR_EMAIL, accumulator.toString());
326 }
327 break;
328 case "url":
329 case "urlname":
330 data.put(localName, accumulator.toString());
331 break;
332 case "metadata":
333 case "gpx":
334 if ((currentState == State.METADATA && "metadata".equals(localName)) ||
335 (currentState == State.GPX && "gpx".equals(localName))) {
336 convertUrlToLink(data.attr);
337 if (currentExtensions != null && !currentExtensions.isEmpty()) {
338 data.put(META_EXTENSIONS, currentExtensions);
339 }
340 currentState = states.pop();
341 }
342 break;
343 case "bounds":
344 // do nothing, has been parsed on startElement
345 break;
346 default:
347 //TODO: parse extensions
348 }
349 break;
350 case AUTHOR:
351 switch (localName) {
352 case "author":
353 currentState = states.pop();
354 break;
355 case "name":
356 data.put(META_AUTHOR_NAME, accumulator.toString());
357 break;
358 case "email":
359 // do nothing, has been parsed on startElement
360 break;
361 case "link":
362 data.put(META_AUTHOR_LINK, currentLink);
363 break;
364 default: // Do nothing
365 }
366 break;
367 case COPYRIGHT:
368 switch (localName) {
369 case "copyright":
370 currentState = states.pop();
371 break;
372 case "year":
373 data.put(META_COPYRIGHT_YEAR, accumulator.toString());
374 break;
375 case "license":
376 data.put(META_COPYRIGHT_LICENSE, accumulator.toString());
377 break;
378 default: // Do nothing
379 }
380 break;
381 case LINK:
382 switch (localName) {
383 case "text":
384 currentLink.text = accumulator.toString();
385 break;
386 case "type":
387 currentLink.type = accumulator.toString();
388 break;
389 case "link":
390 if (currentLink.uri == null && accumulator != null && !accumulator.toString().isEmpty()) {
391 currentLink = new GpxLink(accumulator.toString());
392 }
393 currentState = states.pop();
394 break;
395 default: // Do nothing
396 }
397 if (currentState == State.AUTHOR) {
398 data.put(META_AUTHOR_LINK, currentLink);
399 } else if (currentState != State.LINK) {
400 Map<String, Object> attr = getAttr();
401 if (attr != null && !attr.containsKey(META_LINKS)) {
402 attr.put(META_LINKS, new LinkedList<GpxLink>());
403 }
404 if (attr != null)
405 ((Collection<GpxLink>) attr.get(META_LINKS)).add(currentLink);
406 }
407 break;
408 case WPT:
409 switch (localName) {
410 case "ele":
411 case "magvar":
412 case "name":
413 case "src":
414 case "geoidheight":
415 case "type":
416 case "sym":
417 case "url":
418 case "urlname":
419 currentWayPoint.put(localName, accumulator.toString());
420 break;
421 case "hdop":
422 case "vdop":
423 case "pdop":
424 try {
425 currentWayPoint.put(localName, Float.valueOf(accumulator.toString()));
426 } catch (NumberFormatException e) {
427 currentWayPoint.put(localName, 0f);
428 }
429 break;
430 case "time":
431 case "cmt":
432 case "desc":
433 currentWayPoint.put(localName, accumulator.toString());
434 currentWayPoint.setTime();
435 break;
436 case "rtept":
437 currentState = states.pop();
438 convertUrlToLink(currentWayPoint.attr);
439 currentRoute.routePoints.add(currentWayPoint);
440 break;
441 case "trkpt":
442 currentState = states.pop();
443 convertUrlToLink(currentWayPoint.attr);
444 currentTrackSeg.add(currentWayPoint);
445 break;
446 case "wpt":
447 currentState = states.pop();
448 convertUrlToLink(currentWayPoint.attr);
449 if (currentExtensions != null && !currentExtensions.isEmpty()) {
450 currentWayPoint.put(META_EXTENSIONS, currentExtensions);
451 }
452 data.waypoints.add(currentWayPoint);
453 break;
454 default: // Do nothing
455 }
456 break;
457 case TRKSEG:
458 if ("trkseg".equals(localName)) {
459 currentState = states.pop();
460 currentTrack.add(currentTrackSeg);
461 }
462 break;
463 case TRK:
464 switch (localName) {
465 case "trk":
466 currentState = states.pop();
467 convertUrlToLink(currentTrackAttr);
468 data.tracks.add(new ImmutableGpxTrack(currentTrack, currentTrackAttr));
469 break;
470 case "name":
471 case "cmt":
472 case "desc":
473 case "src":
474 case "type":
475 case "number":
476 case "url":
477 case "urlname":
478 currentTrackAttr.put(localName, accumulator.toString());
479 break;
480 default: // Do nothing
481 }
482 break;
483 case EXT:
484 if ("extensions".equals(localName)) {
485 currentState = states.pop();
486 } else if (JOSM_EXTENSIONS_NAMESPACE_URI.equals(namespaceURI)) {
487 // only interested in extensions written by JOSM
488 currentExtensions.put(localName, accumulator.toString());
489 }
490 break;
491 default:
492 switch (localName) {
493 case "wpt":
494 currentState = states.pop();
495 break;
496 case "rte":
497 currentState = states.pop();
498 convertUrlToLink(currentRoute.attr);
499 data.routes.add(currentRoute);
500 break;
501 default: // Do nothing
502 }
503 }
504 }
505
506 @Override
507 public void endDocument() throws SAXException {
508 if (!states.empty())
509 throw new SAXException(tr("Parse error: invalid document structure for GPX document."));
510 Extensions metaExt = (Extensions) data.get(META_EXTENSIONS);
511 if (metaExt != null && "true".equals(metaExt.get("from-server"))) {
512 data.fromServer = true;
513 }
514 gpxData = data;
515 }
516
517 /**
518 * convert url/urlname to link element (GPX 1.0 -&gt; GPX 1.1).
519 * @param attr attributes
520 */
521 private void convertUrlToLink(Map<String, Object> attr) {
522 String url = (String) attr.get("url");
523 String urlname = (String) attr.get("urlname");
524 if (url != null) {
525 if (!attr.containsKey(META_LINKS)) {
526 attr.put(META_LINKS, new LinkedList<GpxLink>());
527 }
528 GpxLink link = new GpxLink(url);
529 link.text = urlname;
530 @SuppressWarnings("unchecked")
531 Collection<GpxLink> links = (Collection<GpxLink>) attr.get(META_LINKS);
532 links.add(link);
533 }
534 }
535
536 void tryToFinish() throws SAXException {
537 List<String> remainingElements = new ArrayList<>(elements);
538 for (int i = remainingElements.size() - 1; i >= 0; i--) {
539 endElement(null, remainingElements.get(i), remainingElements.get(i));
540 }
541 endDocument();
542 }
543 }
544
545 /**
546 * Constructs a new {@code GpxReader}, which can later parse the input stream
547 * and store the result in trackData and markerData
548 *
549 * @param source the source input stream
550 * @throws IOException if an IO error occurs, e.g. the input stream is closed.
551 */
552 public GpxReader(InputStream source) throws IOException {
553 Reader utf8stream = UTFInputStreamReader.create(source);
554 Reader filtered = new InvalidXmlCharacterFilter(utf8stream);
555 this.inputSource = new InputSource(filtered);
556 }
557
558 /**
559 * Parse the GPX data.
560 *
561 * @param tryToFinish true, if the reader should return at least part of the GPX
562 * data in case of an error.
563 * @return true if file was properly parsed, false if there was error during
564 * parsing but some data were parsed anyway
565 * @throws SAXException if any SAX parsing error occurs
566 * @throws IOException if any I/O error occurs
567 */
568 public boolean parse(boolean tryToFinish) throws SAXException, IOException {
569 Parser parser = new Parser();
570 try {
571 Utils.parseSafeSAX(inputSource, parser);
572 return true;
573 } catch (SAXException e) {
574 if (tryToFinish) {
575 parser.tryToFinish();
576 if (parser.data.isEmpty())
577 throw e;
578 String message = e.getMessage();
579 if (e instanceof SAXParseException) {
580 SAXParseException spe = (SAXParseException) e;
581 message += ' ' + tr("(at line {0}, column {1})", spe.getLineNumber(), spe.getColumnNumber());
582 }
583 Main.warn(message);
584 return false;
585 } else
586 throw e;
587 } catch (ParserConfigurationException e) {
588 Main.error(e); // broken SAXException chaining
589 throw new SAXException(e);
590 }
591 }
592
593 /**
594 * Replies the GPX data.
595 * @return The GPX data
596 */
597 public GpxData getGpxData() {
598 return gpxData;
599 }
600}
Note: See TracBrowser for help on using the repository browser.