net.tourbook.device.sporttracks.FitLogSAXHandler.java Source code

Java tutorial

Introduction

Here is the source code for net.tourbook.device.sporttracks.FitLogSAXHandler.java

Source

/*******************************************************************************
 * Copyright (C) 2005, 2010  Wolfgang Schramm and Contributors
 * 
 * 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 version 2 of the License.
 * 
 * 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, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
 *******************************************************************************/
package net.tourbook.device.sporttracks;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.NoSuchElementException;
import java.util.Set;

import net.tourbook.chart.ChartLabel;
import net.tourbook.data.IWeather;
import net.tourbook.data.TimeData;
import net.tourbook.data.TourData;
import net.tourbook.data.TourMarker;
import net.tourbook.data.TourTag;
import net.tourbook.data.TourType;
import net.tourbook.database.TourDatabase;
import net.tourbook.device.InvalidDeviceSAXException;
import net.tourbook.device.Messages;
import net.tourbook.importdata.TourbookDevice;
import net.tourbook.preferences.TourTypeColorDefinition;
import net.tourbook.util.Util;

import org.eclipse.osgi.util.NLS;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

public class FitLogSAXHandler extends DefaultHandler {

    private static final String TAG_ACTIVITY = "Activity"; //$NON-NLS-1$
    private static final String TAG_ACTIVITY_CADENCE = "Cadence"; //$NON-NLS-1$
    private static final String TAG_ACTIVITY_CALORIES = "Calories"; //$NON-NLS-1$
    private static final String TAG_ACTIVITY_CATEGORY = "Category"; //$NON-NLS-1$
    private static final String TAG_ACTIVITY_DURATION = "Duration"; //$NON-NLS-1$
    private static final String TAG_ACTIVITY_DISTANCE = "Distance"; //$NON-NLS-1$
    private static final String TAG_ACTIVITY_ELEVATION = "Elevation"; //$NON-NLS-1$
    private static final String TAG_ACTIVITY_EQUIPMENT_ITEM = "EquipmentItem"; //$NON-NLS-1$
    private static final String TAG_ACTIVITY_HEART_RATE = "HeartRate"; //$NON-NLS-1$
    private static final String TAG_ACTIVITY_LOCATION = "Location"; //$NON-NLS-1$
    private static final String TAG_ACTIVITY_NAME = "Name"; //$NON-NLS-1$
    private static final String TAG_ACTIVITY_NOTES = "Notes"; //$NON-NLS-1$
    private static final String TAG_ACTIVITY_WEATHER = "Weather"; //$NON-NLS-1$

    private static final String ATTRIB_NAME = "Name"; //$NON-NLS-1$
    private static final String ATTRIB_START_TIME = "StartTime"; //$NON-NLS-1$
    private static final String ATTRIB_TOTAL_SECONDS = "TotalSeconds"; //$NON-NLS-1$
    private static final String ATTRIB_TOTAL_METERS = "TotalMeters"; //$NON-NLS-1$
    private static final String ATTRIB_TOTAL_CAL = "TotalCal"; //$NON-NLS-1$
    private static final String ATTRIB_ASCEND_METERS = "AscendMeters"; //$NON-NLS-1$
    private static final String ATTRIB_DESCEND_METERS = "DescendMeters"; //$NON-NLS-1$
    private static final String ATTRIB_AVERAGE_BPM = "AverageBPM"; //$NON-NLS-1$
    private static final String ATTRIB_MAXIMUM_BPM = "MaximumBPM"; //$NON-NLS-1$
    private static final String ATTRIB_AVERAGE_RPM = "AverageRPM"; //$NON-NLS-1$
    private static final String ATTRIB_WEATHER_TEMP = "Temp"; //$NON-NLS-1$
    private static final String ATTRIB_WEATHER_CONDITIONS = "Conditions"; //$NON-NLS-1$
    //
    private static final String TAG_TRACK = "Track"; //$NON-NLS-1$
    private static final String TAG_TRACK_PT = "pt"; //$NON-NLS-1$
    private static final String ATTRIB_PT_TM = "tm"; //$NON-NLS-1$
    private static final String ATTRIB_PT_LAT = "lat"; //$NON-NLS-1$
    private static final String ATTRIB_PT_LON = "lon"; //$NON-NLS-1$
    private static final String ATTRIB_PT_DIST = "dist"; //$NON-NLS-1$
    private static final String ATTRIB_PT_ELE = "ele"; //$NON-NLS-1$
    private static final String ATTRIB_PT_HR = "hr"; //$NON-NLS-1$
    private static final String ATTRIB_PT_CADENCE = "cadence"; //$NON-NLS-1$
    private static final String ATTRIB_PT_POWER = "power"; //$NON-NLS-1$
    //
    private static final String TAG_LAPS = "Laps"; //$NON-NLS-1$
    private static final String TAG_LAP = "Lap"; //$NON-NLS-1$
    //
    private String _importFilePath;
    private HashMap<Long, TourData> _tourDataMap;
    private FitLogDeviceDataReader _device;
    private Activity _currentActivity;

