/* * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.ddmuilib.location; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.xml.sax.helpers.DefaultHandler; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import java.util.TimeZone; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; /** * A very basic GPX parser to meet the need of the emulator control panel. * <p/> * It parses basic waypoint information, and tracks (merging segments). */ public class GpxParser { private final static String NS_GPX = "http://www.topografix.com/GPX/1/1"; //$NON-NLS-1$ private final static String NODE_WAYPOINT = "wpt"; //$NON-NLS-1$ private final static String NODE_TRACK = "trk"; //$NON-NLS-1$ private final static String NODE_TRACK_SEGMENT = "trkseg"; //$NON-NLS-1$ private final static String NODE_TRACK_POINT = "trkpt"; //$NON-NLS-1$ private final static String NODE_NAME = "name"; //$NON-NLS-1$ private final static String NODE_TIME = "time"; //$NON-NLS-1$ private final static String NODE_ELEVATION = "ele"; //$NON-NLS-1$ private final static String NODE_DESCRIPTION = "desc"; //$NON-NLS-1$ private final static String ATTR_LONGITUDE = "lon"; //$NON-NLS-1$ private final static String ATTR_LATITUDE = "lat"; //$NON-NLS-1$ private static SAXParserFactory sParserFactory; static { sParserFactory = SAXParserFactory.newInstance(); sParserFactory.setNamespaceAware(true); } private String mFileName; private GpxHandler mHandler; /** Pattern to parse time with optional sub-second precision, and optional * Z indicating the time is in UTC. */ private final static Pattern ISO8601_TIME = Pattern.compile("(\\d{4})-(\\d\\d)-(\\d\\d)T(\\d\\d):(\\d\\d):(\\d\\d)(?:(\\.\\d+))?(Z)?"); //$NON-NLS-1$ /** * Handler for the SAX parser. */ private static class GpxHandler extends DefaultHandler { // --------- parsed data --------- List<WayPoint> mWayPoints; List<Track> mTrackList; // --------- state for parsing --------- Track mCurrentTrack; TrackPoint mCurrentTrackPoint; WayPoint mCurrentWayPoint; final StringBuilder mStringAccumulator = new StringBuilder(); boolean mSuccess = true; @Override public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException { // we only care about the standard GPX nodes. try { if (NS_GPX.equals(uri)) { if (NODE_WAYPOINT.equals(localName)) { if (mWayPoints == null) { mWayPoints = new ArrayList<WayPoint>(); } mWayPoints.add(mCurrentWayPoint = new WayPoint()); handleLocation(mCurrentWayPoint, attributes); } else if (NODE_TRACK.equals(localName)) { if (mTrackList == null) { mTrackList = new ArrayList<Track>(); } mTrackList.add(mCurrentTrack = new Track()); } else if (NODE_TRACK_SEGMENT.equals(localName)) { // for now we do nothing here. This will merge all the segments into // a single TrackPoint list in the Track. } else if (NODE_TRACK_POINT.equals(localName)) { if (mCurrentTrack != null) { mCurrentTrack.addPoint(mCurrentTrackPoint = new TrackPoint()); handleLocation(mCurrentTrackPoint, attributes); } } } } finally { // no matter the node, we empty the StringBuilder accumulator when we start // a new node. mStringAccumulator.setLength(0); } } /** * Processes new characters for the node content. The characters are simply stored, * and will be processed when {@link #endElement(String, String, String)} is called. */ @Override public void characters(char[] ch, int start, int length) throws SAXException { mStringAccumulator.append(ch, start, length); } @Override public void endElement(String uri, String localName, String name) throws SAXException { if (NS_GPX.equals(uri)) { if (NODE_WAYPOINT.equals(localName)) { mCurrentWayPoint = null; } else if (NODE_TRACK.equals(localName)) { mCurrentTrack = null; } else if (NODE_TRACK_POINT.equals(localName)) { mCurrentTrackPoint = null; } else if (NODE_NAME.equals(localName)) { if (mCurrentTrack != null) { mCurrentTrack.setName(mStringAccumulator.toString()); } else if (mCurrentWayPoint != null) { mCurrentWayPoint.setName(mStringAccumulator.toString()); } } else if (NODE_TIME.equals(localName)) { if (mCurrentTrackPoint != null) { mCurrentTrackPoint.setTime(computeTime(mStringAccumulator.toString())); } } else if (NODE_ELEVATION.equals(localName)) { if (mCurrentTrackPoint != null) { mCurrentTrackPoint.setElevation( Double.parseDouble(mStringAccumulator.toString())); } else if (mCurrentWayPoint != null) { mCurrentWayPoint.setElevation( Double.parseDouble(mStringAccumulator.toString())); } } else if (NODE_DESCRIPTION.equals(localName)) { if (mCurrentWayPoint != null) { mCurrentWayPoint.setDescription(mStringAccumulator.toString()); } } } } @Override public void error(SAXParseException e) throws SAXException { mSuccess = false; } @Override public void fatalError(SAXParseException e) throws SAXException { mSuccess = false; } /** * Converts the string description of the time into milliseconds since epoch. * @param timeString the string data. * @return date in milliseconds. */ private long computeTime(String timeString) { // Time looks like: 2008-04-05T19:24:50Z Matcher m = ISO8601_TIME.matcher(timeString); if (m.matches()) { // get the various elements and reconstruct time as a long. try { int year = Integer.parseInt(m.group(1)); int month = Integer.parseInt(m.group(2)); int date = Integer.parseInt(m.group(3)); int hourOfDay = Integer.parseInt(m.group(4)); int minute = Integer.parseInt(m.group(5)); int second = Integer.parseInt(m.group(6)); // handle the optional parameters. int milliseconds = 0; String subSecondGroup = m.group(7); if (subSecondGroup != null) { milliseconds = (int)(1000 * Double.parseDouble(subSecondGroup)); } boolean utcTime = m.group(8) != null; // now we convert into milliseconds since epoch. Calendar c; if (utcTime) { c = Calendar.getInstance(TimeZone.getTimeZone("GMT")); //$NON-NLS-1$ } else { c = Calendar.getInstance(); } c.set(year, month, date, hourOfDay, minute, second); return c.getTimeInMillis() + milliseconds; } catch (NumberFormatException e) { // format is invalid, we'll return -1 below. } } // invalid time! return -1; } /** * Handles the location attributes and store them into a {@link LocationPoint}. * @param locationNode the {@link LocationPoint} to receive the location data. * @param attributes the attributes from the XML node. */ private void handleLocation(LocationPoint locationNode, Attributes attributes) { try { double longitude = Double.parseDouble(attributes.getValue(ATTR_LONGITUDE)); double latitude = Double.parseDouble(attributes.getValue(ATTR_LATITUDE)); locationNode.setLocation(longitude, latitude); } catch (NumberFormatException e) { // wrong data, do nothing. } } WayPoint[] getWayPoints() { if (mWayPoints != null) { return mWayPoints.toArray(new WayPoint[mWayPoints.size()]); } return null; } Track[] getTracks() { if (mTrackList != null) { return mTrackList.toArray(new Track[mTrackList.size()]); } return null; } boolean getSuccess() { return mSuccess; } } /** * A GPS track. * <p/>A track is composed of a list of {@link TrackPoint} and optional name and comment. */ public final static class Track { private String mName; private String mComment; private List<TrackPoint> mPoints = new ArrayList<TrackPoint>(); void setName(String name) { mName = name; } public String getName() { return mName; } void setComment(String comment) { mComment = comment; } public String getComment() { return mComment; } void addPoint(TrackPoint trackPoint) { mPoints.add(trackPoint); } public TrackPoint[] getPoints() { return mPoints.toArray(new TrackPoint[mPoints.size()]); } public long getFirstPointTime() { if (mPoints.size() > 0) { return mPoints.get(0).getTime(); } return -1; } public long getLastPointTime() { if (mPoints.size() > 0) { return mPoints.get(mPoints.size()-1).getTime(); } return -1; } public int getPointCount() { return mPoints.size(); } } /** * Creates a new GPX parser for a file specified by its full path. * @param fileName The full path of the GPX file to parse. */ public GpxParser(String fileName) { mFileName = fileName; } /** * Parses the GPX file. * @return <code>true</code> if success. */ public boolean parse() { try { SAXParser parser = sParserFactory.newSAXParser(); mHandler = new GpxHandler(); parser.parse(new InputSource(new FileReader(mFileName)), mHandler); return mHandler.getSuccess(); } catch (ParserConfigurationException e) { } catch (SAXException e) { } catch (IOException e) { } finally { } return false; } /** * Returns the parsed {@link WayPoint} objects, or <code>null</code> if none were found (or * if the parsing failed. */ public WayPoint[] getWayPoints() { if (mHandler != null) { return mHandler.getWayPoints(); } return null; } /** * Returns the parsed {@link Track} objects, or <code>null</code> if none were found (or * if the parsing failed. */ public Track[] getTracks() { if (mHandler != null) { return mHandler.getTracks(); } return null; } }