[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 |
|
---|
[6117] | 19 | import org.openstreetmap.josm.Main;
|
---|
[7575] | 20 | import org.openstreetmap.josm.data.Bounds;
|
---|
[444] | 21 | import org.openstreetmap.josm.data.coor.LatLon;
|
---|
[5679] | 22 | import org.openstreetmap.josm.data.gpx.Extensions;
|
---|
[5681] | 23 | import org.openstreetmap.josm.data.gpx.GpxConstants;
|
---|
[444] | 24 | import org.openstreetmap.josm.data.gpx.GpxData;
|
---|
| 25 | import org.openstreetmap.josm.data.gpx.GpxLink;
|
---|
[582] | 26 | import org.openstreetmap.josm.data.gpx.GpxRoute;
|
---|
[2907] | 27 | import org.openstreetmap.josm.data.gpx.ImmutableGpxTrack;
|
---|
[444] | 28 | import org.openstreetmap.josm.data.gpx.WayPoint;
|
---|
[8287] | 29 | import org.openstreetmap.josm.tools.Utils;
|
---|
[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 |
|
---|
[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 -> 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 | }
|
---|