    private double _prevLatitude;
    private double _prevLongitude;
    private double _distanceAbsolute;

    private boolean _isImported = false;
    private boolean _isNewTag = false;
    private boolean _isNewTourType = false;

    private boolean _isInActivity;
    private boolean _isInTrack;
    private boolean _isInName;

    private boolean _isInNotes;
    private boolean _isInWeather;
    private StringBuilder _characters = new StringBuilder(100);

    private boolean _isInLaps;
    private ArrayList<TourType> _allTourTypes;
    private static final DateTimeFormatter _dtParser = ISODateTimeFormat.dateTimeParser();

    private static final HashMap<String, String> _weatherId = new HashMap<String, String>();
    {
        /*
         * entries which are marked with *) have not a corresponding id/image within MyTourbook
         */
        _weatherId.put("Clear", IWeather.WEATHER_ID_CLEAR); //                        //$NON-NLS-1$
        _weatherId.put("ScatterClouds", IWeather.WEATHER_ID_PART_CLOUDS); //          *)   //$NON-NLS-1$
        _weatherId.put("PartClouds", IWeather.WEATHER_ID_PART_CLOUDS); //               //$NON-NLS-1$
        _weatherId.put("Overcast", IWeather.WEATHER_ID_OVERCAST); //                  //$NON-NLS-1$
        _weatherId.put("MostClouds", IWeather.WEATHER_ID_OVERCAST); //               *)   //$NON-NLS-1$
        _weatherId.put("Clouds", IWeather.WEATHER_ID_PART_CLOUDS); //               *)   //$NON-NLS-1$
        _weatherId.put("ChanceRain", IWeather.WEATHER_ID_SCATTERED_SHOWERS); //         *)   //$NON-NLS-1$
        _weatherId.put("LightDrizzle", IWeather.WEATHER_ID_SCATTERED_SHOWERS); //      *)   //$NON-NLS-1$
        _weatherId.put("LightRain", IWeather.WEATHER_ID_SCATTERED_SHOWERS); //            //$NON-NLS-1$
        _weatherId.put("Rain", IWeather.WEATHER_ID_RAIN); //                        //$NON-NLS-1$
        _weatherId.put("HeavyRain", IWeather.WEATHER_ID_RAIN); //                  *)   //$NON-NLS-1$
        _weatherId.put("ChanceThunder", IWeather.WEATHER_ID_LIGHTNING); //            *)   //$NON-NLS-1$
        _weatherId.put("Thunder", IWeather.WEATHER_ID_LIGHTNING); //                  //$NON-NLS-1$
        _weatherId.put("Snow", IWeather.WEATHER_ID_SNOW); //                        //$NON-NLS-1$
        _weatherId.put("Haze", IWeather.WEATHER_ID_PART_CLOUDS); //                  *)   //$NON-NLS-1$
    }

    private class Activity {

        private ArrayList<TimeData> timeSlices = new ArrayList<TimeData>();
        private ArrayList<Lap> laps = new ArrayList<Lap>();
        private ArrayList<String> equipmentName = new ArrayList<String>();

