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

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

fix #7976 - Get downloaded gpx areas, on the same model as osm data

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