1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ddmuilib.location; 18 19 import org.xml.sax.Attributes; 20 import org.xml.sax.InputSource; 21 import org.xml.sax.SAXException; 22 import org.xml.sax.SAXParseException; 23 import org.xml.sax.helpers.DefaultHandler; 24 25 import java.io.FileReader; 26 import java.io.IOException; 27 import java.util.ArrayList; 28 import java.util.Calendar; 29 import java.util.List; 30 import java.util.TimeZone; 31 import java.util.regex.Matcher; 32 import java.util.regex.Pattern; 33 34 import javax.xml.parsers.ParserConfigurationException; 35 import javax.xml.parsers.SAXParser; 36 import javax.xml.parsers.SAXParserFactory; 37 38 /** 39 * A very basic GPX parser to meet the need of the emulator control panel. 40 * <p/> 41 * It parses basic waypoint information, and tracks (merging segments). 42 */ 43 public class GpxParser { 44 45 private final static String NS_GPX = "http://www.topografix.com/GPX/1/1"; //$NON-NLS-1$ 46 47 private final static String NODE_WAYPOINT = "wpt"; //$NON-NLS-1$ 48 private final static String NODE_TRACK = "trk"; //$NON-NLS-1$ 49 private final static String NODE_TRACK_SEGMENT = "trkseg"; //$NON-NLS-1$ 50 private final static String NODE_TRACK_POINT = "trkpt"; //$NON-NLS-1$ 51 private final static String NODE_NAME = "name"; //$NON-NLS-1$ 52 private final static String NODE_TIME = "time"; //$NON-NLS-1$ 53 private final static String NODE_ELEVATION = "ele"; //$NON-NLS-1$ 54 private final static String NODE_DESCRIPTION = "desc"; //$NON-NLS-1$ 55 private final static String ATTR_LONGITUDE = "lon"; //$NON-NLS-1$ 56 private final static String ATTR_LATITUDE = "lat"; //$NON-NLS-1$ 57 58 private static SAXParserFactory sParserFactory; 59 60 static { 61 sParserFactory = SAXParserFactory.newInstance(); 62 sParserFactory.setNamespaceAware(true); 63 } 64 65 private String mFileName; 66 67 private GpxHandler mHandler; 68 69 /** Pattern to parse time with optional sub-second precision, and optional 70 * Z indicating the time is in UTC. */ 71 private final static Pattern ISO8601_TIME = 72 Pattern.compile("(\\d{4})-(\\d\\d)-(\\d\\d)T(\\d\\d):(\\d\\d):(\\d\\d)(?:(\\.\\d+))?(Z)?"); //$NON-NLS-1$ 73 74 /** 75 * Handler for the SAX parser. 76 */ 77 private static class GpxHandler extends DefaultHandler { 78 // --------- parsed data --------- 79 List<WayPoint> mWayPoints; 80 List<Track> mTrackList; 81 82 // --------- state for parsing --------- 83 Track mCurrentTrack; 84 TrackPoint mCurrentTrackPoint; 85 WayPoint mCurrentWayPoint; 86 final StringBuilder mStringAccumulator = new StringBuilder(); 87 88 boolean mSuccess = true; 89 90 @Override 91 public void startElement(String uri, String localName, String name, Attributes attributes) 92 throws SAXException { 93 // we only care about the standard GPX nodes. 94 try { 95 if (NS_GPX.equals(uri)) { 96 if (NODE_WAYPOINT.equals(localName)) { 97 if (mWayPoints == null) { 98 mWayPoints = new ArrayList<WayPoint>(); 99 } 100 101 mWayPoints.add(mCurrentWayPoint = new WayPoint()); 102 handleLocation(mCurrentWayPoint, attributes); 103 } else if (NODE_TRACK.equals(localName)) { 104 if (mTrackList == null) { 105 mTrackList = new ArrayList<Track>(); 106 } 107 108 mTrackList.add(mCurrentTrack = new Track()); 109 } else if (NODE_TRACK_SEGMENT.equals(localName)) { 110 // for now we do nothing here. This will merge all the segments into 111 // a single TrackPoint list in the Track. 112 } else if (NODE_TRACK_POINT.equals(localName)) { 113 if (mCurrentTrack != null) { 114 mCurrentTrack.addPoint(mCurrentTrackPoint = new TrackPoint()); 115 handleLocation(mCurrentTrackPoint, attributes); 116 } 117 } 118 } 119 } finally { 120 // no matter the node, we empty the StringBuilder accumulator when we start 121 // a new node. 122 mStringAccumulator.setLength(0); 123 } 124 } 125 126 /** 127 * Processes new characters for the node content. The characters are simply stored, 128 * and will be processed when {@link #endElement(String, String, String)} is called. 129 */ 130 @Override 131 public void characters(char[] ch, int start, int length) throws SAXException { 132 mStringAccumulator.append(ch, start, length); 133 } 134 135 @Override 136 public void endElement(String uri, String localName, String name) throws SAXException { 137 if (NS_GPX.equals(uri)) { 138 if (NODE_WAYPOINT.equals(localName)) { 139 mCurrentWayPoint = null; 140 } else if (NODE_TRACK.equals(localName)) { 141 mCurrentTrack = null; 142 } else if (NODE_TRACK_POINT.equals(localName)) { 143 mCurrentTrackPoint = null; 144 } else if (NODE_NAME.equals(localName)) { 145 if (mCurrentTrack != null) { 146 mCurrentTrack.setName(mStringAccumulator.toString()); 147 } else if (mCurrentWayPoint != null) { 148 mCurrentWayPoint.setName(mStringAccumulator.toString()); 149 } 150 } else if (NODE_TIME.equals(localName)) { 151 if (mCurrentTrackPoint != null) { 152 mCurrentTrackPoint.setTime(computeTime(mStringAccumulator.toString())); 153 } 154 } else if (NODE_ELEVATION.equals(localName)) { 155 if (mCurrentTrackPoint != null) { 156 mCurrentTrackPoint.setElevation( 157 Double.parseDouble(mStringAccumulator.toString())); 158 } else if (mCurrentWayPoint != null) { 159 mCurrentWayPoint.setElevation( 160 Double.parseDouble(mStringAccumulator.toString())); 161 } 162 } else if (NODE_DESCRIPTION.equals(localName)) { 163 if (mCurrentWayPoint != null) { 164 mCurrentWayPoint.setDescription(mStringAccumulator.toString()); 165 } 166 } 167 } 168 } 169 170 @Override 171 public void error(SAXParseException e) throws SAXException { 172 mSuccess = false; 173 } 174 175 @Override 176 public void fatalError(SAXParseException e) throws SAXException { 177 mSuccess = false; 178 } 179 180 /** 181 * Converts the string description of the time into milliseconds since epoch. 182 * @param timeString the string data. 183 * @return date in milliseconds. 184 */ 185 private long computeTime(String timeString) { 186 // Time looks like: 2008-04-05T19:24:50Z 187 Matcher m = ISO8601_TIME.matcher(timeString); 188 if (m.matches()) { 189 // get the various elements and reconstruct time as a long. 190 try { 191 int year = Integer.parseInt(m.group(1)); 192 int month = Integer.parseInt(m.group(2)); 193 int date = Integer.parseInt(m.group(3)); 194 int hourOfDay = Integer.parseInt(m.group(4)); 195 int minute = Integer.parseInt(m.group(5)); 196 int second = Integer.parseInt(m.group(6)); 197 198 // handle the optional parameters. 199 int milliseconds = 0; 200 201 String subSecondGroup = m.group(7); 202 if (subSecondGroup != null) { 203 milliseconds = (int)(1000 * Double.parseDouble(subSecondGroup)); 204 } 205 206 boolean utcTime = m.group(8) != null; 207 208 // now we convert into milliseconds since epoch. 209 Calendar c; 210 if (utcTime) { 211 c = Calendar.getInstance(TimeZone.getTimeZone("GMT")); //$NON-NLS-1$ 212 } else { 213 c = Calendar.getInstance(); 214 } 215 216 c.set(year, month, date, hourOfDay, minute, second); 217 218 return c.getTimeInMillis() + milliseconds; 219 } catch (NumberFormatException e) { 220 // format is invalid, we'll return -1 below. 221 } 222 223 } 224 225 // invalid time! 226 return -1; 227 } 228 229 /** 230 * Handles the location attributes and store them into a {@link LocationPoint}. 231 * @param locationNode the {@link LocationPoint} to receive the location data. 232 * @param attributes the attributes from the XML node. 233 */ 234 private void handleLocation(LocationPoint locationNode, Attributes attributes) { 235 try { 236 double longitude = Double.parseDouble(attributes.getValue(ATTR_LONGITUDE)); 237 double latitude = Double.parseDouble(attributes.getValue(ATTR_LATITUDE)); 238 239 locationNode.setLocation(longitude, latitude); 240 } catch (NumberFormatException e) { 241 // wrong data, do nothing. 242 } 243 } 244 245 WayPoint[] getWayPoints() { 246 if (mWayPoints != null) { 247 return mWayPoints.toArray(new WayPoint[mWayPoints.size()]); 248 } 249 250 return null; 251 } 252 253 Track[] getTracks() { 254 if (mTrackList != null) { 255 return mTrackList.toArray(new Track[mTrackList.size()]); 256 } 257 258 return null; 259 } 260 261 boolean getSuccess() { 262 return mSuccess; 263 } 264 } 265 266 /** 267 * A GPS track. 268 * <p/>A track is composed of a list of {@link TrackPoint} and optional name and comment. 269 */ 270 public final static class Track { 271 private String mName; 272 private String mComment; 273 private List<TrackPoint> mPoints = new ArrayList<TrackPoint>(); 274 275 void setName(String name) { 276 mName = name; 277 } 278 279 public String getName() { 280 return mName; 281 } 282 283 void setComment(String comment) { 284 mComment = comment; 285 } 286 287 public String getComment() { 288 return mComment; 289 } 290 291 void addPoint(TrackPoint trackPoint) { 292 mPoints.add(trackPoint); 293 } 294 295 public TrackPoint[] getPoints() { 296 return mPoints.toArray(new TrackPoint[mPoints.size()]); 297 } 298 299 public long getFirstPointTime() { 300 if (mPoints.size() > 0) { 301 return mPoints.get(0).getTime(); 302 } 303 304 return -1; 305 } 306 307 public long getLastPointTime() { 308 if (mPoints.size() > 0) { 309 return mPoints.get(mPoints.size()-1).getTime(); 310 } 311 312 return -1; 313 } 314 315 public int getPointCount() { 316 return mPoints.size(); 317 } 318 } 319 320 /** 321 * Creates a new GPX parser for a file specified by its full path. 322 * @param fileName The full path of the GPX file to parse. 323 */ 324 public GpxParser(String fileName) { 325 mFileName = fileName; 326 } 327 328 /** 329 * Parses the GPX file. 330 * @return <code>true</code> if success. 331 */ 332 public boolean parse() { 333 try { 334 SAXParser parser = sParserFactory.newSAXParser(); 335 336 mHandler = new GpxHandler(); 337 338 parser.parse(new InputSource(new FileReader(mFileName)), mHandler); 339 340 return mHandler.getSuccess(); 341 } catch (ParserConfigurationException e) { 342 } catch (SAXException e) { 343 } catch (IOException e) { 344 } finally { 345 } 346 347 return false; 348 } 349 350 /** 351 * Returns the parsed {@link WayPoint} objects, or <code>null</code> if none were found (or 352 * if the parsing failed. 353 */ 354 public WayPoint[] getWayPoints() { 355 if (mHandler != null) { 356 return mHandler.getWayPoints(); 357 } 358 359 return null; 360 } 361 362 /** 363 * Returns the parsed {@link Track} objects, or <code>null</code> if none were found (or 364 * if the parsing failed. 365 */ 366 public Track[] getTracks() { 367 if (mHandler != null) { 368 return mHandler.getTracks(); 369 } 370 371 return null; 372 } 373 } 374