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

Last change on this file since 8849 was 8846, checked in by Don-vip, 9 years ago

sonar - fb-contrib - minor performance improvements:

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