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

Last change on this file since 2667 was 2512, checked in by stoecker, 14 years ago

i18n updated, fixed files to reduce problems when applying patches, fix #4017

  • Property svn:eol-style set to native
File size: 15.7 KB
Line 
1//License: GPL. Copyright 2007 by Immanuel Scholz and others
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.File;
9import java.io.IOException;
10import java.io.InputStream;
11import java.io.InputStreamReader;
12import java.util.ArrayList;
13import java.util.Collection;
14import java.util.LinkedList;
15import java.util.Map;
16import java.util.Stack;
17
18import javax.xml.parsers.ParserConfigurationException;
19import javax.xml.parsers.SAXParserFactory;
20
21import org.openstreetmap.josm.data.coor.LatLon;
22import org.openstreetmap.josm.data.gpx.GpxData;
23import org.openstreetmap.josm.data.gpx.GpxLink;
24import org.openstreetmap.josm.data.gpx.GpxRoute;
25import org.openstreetmap.josm.data.gpx.GpxTrack;
26import org.openstreetmap.josm.data.gpx.WayPoint;
27import org.xml.sax.Attributes;
28import org.xml.sax.InputSource;
29import org.xml.sax.SAXException;
30import 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 */
36public 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, copyright}
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 private boolean nokiaSportsTrackerBug = false;
61
62 @Override public void startDocument() {
63 accumulator = new StringBuffer();
64 states = new Stack<state>();
65 currentData = new GpxData();
66 }
67
68 private double parseCoord(String s) {
69 try {
70 return Double.parseDouble(s);
71 } catch (NumberFormatException ex) {
72 return Double.NaN;
73 }
74 }
75
76 private LatLon parseLatLon(Attributes atts) {
77 return new LatLon(
78 parseCoord(atts.getValue("lat")),
79 parseCoord(atts.getValue("lon")));
80 }
81
82 @Override public void startElement(String namespaceURI, String qName, String rqName, Attributes atts) throws SAXException {
83 switch(currentState) {
84 case init:
85 if (qName.equals("metadata")) {
86 states.push(currentState);
87 currentState = state.metadata;
88 } else if (qName.equals("wpt")) {
89 states.push(currentState);
90 currentState = state.wpt;
91 currentWayPoint = new WayPoint(parseLatLon(atts));
92 } else if (qName.equals("rte")) {
93 states.push(currentState);
94 currentState = state.rte;
95 currentRoute = new GpxRoute();
96 } else if (qName.equals("trk")) {
97 states.push(currentState);
98 currentState = state.trk;
99 currentTrack = new GpxTrack();
100 } else if (qName.equals("extensions")) {
101 states.push(currentState);
102 currentState = state.ext;
103 } else if (qName.equals("gpx") && atts.getValue("creator") != null && atts.getValue("creator").startsWith("Nokia Sports Tracker")) {
104 nokiaSportsTrackerBug = true;
105 }
106 break;
107 case author:
108 if (qName.equals("link")) {
109 states.push(currentState);
110 currentState = state.link;
111 currentLink = new GpxLink(atts.getValue("href"));
112 } else if (qName.equals("email")) {
113 currentData.attr.put(GpxData.META_AUTHOR_EMAIL, atts.getValue("id") + "@" + atts.getValue("domain"));
114 }
115 break;
116 case trk:
117 if (qName.equals("trkseg")) {
118 states.push(currentState);
119 currentState = state.trkseg;
120 currentTrackSeg = new ArrayList<WayPoint>();
121 } else if (qName.equals("link")) {
122 states.push(currentState);
123 currentState = state.link;
124 currentLink = new GpxLink(atts.getValue("href"));
125 } else if (qName.equals("extensions")) {
126 states.push(currentState);
127 currentState = state.ext;
128 }
129 break;
130 case metadata:
131 if (qName.equals("author")) {
132 states.push(currentState);
133 currentState = state.author;
134 } else if (qName.equals("extensions")) {
135 states.push(currentState);
136 currentState = state.ext;
137 } else if (qName.equals("copyright")) {
138 states.push(currentState);
139 currentState = state.copyright;
140 currentData.attr.put(GpxData.META_COPYRIGHT_AUTHOR, atts.getValue("author"));
141 } else if (qName.equals("link")) {
142 states.push(currentState);
143 currentState = state.link;
144 currentLink = new GpxLink(atts.getValue("href"));
145 }
146 break;
147 case trkseg:
148 if (qName.equals("trkpt")) {
149 states.push(currentState);
150 currentState = state.wpt;
151 currentWayPoint = new WayPoint(parseLatLon(atts));
152 }
153 break;
154 case wpt:
155 if (qName.equals("link")) {
156 states.push(currentState);
157 currentState = state.link;
158 currentLink = new GpxLink(atts.getValue("href"));
159 } else if (qName.equals("extensions")) {
160 states.push(currentState);
161 currentState = state.ext;
162 }
163 break;
164 case rte:
165 if (qName.equals("link")) {
166 states.push(currentState);
167 currentState = state.link;
168 currentLink = new GpxLink(atts.getValue("href"));
169 } else if (qName.equals("rtept")) {
170 states.push(currentState);
171 currentState = state.wpt;
172 currentWayPoint = new WayPoint(parseLatLon(atts));
173 } else if (qName.equals("extensions")) {
174 states.push(currentState);
175 currentState = state.ext;
176 }
177 break;
178 default:
179 }
180 accumulator.setLength(0);
181 }
182
183 @Override public void characters(char[] ch, int start, int length) {
184 /**
185 * Remove illegal characters generated by the Nokia Sports Tracker device.
186 * Don't do this crude substitution for all files, since it would destroy
187 * certain unicode characters.
188 */
189 if (nokiaSportsTrackerBug) {
190 for (int i=0; i<ch.length; ++i) {
191 if (ch[i] == 1) {
192 ch[i] = 32;
193 }
194 }
195 nokiaSportsTrackerBug = false;
196 }
197
198 accumulator.append(ch, start, length);
199 }
200
201 private Map<String, Object> getAttr() {
202 switch (currentState) {
203 case rte: return currentRoute.attr;
204 case metadata: return currentData.attr;
205 case wpt: return currentWayPoint.attr;
206 case trk: return currentTrack.attr;
207 default: return null;
208 }
209 }
210
211 @Override public void endElement(String namespaceURI, String qName, String rqName) {
212 switch (currentState) {
213 case metadata:
214 if (qName.equals("name")) {
215 currentData.attr.put(GpxData.META_NAME, accumulator.toString());
216 } else if (qName.equals("desc")) {
217 currentData.attr.put(GpxData.META_DESC, accumulator.toString());
218 } else if (qName.equals("time")) {
219 currentData.attr.put(GpxData.META_TIME, accumulator.toString());
220 } else if (qName.equals("keywords")) {
221 currentData.attr.put(GpxData.META_KEYWORDS, accumulator.toString());
222 } else if (qName.equals("metadata")) {
223 currentState = states.pop();
224 }
225 //TODO: parse bounds, extensions
226 break;
227 case author:
228 if (qName.equals("author")) {
229 currentState = states.pop();
230 } else if (qName.equals("name")) {
231 currentData.attr.put(GpxData.META_AUTHOR_NAME, accumulator.toString());
232 } else if (qName.equals("email")) {
233 // do nothing, has been parsed on startElement
234 } else if (qName.equals("link")) {
235 currentData.attr.put(GpxData.META_AUTHOR_LINK, currentLink);
236 }
237 break;
238 case copyright:
239 if (qName.equals("copyright")) {
240 currentState = states.pop();
241 } else if (qName.equals("year")) {
242 currentData.attr.put(GpxData.META_COPYRIGHT_YEAR, accumulator.toString());
243 } else if (qName.equals("license")) {
244 currentData.attr.put(GpxData.META_COPYRIGHT_LICENSE, accumulator.toString());
245 }
246 break;
247 case link:
248 if (qName.equals("text")) {
249 currentLink.text = accumulator.toString();
250 } else if (qName.equals("type")) {
251 currentLink.type = accumulator.toString();
252 } else if (qName.equals("link")) {
253 if (currentLink.uri == null && accumulator != null && accumulator.toString().length() != 0) {
254 currentLink = new GpxLink(accumulator.toString());
255 }
256 currentState = states.pop();
257 }
258 if (currentState == state.author) {
259 currentData.attr.put(GpxData.META_AUTHOR_LINK, currentLink);
260 } else if (currentState != state.link) {
261 Map<String, Object> attr = getAttr();
262 if (!attr.containsKey(GpxData.META_LINKS)) {
263 attr.put(GpxData.META_LINKS, new LinkedList<GpxLink>());
264 }
265 ((Collection<GpxLink>) attr.get(GpxData.META_LINKS)).add(currentLink);
266 }
267 break;
268 case wpt:
269 if ( qName.equals("ele") || qName.equals("magvar")
270 || qName.equals("name") || qName.equals("geoidheight")
271 || qName.equals("type") || qName.equals("sym")) {
272 currentWayPoint.attr.put(qName, accumulator.toString());
273 } else if(qName.equals("hdop") /*|| qName.equals("vdop") ||
274 qName.equals("pdop")*/) {
275 try {
276 currentWayPoint.attr.put(qName, Float.parseFloat(accumulator.toString()));
277 } catch(Exception e) {
278 currentWayPoint.attr.put(qName, new Float(0));
279 }
280 } else if (qName.equals("time")) {
281 currentWayPoint.attr.put(qName, accumulator.toString());
282 currentWayPoint.setTime();
283 } else if (qName.equals("cmt") || qName.equals("desc")) {
284 currentWayPoint.attr.put(qName, accumulator.toString());
285 currentWayPoint.setTime();
286 } else if (qName.equals("rtept")) {
287 currentState = states.pop();
288 currentRoute.routePoints.add(currentWayPoint);
289 } else if (qName.equals("trkpt")) {
290 currentState = states.pop();
291 currentTrackSeg.add(currentWayPoint);
292 } else if (qName.equals("wpt")) {
293 currentState = states.pop();
294 currentData.waypoints.add(currentWayPoint);
295 }
296 break;
297 case trkseg:
298 if (qName.equals("trkseg")) {
299 currentState = states.pop();
300 currentTrack.trackSegs.add(currentTrackSeg);
301 }
302 break;
303 case trk:
304 if (qName.equals("trk")) {
305 currentState = states.pop();
306 currentData.tracks.add(currentTrack);
307 } else if (qName.equals("name") || qName.equals("cmt")
308 || qName.equals("desc") || qName.equals("src")
309 || qName.equals("type") || qName.equals("number")
310 || qName.equals("url")) {
311 currentTrack.attr.put(qName, accumulator.toString());
312 }
313 break;
314 case ext:
315 if (qName.equals("extensions")) {
316 currentState = states.pop();
317 }
318 break;
319 default:
320 if (qName.equals("wpt")) {
321 currentState = states.pop();
322 } else if (qName.equals("rte")) {
323 currentState = states.pop();
324 currentData.routes.add(currentRoute);
325 }
326 }
327 }
328
329 @Override public void endDocument() throws SAXException {
330 if (!states.empty())
331 throw new SAXException(tr("Parse error: invalid document structure for GPX document."));
332 data = currentData;
333 }
334 }
335
336 /**
337 * Parse the input stream and store the result in trackData and markerData
338 *
339 * @param relativeMarkerPath The directory to use as relative path for all &lt;wpt&gt;
340 * marker tags. Maybe <code>null</code>, in which case no relative urls are constructed for the markers.
341 */
342 public GpxReader(InputStream source, File relativeMarkerPath) throws SAXException, IOException {
343
344 Parser parser = new Parser();
345 InputSource inputSource = new InputSource(new InputStreamReader(source, "UTF-8"));
346 try {
347 SAXParserFactory factory = SAXParserFactory.newInstance();
348 factory.setNamespaceAware(true);
349 factory.newSAXParser().parse(inputSource, parser);
350 } catch (ParserConfigurationException e) {
351 e.printStackTrace(); // broken SAXException chaining
352 throw new SAXException(e);
353 }
354 }
355}
Note: See TracBrowser for help on using the repository browser.