blob: a30337a7119c53e298b363dc32c6ead5dfdb5440 [file] [log] [blame]
/*
* 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;
}
}