/* * Geopaparazzi - Digital field mapping on Android based devices * Copyright (C) 2016 HydroloGIS (www.hydrologis.com) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package eu.geopaparazzi.library.gpx.parser; import java.io.FileReader; 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.SAXParser; import javax.xml.parsers.SAXParserFactory; 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 eu.geopaparazzi.library.database.GPLog; /** * A very basic GPX parser to meet the need of the emulator control panel. * <p/> * It parses basic waypoint information, and tracks (merging segments). * * <p>Modified to also handle routes and multiple segments by Andrea Antonello (www.hydrologis.com) */ public class GpxParser { 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_ROUTE = "rte"; //$NON-NLS-1$ private final static String NODE_ROUTE_POINT = "rtept"; //$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 = new ArrayList<WayPoint>(); List<TrackSegment> mTrackSegmentList = new ArrayList<TrackSegment>(); List<Route> mRouteList = new ArrayList<Route>(); // --------- state for parsing --------- TrackSegment mCurrentTrackSegment; TrackPoint mCurrentTrackPoint; Route mCurrentRoute; RoutePoint mCurrentRoutePoint; 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 fragment_about the standard GPX nodes. try { if (NODE_WAYPOINT.equals(localName)) { mCurrentWayPoint = new WayPoint(); mWayPoints.add(mCurrentWayPoint); handleLocation(mCurrentWayPoint, attributes); } else if (NODE_TRACK.equals(localName)) { // ignore } else if (NODE_TRACK_SEGMENT.equals(localName)) { mCurrentTrackSegment = new TrackSegment(); mTrackSegmentList.add(mCurrentTrackSegment); } else if (NODE_TRACK_POINT.equals(localName)) { if (mCurrentTrackSegment != null) { mCurrentTrackSegment.addPoint(mCurrentTrackPoint = new TrackPoint()); handleLocation(mCurrentTrackPoint, attributes); } } else if (NODE_ROUTE.equals(localName)) { mCurrentRoute = new Route(); mRouteList.add(mCurrentRoute); } else if (NODE_ROUTE_POINT.equals(localName)) { if (mCurrentRoute != null) { mCurrentRoutePoint = new RoutePoint(); mCurrentRoute.addPoint(mCurrentRoutePoint); handleLocation(mCurrentRoutePoint, 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 (NODE_WAYPOINT.equals(localName)) { mCurrentWayPoint = null; } else if (NODE_TRACK.equals(localName)) { // ignore } else if (NODE_TRACK_SEGMENT.equals(localName)) { mCurrentTrackSegment = null; } else if (NODE_TRACK_POINT.equals(localName)) { mCurrentTrackPoint = null; } else if (NODE_ROUTE.equals(localName)) { mCurrentRoute = null; } else if (NODE_ROUTE_POINT.equals(localName)) { mCurrentRoutePoint = null; } else if (NODE_NAME.equals(localName)) { if (mCurrentTrackSegment != null) { mCurrentTrackSegment.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) { double elev = Double.parseDouble(mStringAccumulator.toString()); mCurrentTrackPoint.setElevation(elev); } else if (mCurrentWayPoint != null) { double elev = Double.parseDouble(mStringAccumulator.toString()); mCurrentWayPoint.setElevation(elev); } } 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 static 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 static 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. } } List<WayPoint> getWayPoints() { return mWayPoints; } List<TrackSegment> getTracks() { return mTrackSegmentList; } List<Route> getRoutes() { return mRouteList; } 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 TrackSegment { private String mName; private String mComment; private List<TrackPoint> mPoints = new ArrayList<TrackPoint>(); void setName( String name ) { mName = name; } /** * @return name. */ public String getName() { return mName; } void setComment( String comment ) { mComment = comment; } String getComment() { return mComment; } void addPoint( TrackPoint trackPoint ) { mPoints.add(trackPoint); } /** * @return get points list. */ public List<TrackPoint> getPoints() { return mPoints; } long getFirstPointTime() { if (mPoints.size() > 0) { return mPoints.get(0).getTime(); } return -1; } long getLastPointTime() { if (mPoints.size() > 0) { return mPoints.get(mPoints.size() - 1).getTime(); } return -1; } int getPointCount() { return mPoints.size(); } } /** * A GPS route. * <p/>A route is composed of a list of {@link RoutePoint} and optional name and comment. */ public final static class Route { private String mName; private String mComment; private List<RoutePoint> mPoints = new ArrayList<RoutePoint>(); void setName( String name ) { mName = name; } /** * @return thename. */ public String getName() { return mName; } void setComment( String comment ) { mComment = comment; } /** * @return comment. */ public String getComment() { return mComment; } void addPoint( RoutePoint routePoint ) { mPoints.add(routePoint); } /** * @return points list. */ public List<RoutePoint> getPoints() { return mPoints; } /** * @return start time. */ public long getFirstPointTime() { if (mPoints.size() > 0) { return mPoints.get(0).getTime(); } return -1; } /** * @return end time. */ public long getLastPointTime() { if (mPoints.size() > 0) { return mPoints.get(mPoints.size() - 1).getTime(); } return -1; } /** * @return points count. */ 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 (Exception e) { GPLog.error(this, null, e); } return false; } /** * Returns the parsed {@link WayPoint} objects, or <code>null</code> if none were found (or * if the parsing failed. * * @return list of waypoints. */ public List<WayPoint> getWayPoints() { if (mHandler != null) return mHandler.getWayPoints(); return null; } /** * Returns the parsed {@link TrackSegment} objects, or <code>null</code> if none were found (or * if the parsing failed. * * @return list of tracksegments. */ public List<TrackSegment> getTracks() { if (mHandler != null) { return mHandler.getTracks(); } return null; } /** * @return the list of routes. */ public List<Route> getRoutes() { if (mHandler != null) { return mHandler.getRoutes(); } return null; } }