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

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

Sonar - various performance improvements

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