        private DateTime tourStartTime;
        private long tourStartTimeMills = Long.MIN_VALUE;
        //      private DateTime         trackTourDateTime;
        //      private long            trackTourStartTime   = Long.MIN_VALUE;

        private String location;
        private String name;
        private String notes;
        private String categoryName;

        private int calories;
        private int duration;
        private int distance;
        private int elevationUp;
        private int elevationDown;
        private int avgPulse;
        private int maxPulse;
        private int avgCadence;
        //      private int               maxCadence;      is not yet supported

        private String weatherText;
        private String weatherConditions;
        private float weatherTemperature = Float.MIN_VALUE;
    }

    private class Lap {

        private long lapStartTime;
    }

    public FitLogSAXHandler(final FitLogDeviceDataReader device, final String importFilePath,
            final HashMap<Long, TourData> tourDataMap) {

        _importFilePath = importFilePath;
        _tourDataMap = tourDataMap;
        _device = device;

        _allTourTypes = TourDatabase.getAllTourTypes();
    }

    @Override
    public void characters(final char[] chars, final int startIndex, final int length) throws SAXException {

        if (_isInName || _isInNotes || _isInWeather) {

            _characters.append(chars, startIndex, length);
        }
    }

    @Override
    public void endElement(final String uri, final String localName, final String name) throws SAXException {

        /*
         * get values
         */
        if (_isInName || _isInNotes || _isInWeather) {
            parseActivity02End(name);
        }

        /*
         * set state
         */
        if (name.equals(TAG_TRACK)) {

            _isInTrack = false;

        } else if (name.equals(TAG_LAPS)) {

            _isInLaps = false;

        } else if (name.equals(TAG_ACTIVITY)) {

            // activity/tour ends

            _isInActivity = false;

            finalizeTour();
        }
    }

    private void finalizeTour() {

        boolean isComputeDrivingTime = true;

        // create data object for each tour
        final TourData tourData = new TourData();

        /*
         * set tour start date/time
         */
        //      DateTime tourDateTime = _currentActivity.trackTourDateTime;
        //      final long trackStartTime = _currentActivity.trackTourStartTime;
        //      if (trackStartTime != Long.MIN_VALUE && trackStartTime < 0) {
        //
        //         // this case occured, e.g. year was 0002
        //         tourDateTime = _currentActivity.tourStartTime;
        //
        //      } else if (tourDateTime == null) {
        //
        //         // this case can occure when a tour do not have a track
        //         tourDateTime = _currentActivity.tourStartTime;
        //      }
        final DateTime tourStartTime = _currentActivity.tourStartTime;

        tourData.setStartHour((short) tourStartTime.getHourOfDay());
        tourData.setStartMinute((short) tourStartTime.getMinuteOfHour());
        tourData.setStartSecond((short) tourStartTime.getSecondOfMinute());

        tourData.setStartYear((short) tourStartTime.getYear());
        tourData.setStartMonth((short) tourStartTime.getMonthOfYear());
        tourData.setStartDay((short) tourStartTime.getDayOfMonth());

        tourData.setWeek(tourStartTime);

        tourData.setTourTitle(_currentActivity.name);
        tourData.setTourDescription(_currentActivity.notes);
        tourData.setTourStartPlace(_currentActivity.location);

        tourData.setCalories(_currentActivity.calories);

        /*
         * weather
         */
        tourData.setWeather(_currentActivity.weatherText);
        tourData.setWeatherClouds(_weatherId.get(_currentActivity.weatherConditions));

        final float weatherTemperature = _currentActivity.weatherTemperature;
        if (weatherTemperature != Float.MIN_VALUE) {
            tourData.setTemperatureScale(TourbookDevice.TEMPERATURE_SCALE);
            tourData.setAvgTemperature((int) (weatherTemperature * TourbookDevice.TEMPERATURE_SCALE));
        }

        tourData.importRawDataFile = _importFilePath;
        tourData.setTourImportFilePath(_importFilePath);

        tourData.setDeviceTimeInterval((short) -1);

        if (_currentActivity.timeSlices.size() == 0) {

            // tour do not contain a track

            tourData.setTourDistance(_currentActivity.distance);

            tourData.setTourRecordingTime(_currentActivity.duration);
            tourData.setTourDrivingTime(_currentActivity.duration);
            isComputeDrivingTime = false;

            tourData.setTourAltUp(_currentActivity.elevationUp);
            tourData.setTourAltDown(_currentActivity.elevationDown);

        } else {
            tourData.createTimeSeries(_currentActivity.timeSlices, false);
        }

        // after all data are added, the tour id can be created because it depends on the tour distance
        final Long tourId = tourData
                .createTourId(_device.createUniqueId(tourData, Util.UNIQUE_ID_SUFFIX_SPORT_TRACKS_FITLOG));

        // check if the tour is already imported
        if (_tourDataMap.containsKey(tourId) == false) {

            if (isComputeDrivingTime) {
                tourData.computeTourDrivingTime();
            }
            tourData.computeAltitudeUpDown();
            tourData.computeComputedValues();

            if (tourData.pulseSerie == null) {
                tourData.setAvgPulse(_currentActivity.avgPulse);
                tourData.setMaxPulse(_currentActivity.maxPulse);
            }

            if (tourData.cadenceSerie == null) {
                tourData.setAvgCadence(_currentActivity.avgCadence);
            }

            tourData.setDeviceId(_device.deviceId);
            tourData.setDeviceName(_device.visibleName);

            finalizeTour10SetTourType(tourData);
            finalizeTour20SetTags(tourData);
            finalizeTour30CreateMarkers(tourData);

            // add new tour to other tours
            _tourDataMap.put(tourId, tourData);
        }

        // cleanup
        _currentActivity.timeSlices.clear();
        _currentActivity.laps.clear();
        _currentActivity.equipmentName.clear();

        _isImported = true;
    }

