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

Last change on this file since 7058 was 7033, checked in by Don-vip, 10 years ago

see #8465 - global use of try-with-resources, according to

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