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

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

sonar - squid:S2259 - Null pointers should not be dereferenced

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