    /**
     * Set tour type from category field
     * 
     * @param tourData
     */
    private void finalizeTour10SetTourType(final TourData tourData) {

        final String categoryName = _currentActivity.categoryName;

        if (categoryName == null) {
            return;
        }

        TourType tourType = null;

        // find tour type in existing tour types
        for (final TourType mapTourType : _allTourTypes) {
            if (categoryName.equalsIgnoreCase(mapTourType.getName())) {
                tourType = mapTourType;
                break;
            }
        }

        TourType newSavedTourType = null;

        if (tourType == null) {

            // create new tour type

            final TourType newTourType = new TourType(categoryName);

            final TourTypeColorDefinition newColorDefinition = new TourTypeColorDefinition(newTourType,
                    Long.toString(newTourType.getTypeId()), newTourType.getName());

            newTourType.setColorBright(newColorDefinition.getDefaultGradientBright());
            newTourType.setColorDark(newColorDefinition.getDefaultGradientDark());
            newTourType.setColorLine(newColorDefinition.getDefaultLineColor());

            // save new entity
            newSavedTourType = TourDatabase.saveEntity(newTourType, newTourType.getTypeId(), TourType.class);
            if (newSavedTourType != null) {
                tourType = newSavedTourType;
                _allTourTypes.add(tourType);
            }
        }

        tourData.setTourType(tourType);

        _isNewTourType |= newSavedTourType != null;
    }

    private void finalizeTour20SetTags(final TourData tourData) {

        final ArrayList<String> equipmentNames = _currentActivity.equipmentName;
        if (equipmentNames.size() == 0) {
            return;
        }

        boolean isNewTag = false;

        final Set<TourTag> tourTags = new HashSet<TourTag>();

        HashMap<Long, TourTag> tourTagMap = TourDatabase.getAllTourTags();
        TourTag[] allTourTags = tourTagMap.values().toArray(new TourTag[tourTagMap.size()]);

        try {

            for (final String tagLabel : equipmentNames) {

                boolean isTagAvailable = false;

                for (final TourTag tourTag : allTourTags) {
                    if (tourTag.getTagName().equals(tagLabel)) {

                        // existing tag is found

                        isTagAvailable = true;

                        tourTags.add(tourTag);
                        break;
                    }
                }

                if (isTagAvailable == false) {

                    // create a new tag

                    final TourTag tourTag = new TourTag(tagLabel);
                    tourTag.setRoot(true);

                    // persist tag
                    final TourTag savedTag = TourDatabase.saveEntity(tourTag, TourDatabase.ENTITY_IS_NOT_SAVED,
                            TourTag.class);

                    if (savedTag != null) {

                        tourTags.add(savedTag);

                        // reload tour tag list

                        TourDatabase.clearTourTags();

                        tourTagMap = TourDatabase.getAllTourTags();
                        allTourTags = tourTagMap.values().toArray(new TourTag[tourTagMap.size()]);

                        isNewTag = true;
                    }
                }
            }

        } catch (final NoSuchElementException e) {
            // no further tokens
        } finally {

            tourData.setTourTags(tourTags);
        }

        _isNewTag |= isNewTag;
    }

