[8378] | 1 | // License: GPL. For details, see LICENSE file.
|
---|
[444] | 2 | package org.openstreetmap.josm.io;
|
---|
| 3 |
|
---|
| 4 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
| 5 |
|
---|
| 6 | import java.io.IOException;
|
---|
| 7 | import java.io.InputStream;
|
---|
[6080] | 8 | import java.io.Reader;
|
---|
[582] | 9 | import java.util.ArrayList;
|
---|
[444] | 10 | import java.util.Collection;
|
---|
[2907] | 11 | import java.util.HashMap;
|
---|
[444] | 12 | import java.util.LinkedList;
|
---|
[2795] | 13 | import java.util.List;
|
---|
[582] | 14 | import java.util.Map;
|
---|
[8856] | 15 | import java.util.Stack;
|
---|
[444] | 16 |
|
---|
| 17 | import javax.xml.parsers.ParserConfigurationException;
|
---|
| 18 |
|
---|
[7575] | 19 | import org.openstreetmap.josm.data.Bounds;
|
---|
[444] | 20 | import org.openstreetmap.josm.data.coor.LatLon;
|
---|
[5679] | 21 | import org.openstreetmap.josm.data.gpx.Extensions;
|
---|
[5681] | 22 | import org.openstreetmap.josm.data.gpx.GpxConstants;
|
---|
[444] | 23 | import org.openstreetmap.josm.data.gpx.GpxData;
|
---|
| 24 | import org.openstreetmap.josm.data.gpx.GpxLink;
|
---|
[582] | 25 | import org.openstreetmap.josm.data.gpx.GpxRoute;
|
---|
[2907] | 26 | import org.openstreetmap.josm.data.gpx.ImmutableGpxTrack;
|
---|
[444] | 27 | import org.openstreetmap.josm.data.gpx.WayPoint;
|
---|
[12620] | 28 | import org.openstreetmap.josm.tools.Logging;
|
---|
[13901] | 29 | import org.openstreetmap.josm.tools.XmlUtils;
|
---|
[444] | 30 | import org.xml.sax.Attributes;
|
---|
| 31 | import org.xml.sax.InputSource;
|
---|
| 32 | import org.xml.sax.SAXException;
|
---|
[6117] | 33 | import org.xml.sax.SAXParseException;
|
---|
[444] | 34 | import 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] | 44 | public 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 |
|
---|
[13194] | 92 | private double parseCoord(Attributes atts, String key) {
|
---|
| 93 | String val = atts.getValue(key);
|
---|
| 94 | if (val != null) {
|
---|
| 95 | return parseCoord(val);
|
---|
| 96 | } else {
|
---|
| 97 | // Some software do not respect GPX schema and use "minLat" / "minLon" instead of "minlat" / "minlon"
|
---|
| 98 | return parseCoord(atts.getValue(key.replaceFirst("l", "L")));
|
---|
| 99 | }
|
---|
| 100 | }
|
---|
| 101 |
|
---|
[1169] | 102 | private double parseCoord(String s) {
|
---|
[13194] | 103 | if (s != null) {
|
---|
| 104 | try {
|
---|
| 105 | return Double.parseDouble(s);
|
---|
| 106 | } catch (NumberFormatException ex) {
|
---|
| 107 | Logging.trace(ex);
|
---|
| 108 | }
|
---|
[1169] | 109 | }
|
---|
[13194] | 110 | return Double.NaN;
|
---|
[1169] | 111 | }
|
---|
[512] | 112 |
|
---|
[1169] | 113 | private LatLon parseLatLon(Attributes atts) {
|
---|
| 114 | return new LatLon(
|
---|
[13194] | 115 | parseCoord(atts, "lat"),
|
---|
| 116 | parseCoord(atts, "lon"));
|
---|
[1169] | 117 | }
|
---|
[444] | 118 |
|
---|
[7012] | 119 | @Override
|
---|
| 120 | public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
|
---|
[5679] | 121 | elements.push(localName);
|
---|
[1169] | 122 | switch(currentState) {
|
---|
[10216] | 123 | case INIT:
|
---|
[5395] | 124 | states.push(currentState);
|
---|
[10216] | 125 | currentState = State.GPX;
|
---|
[5679] | 126 | data.creator = atts.getValue("creator");
|
---|
[5395] | 127 | version = atts.getValue("version");
|
---|
[5397] | 128 | if (version != null && version.startsWith("1.0")) {
|
---|
| 129 | version = "1.0";
|
---|
| 130 | } else if (!"1.1".equals(version)) {
|
---|
| 131 | // unknown version, assume 1.1
|
---|
[5395] | 132 | version = "1.1";
|
---|
| 133 | }
|
---|
[5397] | 134 | break;
|
---|
[10216] | 135 | case GPX:
|
---|
[7012] | 136 | switch (localName) {
|
---|
| 137 | case "metadata":
|
---|
[2789] | 138 | states.push(currentState);
|
---|
[10216] | 139 | currentState = State.METADATA;
|
---|
[7012] | 140 | break;
|
---|
| 141 | case "wpt":
|
---|
[2789] | 142 | states.push(currentState);
|
---|
[10216] | 143 | currentState = State.WPT;
|
---|
[2789] | 144 | currentWayPoint = new WayPoint(parseLatLon(atts));
|
---|
[7012] | 145 | break;
|
---|
| 146 | case "rte":
|
---|
[2789] | 147 | states.push(currentState);
|
---|
[10216] | 148 | currentState = State.RTE;
|
---|
[2789] | 149 | currentRoute = new GpxRoute();
|
---|
[7012] | 150 | break;
|
---|
| 151 | case "trk":
|
---|
[2789] | 152 | states.push(currentState);
|
---|
[10216] | 153 | currentState = State.TRK;
|
---|
[7005] | 154 | currentTrack = new ArrayList<>();
|
---|
| 155 | currentTrackAttr = new HashMap<>();
|
---|
[7012] | 156 | break;
|
---|
| 157 | case "extensions":
|
---|
[2789] | 158 | states.push(currentState);
|
---|
[10216] | 159 | currentState = State.EXT;
|
---|
[5679] | 160 | currentExtensions = new Extensions();
|
---|
[7012] | 161 | break;
|
---|
| 162 | case "gpx":
|
---|
| 163 | if (atts.getValue("creator") != null && atts.getValue("creator").startsWith("Nokia Sports Tracker")) {
|
---|
| 164 | nokiaSportsTrackerBug = true;
|
---|
| 165 | }
|
---|
[10216] | 166 | break;
|
---|
| 167 | default: // Do nothing
|
---|
[2789] | 168 | }
|
---|
| 169 | break;
|
---|
[10216] | 170 | case METADATA:
|
---|
[7012] | 171 | switch (localName) {
|
---|
| 172 | case "author":
|
---|
[5395] | 173 | states.push(currentState);
|
---|
[10216] | 174 | currentState = State.AUTHOR;
|
---|
[7012] | 175 | break;
|
---|
| 176 | case "extensions":
|
---|
[5395] | 177 | states.push(currentState);
|
---|
[10216] | 178 | currentState = State.EXT;
|
---|
[5679] | 179 | currentExtensions = new Extensions();
|
---|
[7012] | 180 | break;
|
---|
| 181 | case "copyright":
|
---|
[5395] | 182 | states.push(currentState);
|
---|
[10216] | 183 | currentState = State.COPYRIGHT;
|
---|
[7518] | 184 | data.put(META_COPYRIGHT_AUTHOR, atts.getValue("author"));
|
---|
[7012] | 185 | break;
|
---|
| 186 | case "link":
|
---|
[5395] | 187 | states.push(currentState);
|
---|
[10216] | 188 | currentState = State.LINK;
|
---|
[5395] | 189 | currentLink = new GpxLink(atts.getValue("href"));
|
---|
[7575] | 190 | break;
|
---|
| 191 | case "bounds":
|
---|
| 192 | data.put(META_BOUNDS, new Bounds(
|
---|
[13194] | 193 | parseCoord(atts, "minlat"),
|
---|
| 194 | parseCoord(atts, "minlon"),
|
---|
| 195 | parseCoord(atts, "maxlat"),
|
---|
| 196 | parseCoord(atts, "maxlon")));
|
---|
[10216] | 197 | break;
|
---|
| 198 | default: // Do nothing
|
---|
[5395] | 199 | }
|
---|
| 200 | break;
|
---|
[10216] | 201 | case AUTHOR:
|
---|
[7012] | 202 | switch (localName) {
|
---|
| 203 | case "link":
|
---|
[2789] | 204 | states.push(currentState);
|
---|
[10216] | 205 | currentState = State.LINK;
|
---|
[2789] | 206 | currentLink = new GpxLink(atts.getValue("href"));
|
---|
[7012] | 207 | break;
|
---|
| 208 | case "email":
|
---|
[8846] | 209 | data.put(META_AUTHOR_EMAIL, atts.getValue("id") + '@' + atts.getValue("domain"));
|
---|
[10216] | 210 | break;
|
---|
| 211 | default: // Do nothing
|
---|
[2789] | 212 | }
|
---|
| 213 | break;
|
---|
[10216] | 214 | case TRK:
|
---|
[7012] | 215 | switch (localName) {
|
---|
| 216 | case "trkseg":
|
---|
[2789] | 217 | states.push(currentState);
|
---|
[10216] | 218 | currentState = State.TRKSEG;
|
---|
[7005] | 219 | currentTrackSeg = new ArrayList<>();
|
---|
[7012] | 220 | break;
|
---|
| 221 | case "link":
|
---|
[2789] | 222 | states.push(currentState);
|
---|
[10216] | 223 | currentState = State.LINK;
|
---|
[2789] | 224 | currentLink = new GpxLink(atts.getValue("href"));
|
---|
[7012] | 225 | break;
|
---|
| 226 | case "extensions":
|
---|
[2789] | 227 | states.push(currentState);
|
---|
[10216] | 228 | currentState = State.EXT;
|
---|
[5679] | 229 | currentExtensions = new Extensions();
|
---|
[10216] | 230 | break;
|
---|
| 231 | default: // Do nothing
|
---|
[2789] | 232 | }
|
---|
| 233 | break;
|
---|
[10216] | 234 | case TRKSEG:
|
---|
[7012] | 235 | if ("trkpt".equals(localName)) {
|
---|
[2789] | 236 | states.push(currentState);
|
---|
[10216] | 237 | currentState = State.WPT;
|
---|
[2789] | 238 | currentWayPoint = new WayPoint(parseLatLon(atts));
|
---|
| 239 | }
|
---|
| 240 | break;
|
---|
[10216] | 241 | case WPT:
|
---|
[7012] | 242 | switch (localName) {
|
---|
| 243 | case "link":
|
---|
[2789] | 244 | states.push(currentState);
|
---|
[10216] | 245 | currentState = State.LINK;
|
---|
[2789] | 246 | currentLink = new GpxLink(atts.getValue("href"));
|
---|
[7012] | 247 | break;
|
---|
| 248 | case "extensions":
|
---|
[2789] | 249 | states.push(currentState);
|
---|
[10216] | 250 | currentState = State.EXT;
|
---|
[5679] | 251 | currentExtensions = new Extensions();
|
---|
[7012] | 252 | break;
|
---|
[10216] | 253 | default: // Do nothing
|
---|
[2789] | 254 | }
|
---|
| 255 | break;
|
---|
[10216] | 256 | case RTE:
|
---|
[7012] | 257 | switch (localName) {
|
---|
| 258 | case "link":
|
---|
[2789] | 259 | states.push(currentState);
|
---|
[10216] | 260 | currentState = State.LINK;
|
---|
[2789] | 261 | currentLink = new GpxLink(atts.getValue("href"));
|
---|
[7012] | 262 | break;
|
---|
| 263 | case "rtept":
|
---|
[2789] | 264 | states.push(currentState);
|
---|
[10216] | 265 | currentState = State.WPT;
|
---|
[2789] | 266 | currentWayPoint = new WayPoint(parseLatLon(atts));
|
---|
[7012] | 267 | break;
|
---|
| 268 | case "extensions":
|
---|
[2789] | 269 | states.push(currentState);
|
---|
[10216] | 270 | currentState = State.EXT;
|
---|
[5679] | 271 | currentExtensions = new Extensions();
|
---|
[7012] | 272 | break;
|
---|
[10216] | 273 | default: // Do nothing
|
---|
[2789] | 274 | }
|
---|
| 275 | break;
|
---|
[10216] | 276 | default: // Do nothing
|
---|
[1169] | 277 | }
|
---|
| 278 | accumulator.setLength(0);
|
---|
| 279 | }
|
---|
[444] | 280 |
|
---|
[7012] | 281 | @Override
|
---|
| 282 | public void characters(char[] ch, int start, int length) {
|
---|
[2512] | 283 | /**
|
---|
[2342] | 284 | * Remove illegal characters generated by the Nokia Sports Tracker device.
|
---|
[2512] | 285 | * Don't do this crude substitution for all files, since it would destroy
|
---|
[2342] | 286 | * certain unicode characters.
|
---|
| 287 | */
|
---|
| 288 | if (nokiaSportsTrackerBug) {
|
---|
[8510] | 289 | for (int i = 0; i < ch.length; ++i) {
|
---|
[2342] | 290 | if (ch[i] == 1) {
|
---|
| 291 | ch[i] = 32;
|
---|
| 292 | }
|
---|
| 293 | }
|
---|
| 294 | nokiaSportsTrackerBug = false;
|
---|
| 295 | }
|
---|
| 296 |
|
---|
[1169] | 297 | accumulator.append(ch, start, length);
|
---|
| 298 | }
|
---|
[444] | 299 |
|
---|
[1169] | 300 | private Map<String, Object> getAttr() {
|
---|
| 301 | switch (currentState) {
|
---|
[10216] | 302 | case RTE: return currentRoute.attr;
|
---|
| 303 | case METADATA: return data.attr;
|
---|
| 304 | case WPT: return currentWayPoint.attr;
|
---|
| 305 | case TRK: return currentTrackAttr;
|
---|
[2789] | 306 | default: return null;
|
---|
[1169] | 307 | }
|
---|
| 308 | }
|
---|
[512] | 309 |
|
---|
[2789] | 310 | @SuppressWarnings("unchecked")
|
---|
[7012] | 311 | @Override
|
---|
| 312 | public void endElement(String namespaceURI, String localName, String qName) {
|
---|
[2795] | 313 | elements.pop();
|
---|
[1169] | 314 | switch (currentState) {
|
---|
[10216] | 315 | case GPX: // GPX 1.0
|
---|
| 316 | case METADATA: // GPX 1.1
|
---|
[7012] | 317 | switch (localName) {
|
---|
| 318 | case "name":
|
---|
[7518] | 319 | data.put(META_NAME, accumulator.toString());
|
---|
[7012] | 320 | break;
|
---|
| 321 | case "desc":
|
---|
[7518] | 322 | data.put(META_DESC, accumulator.toString());
|
---|
[7012] | 323 | break;
|
---|
| 324 | case "time":
|
---|
[7518] | 325 | data.put(META_TIME, accumulator.toString());
|
---|
[7012] | 326 | break;
|
---|
| 327 | case "keywords":
|
---|
[7518] | 328 | data.put(META_KEYWORDS, accumulator.toString());
|
---|
[7012] | 329 | break;
|
---|
| 330 | case "author":
|
---|
| 331 | if ("1.0".equals(version)) {
|
---|
| 332 | // author is a string in 1.0, but complex element in 1.1
|
---|
[7518] | 333 | data.put(META_AUTHOR_NAME, accumulator.toString());
|
---|
[7012] | 334 | }
|
---|
| 335 | break;
|
---|
| 336 | case "email":
|
---|
| 337 | if ("1.0".equals(version)) {
|
---|
[7518] | 338 | data.put(META_AUTHOR_EMAIL, accumulator.toString());
|
---|
[7012] | 339 | }
|
---|
| 340 | break;
|
---|
| 341 | case "url":
|
---|
| 342 | case "urlname":
|
---|
[7518] | 343 | data.put(localName, accumulator.toString());
|
---|
[7012] | 344 | break;
|
---|
| 345 | case "metadata":
|
---|
| 346 | case "gpx":
|
---|
[10216] | 347 | if ((currentState == State.METADATA && "metadata".equals(localName)) ||
|
---|
| 348 | (currentState == State.GPX && "gpx".equals(localName))) {
|
---|
[7012] | 349 | convertUrlToLink(data.attr);
|
---|
| 350 | if (currentExtensions != null && !currentExtensions.isEmpty()) {
|
---|
[7518] | 351 | data.put(META_EXTENSIONS, currentExtensions);
|
---|
[7012] | 352 | }
|
---|
| 353 | currentState = states.pop();
|
---|
[5679] | 354 | }
|
---|
[11675] | 355 | break;
|
---|
[7575] | 356 | case "bounds":
|
---|
| 357 | // do nothing, has been parsed on startElement
|
---|
| 358 | break;
|
---|
[7012] | 359 | default:
|
---|
[7575] | 360 | //TODO: parse extensions
|
---|
[2789] | 361 | }
|
---|
| 362 | break;
|
---|
[10216] | 363 | case AUTHOR:
|
---|
[7012] | 364 | switch (localName) {
|
---|
| 365 | case "author":
|
---|
[2789] | 366 | currentState = states.pop();
|
---|
[7012] | 367 | break;
|
---|
| 368 | case "name":
|
---|
[7518] | 369 | data.put(META_AUTHOR_NAME, accumulator.toString());
|
---|
[7012] | 370 | break;
|
---|
| 371 | case "email":
|
---|
[2789] | 372 | // do nothing, has been parsed on startElement
|
---|
[7012] | 373 | break;
|
---|
| 374 | case "link":
|
---|
[7518] | 375 | data.put(META_AUTHOR_LINK, currentLink);
|
---|
[7012] | 376 | break;
|
---|
[10216] | 377 | default: // Do nothing
|
---|
[2789] | 378 | }
|
---|
| 379 | break;
|
---|
[10216] | 380 | case COPYRIGHT:
|
---|
[7012] | 381 | switch (localName) {
|
---|
| 382 | case "copyright":
|
---|
[2789] | 383 | currentState = states.pop();
|
---|
[7012] | 384 | break;
|
---|
| 385 | case "year":
|
---|
[7518] | 386 | data.put(META_COPYRIGHT_YEAR, accumulator.toString());
|
---|
[7012] | 387 | break;
|
---|
| 388 | case "license":
|
---|
[7518] | 389 | data.put(META_COPYRIGHT_LICENSE, accumulator.toString());
|
---|
[7012] | 390 | break;
|
---|
[10216] | 391 | default: // Do nothing
|
---|
[2789] | 392 | }
|
---|
| 393 | break;
|
---|
[10216] | 394 | case LINK:
|
---|
[7012] | 395 | switch (localName) {
|
---|
| 396 | case "text":
|
---|
[2789] | 397 | currentLink.text = accumulator.toString();
|
---|
[7012] | 398 | break;
|
---|
| 399 | case "type":
|
---|
[2789] | 400 | currentLink.type = accumulator.toString();
|
---|
[7012] | 401 | break;
|
---|
| 402 | case "link":
|
---|
[8461] | 403 | if (currentLink.uri == null && accumulator != null && !accumulator.toString().isEmpty()) {
|
---|
[2789] | 404 | currentLink = new GpxLink(accumulator.toString());
|
---|
[1169] | 405 | }
|
---|
[2789] | 406 | currentState = states.pop();
|
---|
[7012] | 407 | break;
|
---|
[10216] | 408 | default: // Do nothing
|
---|
[2789] | 409 | }
|
---|
[10216] | 410 | if (currentState == State.AUTHOR) {
|
---|
[7518] | 411 | data.put(META_AUTHOR_LINK, currentLink);
|
---|
[10216] | 412 | } else if (currentState != State.LINK) {
|
---|
[2789] | 413 | Map<String, Object> attr = getAttr();
|
---|
[11397] | 414 | if (attr != null && !attr.containsKey(META_LINKS)) {
|
---|
[5681] | 415 | attr.put(META_LINKS, new LinkedList<GpxLink>());
|
---|
[2211] | 416 | }
|
---|
[11397] | 417 | if (attr != null)
|
---|
| 418 | ((Collection<GpxLink>) attr.get(META_LINKS)).add(currentLink);
|
---|
[2789] | 419 | }
|
---|
| 420 | break;
|
---|
[10216] | 421 | case WPT:
|
---|
[7012] | 422 | switch (localName) {
|
---|
| 423 | case "ele":
|
---|
| 424 | case "magvar":
|
---|
| 425 | case "name":
|
---|
| 426 | case "src":
|
---|
| 427 | case "geoidheight":
|
---|
| 428 | case "type":
|
---|
| 429 | case "sym":
|
---|
| 430 | case "url":
|
---|
| 431 | case "urlname":
|
---|
[7518] | 432 | currentWayPoint.put(localName, accumulator.toString());
|
---|
[7012] | 433 | break;
|
---|
| 434 | case "hdop":
|
---|
| 435 | case "vdop":
|
---|
| 436 | case "pdop":
|
---|
[2789] | 437 | try {
|
---|
[8390] | 438 | currentWayPoint.put(localName, Float.valueOf(accumulator.toString()));
|
---|
[10212] | 439 | } catch (NumberFormatException e) {
|
---|
[10045] | 440 | currentWayPoint.put(localName, 0f);
|
---|
[1425] | 441 | }
|
---|
[7012] | 442 | break;
|
---|
| 443 | case "time":
|
---|
| 444 | case "cmt":
|
---|
| 445 | case "desc":
|
---|
[7518] | 446 | currentWayPoint.put(localName, accumulator.toString());
|
---|
[2789] | 447 | currentWayPoint.setTime();
|
---|
[7012] | 448 | break;
|
---|
| 449 | case "rtept":
|
---|
[2789] | 450 | currentState = states.pop();
|
---|
[5398] | 451 | convertUrlToLink(currentWayPoint.attr);
|
---|
[2789] | 452 | currentRoute.routePoints.add(currentWayPoint);
|
---|
[7012] | 453 | break;
|
---|
| 454 | case "trkpt":
|
---|
[2789] | 455 | currentState = states.pop();
|
---|
[5398] | 456 | convertUrlToLink(currentWayPoint.attr);
|
---|
[2789] | 457 | currentTrackSeg.add(currentWayPoint);
|
---|
[7012] | 458 | break;
|
---|
| 459 | case "wpt":
|
---|
[2789] | 460 | currentState = states.pop();
|
---|
[5398] | 461 | convertUrlToLink(currentWayPoint.attr);
|
---|
[5684] | 462 | if (currentExtensions != null && !currentExtensions.isEmpty()) {
|
---|
[7518] | 463 | currentWayPoint.put(META_EXTENSIONS, currentExtensions);
|
---|
[5684] | 464 | }
|
---|
[5679] | 465 | data.waypoints.add(currentWayPoint);
|
---|
[7012] | 466 | break;
|
---|
[10216] | 467 | default: // Do nothing
|
---|
[2789] | 468 | }
|
---|
| 469 | break;
|
---|
[10216] | 470 | case TRKSEG:
|
---|
[7012] | 471 | if ("trkseg".equals(localName)) {
|
---|
[2789] | 472 | currentState = states.pop();
|
---|
[2907] | 473 | currentTrack.add(currentTrackSeg);
|
---|
[2789] | 474 | }
|
---|
| 475 | break;
|
---|
[10216] | 476 | case TRK:
|
---|
[7012] | 477 | switch (localName) {
|
---|
| 478 | case "trk":
|
---|
[2789] | 479 | currentState = states.pop();
|
---|
[5398] | 480 | convertUrlToLink(currentTrackAttr);
|
---|
[12156] | 481 | data.addTrack(new ImmutableGpxTrack(currentTrack, currentTrackAttr));
|
---|
[7012] | 482 | break;
|
---|
| 483 | case "name":
|
---|
| 484 | case "cmt":
|
---|
| 485 | case "desc":
|
---|
| 486 | case "src":
|
---|
| 487 | case "type":
|
---|
| 488 | case "number":
|
---|
| 489 | case "url":
|
---|
| 490 | case "urlname":
|
---|
[5679] | 491 | currentTrackAttr.put(localName, accumulator.toString());
|
---|
[7012] | 492 | break;
|
---|
[10216] | 493 | default: // Do nothing
|
---|
[2789] | 494 | }
|
---|
| 495 | break;
|
---|
[10216] | 496 | case EXT:
|
---|
[7012] | 497 | if ("extensions".equals(localName)) {
|
---|
[2789] | 498 | currentState = states.pop();
|
---|
[5681] | 499 | } else if (JOSM_EXTENSIONS_NAMESPACE_URI.equals(namespaceURI)) {
|
---|
[7012] | 500 | // only interested in extensions written by JOSM
|
---|
[5679] | 501 | currentExtensions.put(localName, accumulator.toString());
|
---|
[2789] | 502 | }
|
---|
| 503 | break;
|
---|
| 504 | default:
|
---|
[7012] | 505 | switch (localName) {
|
---|
| 506 | case "wpt":
|
---|
[2789] | 507 | currentState = states.pop();
|
---|
[7012] | 508 | break;
|
---|
| 509 | case "rte":
|
---|
[2789] | 510 | currentState = states.pop();
|
---|
[5398] | 511 | convertUrlToLink(currentRoute.attr);
|
---|
[12156] | 512 | data.addRoute(currentRoute);
|
---|
[7012] | 513 | break;
|
---|
[10216] | 514 | default: // Do nothing
|
---|
[2789] | 515 | }
|
---|
[1169] | 516 | }
|
---|
| 517 | }
|
---|
[444] | 518 |
|
---|
[7012] | 519 | @Override
|
---|
[10378] | 520 | public void endDocument() throws SAXException {
|
---|
[8856] | 521 | if (!states.empty())
|
---|
[2181] | 522 | throw new SAXException(tr("Parse error: invalid document structure for GPX document."));
|
---|
[7518] | 523 | Extensions metaExt = (Extensions) data.get(META_EXTENSIONS);
|
---|
[5679] | 524 | if (metaExt != null && "true".equals(metaExt.get("from-server"))) {
|
---|
| 525 | data.fromServer = true;
|
---|
| 526 | }
|
---|
| 527 | gpxData = data;
|
---|
[1169] | 528 | }
|
---|
[2795] | 529 |
|
---|
[5398] | 530 | /**
|
---|
[6830] | 531 | * convert url/urlname to link element (GPX 1.0 -> GPX 1.1).
|
---|
[9231] | 532 | * @param attr attributes
|
---|
[5398] | 533 | */
|
---|
| 534 | private void convertUrlToLink(Map<String, Object> attr) {
|
---|
| 535 | String url = (String) attr.get("url");
|
---|
| 536 | String urlname = (String) attr.get("urlname");
|
---|
| 537 | if (url != null) {
|
---|
[5681] | 538 | if (!attr.containsKey(META_LINKS)) {
|
---|
| 539 | attr.put(META_LINKS, new LinkedList<GpxLink>());
|
---|
[5398] | 540 | }
|
---|
| 541 | GpxLink link = new GpxLink(url);
|
---|
| 542 | link.text = urlname;
|
---|
[9231] | 543 | @SuppressWarnings("unchecked")
|
---|
[6142] | 544 | Collection<GpxLink> links = (Collection<GpxLink>) attr.get(META_LINKS);
|
---|
[5590] | 545 | links.add(link);
|
---|
[5398] | 546 | }
|
---|
| 547 | }
|
---|
| 548 |
|
---|
[10216] | 549 | void tryToFinish() throws SAXException {
|
---|
[7005] | 550 | List<String> remainingElements = new ArrayList<>(elements);
|
---|
[8510] | 551 | for (int i = remainingElements.size() - 1; i >= 0; i--) {
|
---|
[4819] | 552 | endElement(null, remainingElements.get(i), remainingElements.get(i));
|
---|
[2795] | 553 | }
|
---|
| 554 | endDocument();
|
---|
| 555 | }
|
---|
[1169] | 556 | }
|
---|
| 557 |
|
---|
| 558 | /**
|
---|
[7509] | 559 | * Constructs a new {@code GpxReader}, which can later parse the input stream
|
---|
[7033] | 560 | * and store the result in trackData and markerData
|
---|
[1169] | 561 | *
|
---|
[5854] | 562 | * @param source the source input stream
|
---|
| 563 | * @throws IOException if an IO error occurs, e.g. the input stream is closed.
|
---|
[1169] | 564 | */
|
---|
[2795] | 565 | public GpxReader(InputStream source) throws IOException {
|
---|
[6650] | 566 | Reader utf8stream = UTFInputStreamReader.create(source);
|
---|
[6080] | 567 | Reader filtered = new InvalidXmlCharacterFilter(utf8stream);
|
---|
| 568 | this.inputSource = new InputSource(filtered);
|
---|
[2795] | 569 | }
|
---|
[1169] | 570 |
|
---|
[2795] | 571 | /**
|
---|
[5854] | 572 | * Parse the GPX data.
|
---|
[2801] | 573 | *
|
---|
[5854] | 574 | * @param tryToFinish true, if the reader should return at least part of the GPX
|
---|
| 575 | * data in case of an error.
|
---|
| 576 | * @return true if file was properly parsed, false if there was error during
|
---|
| 577 | * parsing but some data were parsed anyway
|
---|
[8470] | 578 | * @throws SAXException if any SAX parsing error occurs
|
---|
| 579 | * @throws IOException if any I/O error occurs
|
---|
[2795] | 580 | */
|
---|
| 581 | public boolean parse(boolean tryToFinish) throws SAXException, IOException {
|
---|
[1169] | 582 | Parser parser = new Parser();
|
---|
| 583 | try {
|
---|
[13901] | 584 | XmlUtils.parseSafeSAX(inputSource, parser);
|
---|
[2795] | 585 | return true;
|
---|
| 586 | } catch (SAXException e) {
|
---|
| 587 | if (tryToFinish) {
|
---|
| 588 | parser.tryToFinish();
|
---|
[5679] | 589 | if (parser.data.isEmpty())
|
---|
[2795] | 590 | throw e;
|
---|
[6117] | 591 | String message = e.getMessage();
|
---|
| 592 | if (e instanceof SAXParseException) {
|
---|
[8510] | 593 | SAXParseException spe = (SAXParseException) e;
|
---|
[8846] | 594 | message += ' ' + tr("(at line {0}, column {1})", spe.getLineNumber(), spe.getColumnNumber());
|
---|
[6117] | 595 | }
|
---|
[12620] | 596 | Logging.warn(message);
|
---|
[2795] | 597 | return false;
|
---|
| 598 | } else
|
---|
| 599 | throw e;
|
---|
[1169] | 600 | } catch (ParserConfigurationException e) {
|
---|
[12620] | 601 | Logging.error(e); // broken SAXException chaining
|
---|
[1169] | 602 | throw new SAXException(e);
|
---|
| 603 | }
|
---|
| 604 | }
|
---|
[5679] | 605 |
|
---|
[6117] | 606 | /**
|
---|
| 607 | * Replies the GPX data.
|
---|
| 608 | * @return The GPX data
|
---|
| 609 | */
|
---|
[5679] | 610 | public GpxData getGpxData() {
|
---|
| 611 | return gpxData;
|
---|
| 612 | }
|
---|
[444] | 613 | }
|
---|