[444] | 1 | //License: GPL. Copyright 2007 by Immanuel Scholz and others
|
---|
| 2 |
|
---|
| 3 | //TODO: this is far from complete, but can emulate old RawGps behaviour
|
---|
| 4 | package org.openstreetmap.josm.io;
|
---|
| 5 |
|
---|
| 6 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
| 7 |
|
---|
| 8 | import java.io.File;
|
---|
| 9 | import java.io.IOException;
|
---|
| 10 | import java.io.InputStream;
|
---|
| 11 | import java.io.InputStreamReader;
|
---|
[582] | 12 | import java.util.ArrayList;
|
---|
[444] | 13 | import java.util.Collection;
|
---|
| 14 | import java.util.LinkedList;
|
---|
[582] | 15 | import java.util.Map;
|
---|
[444] | 16 | import java.util.Stack;
|
---|
| 17 |
|
---|
| 18 | import javax.xml.parsers.ParserConfigurationException;
|
---|
| 19 | import javax.xml.parsers.SAXParserFactory;
|
---|
| 20 |
|
---|
| 21 | import org.openstreetmap.josm.data.coor.LatLon;
|
---|
| 22 | import org.openstreetmap.josm.data.gpx.GpxData;
|
---|
| 23 | import org.openstreetmap.josm.data.gpx.GpxLink;
|
---|
[582] | 24 | import org.openstreetmap.josm.data.gpx.GpxRoute;
|
---|
[444] | 25 | import org.openstreetmap.josm.data.gpx.GpxTrack;
|
---|
| 26 | import org.openstreetmap.josm.data.gpx.WayPoint;
|
---|
| 27 | import org.xml.sax.Attributes;
|
---|
| 28 | import org.xml.sax.InputSource;
|
---|
| 29 | import org.xml.sax.SAXException;
|
---|
| 30 | import org.xml.sax.helpers.DefaultHandler;
|
---|
| 31 |
|
---|
| 32 | /**
|
---|
| 33 | * Read a gpx file. Bounds are not read, as we caluclate them. @see GpxData.recalculateBounds()
|
---|
| 34 | * @author imi, ramack
|
---|
| 35 | */
|
---|
| 36 | public class GpxReader {
|
---|
| 37 | // TODO: implement GPX 1.0 parsing
|
---|
| 38 |
|
---|
| 39 | /**
|
---|
| 40 | * The resulting gpx data
|
---|
| 41 | */
|
---|
| 42 | public GpxData data;
|
---|
| 43 | public enum state { init, metadata, wpt, rte, trk, ext, author, link, trkseg }
|
---|
| 44 |
|
---|
| 45 | private class Parser extends DefaultHandler {
|
---|
| 46 |
|
---|
| 47 | private GpxData currentData;
|
---|
| 48 | private GpxTrack currentTrack;
|
---|
| 49 | private Collection<WayPoint> currentTrackSeg;
|
---|
| 50 | private GpxRoute currentRoute;
|
---|
| 51 | private WayPoint currentWayPoint;
|
---|
| 52 |
|
---|
| 53 | private state currentState = state.init;
|
---|
| 54 |
|
---|
| 55 | private GpxLink currentLink;
|
---|
| 56 | private Stack<state> states;
|
---|
| 57 |
|
---|
| 58 | private StringBuffer accumulator = new StringBuffer();
|
---|
| 59 |
|
---|
| 60 | @Override public void startDocument() {
|
---|
| 61 | accumulator = new StringBuffer();
|
---|
| 62 | states = new Stack<state>();
|
---|
| 63 | currentData = new GpxData();
|
---|
| 64 | }
|
---|
| 65 |
|
---|
[512] | 66 | private double parseCoord(String s) {
|
---|
| 67 | try {
|
---|
| 68 | return Double.parseDouble(s);
|
---|
| 69 | } catch (NumberFormatException ex) {
|
---|
| 70 | return Double.NaN;
|
---|
| 71 | }
|
---|
| 72 | }
|
---|
| 73 |
|
---|
| 74 | private LatLon parseLatLon(Attributes atts) {
|
---|
| 75 | return new LatLon(
|
---|
| 76 | parseCoord(atts.getValue("lat")),
|
---|
| 77 | parseCoord(atts.getValue("lon")));
|
---|
| 78 | }
|
---|
| 79 |
|
---|
[444] | 80 | @Override public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
|
---|
| 81 | switch(currentState) {
|
---|
| 82 | case init:
|
---|
| 83 | if (qName.equals("metadata")) {
|
---|
| 84 | states.push(currentState);
|
---|
| 85 | currentState = state.metadata;
|
---|
| 86 | } else if (qName.equals("wpt")) {
|
---|
| 87 | states.push(currentState);
|
---|
| 88 | currentState = state.wpt;
|
---|
[512] | 89 | currentWayPoint = new WayPoint(parseLatLon(atts));
|
---|
[444] | 90 | } else if (qName.equals("rte")) {
|
---|
| 91 | states.push(currentState);
|
---|
| 92 | currentState = state.rte;
|
---|
| 93 | currentRoute = new GpxRoute();
|
---|
| 94 | } else if (qName.equals("trk")) {
|
---|
| 95 | states.push(currentState);
|
---|
| 96 | currentState = state.trk;
|
---|
| 97 | currentTrack = new GpxTrack();
|
---|
| 98 | } else if (qName.equals("extensions")) {
|
---|
| 99 | states.push(currentState);
|
---|
| 100 | currentState = state.ext;
|
---|
| 101 | }
|
---|
| 102 | break;
|
---|
| 103 | case author:
|
---|
| 104 | if (qName.equals("link")) {
|
---|
| 105 | states.push(currentState);
|
---|
| 106 | currentState = state.link;
|
---|
| 107 | currentLink = new GpxLink(atts.getValue("href"));
|
---|
| 108 | }
|
---|
| 109 | break;
|
---|
| 110 | case trk:
|
---|
| 111 | if (qName.equals("trkseg")) {
|
---|
| 112 | states.push(currentState);
|
---|
| 113 | currentState = state.trkseg;
|
---|
[447] | 114 | currentTrackSeg = new ArrayList<WayPoint>();
|
---|
[449] | 115 | } else if (qName.equals("link")) {
|
---|
[444] | 116 | states.push(currentState);
|
---|
| 117 | currentState = state.link;
|
---|
| 118 | currentLink = new GpxLink(atts.getValue("href"));
|
---|
[449] | 119 | } else if (qName.equals("extensions")) {
|
---|
| 120 | states.push(currentState);
|
---|
| 121 | currentState = state.ext;
|
---|
[444] | 122 | }
|
---|
| 123 | break;
|
---|
| 124 | case metadata:
|
---|
| 125 | if (qName.equals("author")) {
|
---|
| 126 | states.push(currentState);
|
---|
| 127 | currentState = state.author;
|
---|
[449] | 128 | } else if (qName.equals("extensions")) {
|
---|
| 129 | states.push(currentState);
|
---|
| 130 | currentState = state.ext;
|
---|
[444] | 131 | }
|
---|
| 132 | break;
|
---|
| 133 | case trkseg:
|
---|
| 134 | if (qName.equals("trkpt")) {
|
---|
| 135 | states.push(currentState);
|
---|
| 136 | currentState = state.wpt;
|
---|
[512] | 137 | currentWayPoint = new WayPoint(parseLatLon(atts));
|
---|
[444] | 138 | }
|
---|
| 139 | break;
|
---|
| 140 | case wpt:
|
---|
| 141 | if (qName.equals("link")) {
|
---|
| 142 | states.push(currentState);
|
---|
| 143 | currentState = state.link;
|
---|
| 144 | currentLink = new GpxLink(atts.getValue("href"));
|
---|
[449] | 145 | } else if (qName.equals("extensions")) {
|
---|
| 146 | states.push(currentState);
|
---|
| 147 | currentState = state.ext;
|
---|
[444] | 148 | }
|
---|
| 149 | break;
|
---|
| 150 | case rte:
|
---|
| 151 | if (qName.equals("link")) {
|
---|
| 152 | states.push(currentState);
|
---|
| 153 | currentState = state.link;
|
---|
| 154 | currentLink = new GpxLink(atts.getValue("href"));
|
---|
[449] | 155 | } else if (qName.equals("rtept")) {
|
---|
[444] | 156 | states.push(currentState);
|
---|
| 157 | currentState = state.wpt;
|
---|
[512] | 158 | currentWayPoint = new WayPoint(parseLatLon(atts));
|
---|
[449] | 159 | } else if (qName.equals("extensions")) {
|
---|
| 160 | states.push(currentState);
|
---|
| 161 | currentState = state.ext;
|
---|
[444] | 162 | }
|
---|
| 163 | break;
|
---|
| 164 | default:
|
---|
| 165 | }
|
---|
| 166 | accumulator.setLength(0);
|
---|
| 167 | }
|
---|
| 168 |
|
---|
| 169 | @Override public void characters(char[] ch, int start, int length) {
|
---|
| 170 | accumulator.append(ch, start, length);
|
---|
| 171 | }
|
---|
| 172 |
|
---|
| 173 | private Map<String, Object> getAttr() {
|
---|
| 174 | switch (currentState) {
|
---|
| 175 | case rte: return currentRoute.attr;
|
---|
| 176 | case metadata: return currentData.attr;
|
---|
| 177 | case wpt: return currentWayPoint.attr;
|
---|
| 178 | case trk: return currentTrack.attr;
|
---|
| 179 | default: return null;
|
---|
| 180 | }
|
---|
| 181 | }
|
---|
| 182 |
|
---|
| 183 | @Override public void endElement(String namespaceURI, String localName, String qName) {
|
---|
| 184 | switch (currentState) {
|
---|
| 185 | case metadata:
|
---|
| 186 | if (qName.equals("name") || qName.equals("desc") ||
|
---|
| 187 | qName.equals("time") || qName.equals("keywords")) {
|
---|
| 188 | currentData.attr.put(qName, accumulator.toString());
|
---|
| 189 | } else if (qName.equals("metadata")) {
|
---|
| 190 | currentState = states.pop();
|
---|
| 191 | }
|
---|
| 192 | //TODO: parse copyright, bounds, extensions
|
---|
| 193 | break;
|
---|
| 194 | case author:
|
---|
| 195 | if (qName.equals("author")) {
|
---|
| 196 | currentState = states.pop();
|
---|
| 197 | } else if (qName.equals("name") || qName.equals("email")) {
|
---|
| 198 | currentData.attr.put("author" + qName, accumulator.toString());
|
---|
| 199 | } else if (qName.equals("link")) {
|
---|
| 200 | currentData.attr.put("authorlink", currentLink);
|
---|
| 201 | }
|
---|
| 202 | break;
|
---|
| 203 | case link:
|
---|
| 204 | if (qName.equals("text")) {
|
---|
| 205 | currentLink.text = accumulator.toString();
|
---|
| 206 | } else if (qName.equals("type")) {
|
---|
| 207 | currentLink.type = accumulator.toString();
|
---|
| 208 | } else if (qName.equals("link")) {
|
---|
[512] | 209 | // <link>URL</link>
|
---|
| 210 | if (currentLink.uri == null)
|
---|
| 211 | currentLink.uri = accumulator.toString();
|
---|
| 212 |
|
---|
[444] | 213 | currentState = states.pop();
|
---|
| 214 | }
|
---|
| 215 | if (currentState == state.author) {
|
---|
| 216 | currentData.attr.put("authorlink", currentLink);
|
---|
| 217 | } else if (currentState != state.link) {
|
---|
| 218 | Map<String, Object> attr = getAttr();
|
---|
| 219 | if (!attr.containsKey("link")) {
|
---|
| 220 | attr.put("link", new LinkedList<GpxLink>());
|
---|
| 221 | }
|
---|
| 222 | ((Collection<GpxLink>) attr.get("link")).add(currentLink);
|
---|
| 223 | }
|
---|
| 224 | break;
|
---|
| 225 | case wpt:
|
---|
[554] | 226 | if (qName.equals("ele") || qName.equals("desc")
|
---|
[444] | 227 | || qName.equals("magvar") || qName.equals("geoidheight")
|
---|
[554] | 228 | || qName.equals("name") || qName.equals("sym")
|
---|
| 229 | || qName.equals("cmt") || qName.equals("type")) {
|
---|
[444] | 230 | currentWayPoint.attr.put(qName, accumulator.toString());
|
---|
[554] | 231 | } else if (qName.equals("time")) {
|
---|
| 232 | currentWayPoint.attr.put(qName, accumulator.toString());
|
---|
| 233 | currentWayPoint.setTime();
|
---|
[444] | 234 | } else if (qName.equals("rtept")) {
|
---|
| 235 | currentState = states.pop();
|
---|
| 236 | currentRoute.routePoints.add(currentWayPoint);
|
---|
| 237 | } else if (qName.equals("trkpt")) {
|
---|
| 238 | currentState = states.pop();
|
---|
| 239 | currentTrackSeg.add(currentWayPoint);
|
---|
| 240 | } else if (qName.equals("wpt")) {
|
---|
| 241 | currentState = states.pop();
|
---|
| 242 | currentData.waypoints.add(currentWayPoint);
|
---|
| 243 | }
|
---|
| 244 | break;
|
---|
| 245 | case trkseg:
|
---|
| 246 | if (qName.equals("trkseg")) {
|
---|
| 247 | currentState = states.pop();
|
---|
| 248 | currentTrack.trackSegs.add(currentTrackSeg);
|
---|
| 249 | }
|
---|
| 250 | break;
|
---|
| 251 | case trk:
|
---|
| 252 | if (qName.equals("trk")) {
|
---|
| 253 | currentState = states.pop();
|
---|
| 254 | currentData.tracks.add(currentTrack);
|
---|
| 255 | } else if (qName.equals("name") || qName.equals("cmt")
|
---|
| 256 | || qName.equals("desc") || qName.equals("src")
|
---|
| 257 | || qName.equals("type") || qName.equals("number")) {
|
---|
| 258 | currentTrack.attr.put(qName, accumulator.toString());
|
---|
| 259 | }
|
---|
[449] | 260 | break;
|
---|
| 261 | case ext:
|
---|
| 262 | if (qName.equals("extensions")) {
|
---|
| 263 | currentState = states.pop();
|
---|
| 264 | }
|
---|
| 265 | break;
|
---|
[444] | 266 | default:
|
---|
| 267 | if (qName.equals("wpt")) {
|
---|
| 268 | currentState = states.pop();
|
---|
| 269 | } else if (qName.equals("rte")) {
|
---|
| 270 | currentState = states.pop();
|
---|
| 271 | currentData.routes.add(currentRoute);
|
---|
| 272 | }
|
---|
| 273 | }
|
---|
| 274 | }
|
---|
| 275 |
|
---|
| 276 | @Override public void endDocument() throws SAXException {
|
---|
| 277 | if (!states.empty()) {
|
---|
| 278 | throw new SAXException(tr("Parse error: invalid document structure for gpx document"));
|
---|
| 279 | }
|
---|
| 280 | data = currentData;
|
---|
| 281 | }
|
---|
| 282 | }
|
---|
| 283 |
|
---|
| 284 | /**
|
---|
| 285 | * Parse the input stream and store the result in trackData and markerData
|
---|
| 286 | *
|
---|
| 287 | * @param relativeMarkerPath The directory to use as relative path for all <wpt>
|
---|
| 288 | * marker tags. Maybe <code>null</code>, in which case no relative urls are constructed for the markers.
|
---|
| 289 | */
|
---|
| 290 | public GpxReader(InputStream source, File relativeMarkerPath) throws SAXException, IOException {
|
---|
| 291 |
|
---|
| 292 | Parser parser = new Parser();
|
---|
| 293 | InputSource inputSource = new InputSource(new InputStreamReader(source, "UTF-8"));
|
---|
| 294 | try {
|
---|
| 295 | SAXParserFactory.newInstance().newSAXParser().parse(inputSource, parser);
|
---|
| 296 | data.storageFile = relativeMarkerPath;
|
---|
| 297 | } catch (ParserConfigurationException e) {
|
---|
| 298 | e.printStackTrace(); // broken SAXException chaining
|
---|
| 299 | throw new SAXException(e);
|
---|
| 300 | }
|
---|
| 301 | }
|
---|
| 302 | }
|
---|