    private void finalizeTour30CreateMarkers(final TourData tourData) {

        final ArrayList<Lap> _laps = _currentActivity.laps;
        if (_laps.size() == 0) {
            return;
        }

        final int[] timeSerie = tourData.timeSerie;
        if (timeSerie == null || timeSerie.length == 0) {
            // fixed bug: http://sourceforge.net/support/tracker.php?aid=3232030
            return;
        }

        final Set<TourMarker> tourMarkers = tourData.getTourMarkers();
        final int[] distanceSerie = tourData.distanceSerie;

        /*
         * tour and track can have different start times
         */
        final long tourStartTime = _currentActivity.tourStartTimeMills;
        //      final long tour2sliceTimeDiff = _currentActivity.trackTourStartTime - tourStartTime;

        int lapCounter = 1;

        for (final Lap lap : _laps) {

            final long startTimeDiff = lap.lapStartTime - tourStartTime;// - tour2sliceTimeDiff;
            long lapRelativeTime = startTimeDiff / 1000;
            int serieIndex = 0;

            // get serie index
            for (final int tourRelativeTime : timeSerie) {
                if (tourRelativeTime >= lapRelativeTime) {
                    break;
                }
                serieIndex++;
            }

            if (lapRelativeTime < 0) {
                // this case occured
                lapRelativeTime = 0;
            }

            // check array bounds
            if (serieIndex >= timeSerie.length) {
                serieIndex = timeSerie.length - 1;
            }

            final TourMarker tourMarker = new TourMarker(tourData, ChartLabel.MARKER_TYPE_DEVICE);

            tourMarker.setLabel(Integer.toString(lapCounter));
            tourMarker.setSerieIndex(serieIndex);
            tourMarker.setTime((int) lapRelativeTime);
            tourMarker.setVisualPosition(ChartLabel.VISUAL_HORIZONTAL_ABOVE_GRAPH_CENTERED);

            if (distanceSerie != null) {
                tourMarker.setDistance(distanceSerie[serieIndex]);
            }

            tourMarkers.add(tourMarker);

            lapCounter++;
        }
    }

    private void initTour(final Attributes attributes) {

        _currentActivity = new Activity();

        _distanceAbsolute = 0;

        _prevLatitude = Double.MIN_VALUE;
        _prevLongitude = Double.MIN_VALUE;

        final String startTime = attributes.getValue(ATTRIB_START_TIME);
        if (startTime != null) {
            final DateTime tourDateTime = _dtParser.parseDateTime(startTime);
            _currentActivity.tourStartTime = tourDateTime;
            _currentActivity.tourStartTimeMills = tourDateTime.getMillis();
        }
    }

    public boolean isImported() {
        return _isImported;
    }

    public boolean isNewTag() {
        return _isNewTag;
    }

    public boolean isNewTourType() {
        return _isNewTourType;
    }

