Home | History | Annotate | Download | only in location
      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