    private void parseActivity01Start(final String name, final Attributes attributes) {

        if (name.equals(TAG_ACTIVITY_NAME)) {

            _isInName = true;

        } else if (name.equals(TAG_ACTIVITY_NOTES)) {

            _isInNotes = true;

        } else if (name.equals(TAG_ACTIVITY_LOCATION)) {

            _currentActivity.location = attributes.getValue(ATTRIB_NAME);

        } else if (name.equals(TAG_ACTIVITY_CATEGORY)) {

            _currentActivity.categoryName = attributes.getValue(ATTRIB_NAME);
        } else if (name.equals(TAG_ACTIVITY_EQUIPMENT_ITEM)) {

            _currentActivity.equipmentName.add(attributes.getValue(ATTRIB_NAME));

        } else if (name.equals(TAG_ACTIVITY_CALORIES)) {

            //      <xs:element name="Calories">
            //         <xs:complexType>
            //            <xs:attribute name="TotalCal" type="xs:decimal" use="optional"/>
            //         </xs:complexType>
            //      </xs:element>
            _currentActivity.calories = Util.parseInt0(attributes, ATTRIB_TOTAL_CAL);

        } else if (name.equals(TAG_ACTIVITY_DURATION)) {

            //      <xs:element name="Duration">
            //         <xs:complexType>
            //            <xs:attribute name="TotalSeconds" type="xs:decimal" use="optional"/>
            //         </xs:complexType>
            //      </xs:element>
            _currentActivity.duration = Util.parseInt0(attributes, ATTRIB_TOTAL_SECONDS);

        } else if (name.equals(TAG_ACTIVITY_DISTANCE)) {

            //      <xs:element name="Distance">
            //         <xs:complexType>
            //            <xs:attribute name="TotalMeters" type="xs:decimal" use="optional"/>
            //         </xs:complexType>
            //      </xs:element>
            _currentActivity.distance = Util.parseInt0(attributes, ATTRIB_TOTAL_METERS);

        } else if (name.equals(TAG_ACTIVITY_ELEVATION)) {

            //      <xs:element name="Elevation">
            //         <xs:complexType>
            //            <xs:attribute name="DescendMeters" type="xs:decimal" use="optional"/>
            //            <xs:attribute name="AscendMeters" type="xs:decimal" use="optional"/>
            //         </xs:complexType>
            //      </xs:element>
            _currentActivity.elevationUp = Util.parseInt0(attributes, ATTRIB_ASCEND_METERS);
            _currentActivity.elevationDown = Util.parseInt0(attributes, ATTRIB_DESCEND_METERS);

        } else if (name.equals(TAG_ACTIVITY_HEART_RATE)) {

            //      <xs:element name="HeartRate">
            //         <xs:complexType>
            //            <xs:attribute name="AverageBPM" type="xs:decimal" use="optional"/>
            //            <xs:attribute name="MaximumBPM" type="xs:decimal" use="optional"/>
            //         </xs:complexType>
            //      </xs:element>
            _currentActivity.avgPulse = Util.parseInt0(attributes, ATTRIB_AVERAGE_BPM);
            _currentActivity.maxPulse = Util.parseInt0(attributes, ATTRIB_MAXIMUM_BPM);

        } else if (name.equals(TAG_ACTIVITY_CADENCE)) {

            //      <xs:element name="Cadence">
            //         <xs:complexType>
            //            <xs:attribute name="AverageRPM" type="xs:decimal" use="optional"/>
            //            <xs:attribute name="MaximumRPM" type="xs:decimal" use="optional "/>
            //         </xs:complexType>
            //      </xs:element>
            _currentActivity.avgCadence = Util.parseInt0(attributes, ATTRIB_AVERAGE_RPM);
            //   !!! not yet supported !!!
            //         _currentActivity.maxCadence = Util.parseInt0(attributes, ATTRIB_MAXIMUM_RPM);

        } else if (name.equals(TAG_ACTIVITY_WEATHER)) {

            _isInWeather = true;
            _currentActivity.weatherTemperature = Util.parseFloat(attributes, ATTRIB_WEATHER_TEMP);
            _currentActivity.weatherConditions = attributes.getValue(ATTRIB_WEATHER_CONDITIONS);

        } else {
            return;
        }

        _characters.delete(0, _characters.length());
    }

    private void parseActivity02End(final String name) {

        if (_isInName) {

            _isInName = false;
            _currentActivity.name = _characters.toString();

        } else if (_isInNotes) {

            _isInNotes = false;
            _currentActivity.notes = _characters.toString();

        } else if (_isInWeather) {

            _isInWeather = false;
            _currentActivity.weatherText = _characters.toString();
        }
    }

    private void parseLaps(final String name, final Attributes attributes) {

        if (name.equals(TAG_LAP)) {

            final String startTime = attributes.getValue(ATTRIB_START_TIME);

            if (startTime != null) {

                final Lap lap = new Lap();

                lap.lapStartTime = _dtParser.parseDateTime(startTime).getMillis();

                _currentActivity.laps.add(lap);
            }
        }
    }

    //   private void parseTrack(final Attributes attributes) {
    //
    //      final String startTime = attributes.getValue(ATTRIB_START_TIME);
    //
    //      if (startTime != null) {
    //         _currentActivity.trackTourDateTime = _dtParser.parseDateTime(startTime);
    //         _currentActivity.trackTourStartTime = _currentActivity.trackTourDateTime.getMillis();
    //      }
    //   }

    private void parseTrackPoints(final String name, final Attributes attributes) throws InvalidDeviceSAXException {

        if (name.equals(TAG_TRACK_PT)) {

            if (_currentActivity.tourStartTimeMills == Long.MIN_VALUE) {
                throw new InvalidDeviceSAXException(
                        NLS.bind(Messages.FitLog_Error_InvalidStartTime, _importFilePath));
            }

            final TimeData timeSlice = new TimeData();

            // relative time in seconds
            final long tmValue = Util.parseLong(attributes, ATTRIB_PT_TM);
            if (tmValue != Long.MIN_VALUE) {
                timeSlice.absoluteTime = _currentActivity.tourStartTimeMills + (tmValue * 1000);
            }

            final double tpDistance = Util.parseDouble(attributes, ATTRIB_PT_DIST);
            final double latitude = Util.parseDouble(attributes, ATTRIB_PT_LAT);
            final double longitude = Util.parseDouble(attributes, ATTRIB_PT_LON);

            if (tpDistance != Double.MIN_VALUE) {
                _distanceAbsolute = tpDistance;
            } else if (tpDistance == Double.MIN_VALUE && latitude != Double.MIN_VALUE
                    && longitude != Double.MIN_VALUE && _prevLatitude != Double.MIN_VALUE
                    && _prevLongitude != Double.MIN_VALUE) {

                // get distance from lat/lon when it's not set
                _distanceAbsolute += Util.distanceVincenty(_prevLatitude, _prevLongitude, latitude, longitude);
            }

            if (latitude != Double.MIN_VALUE && longitude != Double.MIN_VALUE) {
                _prevLatitude = latitude;
                _prevLongitude = longitude;
            }

            timeSlice.absoluteDistance = (float) _distanceAbsolute;
            timeSlice.absoluteAltitude = Util.parseFloat(attributes, ATTRIB_PT_ELE);
            timeSlice.cadence = Util.parseInt(attributes, ATTRIB_PT_CADENCE);
            timeSlice.pulse = Util.parseInt(attributes, ATTRIB_PT_HR);
            timeSlice.power = Util.parseInt(attributes, ATTRIB_PT_POWER);
            timeSlice.latitude = latitude;
            timeSlice.longitude = longitude;

            _currentActivity.timeSlices.add(timeSlice);
        }
    }

    @Override
    public void startElement(final String uri, final String localName, final String name,
            final Attributes attributes) throws SAXException {

        if (_isInActivity) {

            if (_isInTrack) {
                parseTrackPoints(name, attributes);
            } else if (_isInLaps) {
                parseLaps(name, attributes);
            } else {
                parseActivity01Start(name, attributes);
            }
        }

        if (name.equals(TAG_TRACK)) {

            _isInTrack = true;

            //         parseTrack(attributes);

        } else if (name.equals(TAG_LAPS)) {

            _isInLaps = true;

        } else if (name.equals(TAG_ACTIVITY)) {

            /*
             * a new exercise/tour starts
             */

            _isInActivity = true;

            initTour(attributes);
        }
    }

}