/* This file is part of Wattzap Community Edition. * * Wattzap Community Edtion 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. * * Wattzap Community Edition 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 Wattzap. If not, see <http://www.gnu.org/licenses/>. */ package com.wattzap.utils; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.xml.sax.Attributes; import org.xml.sax.helpers.DefaultHandler; import com.wattzap.model.GPXReader; import com.wattzap.model.UserPreferences; import com.wattzap.model.dto.Telemetry; import com.wattzap.model.dto.WorkoutData; import com.wattzap.model.power.Power; /** * Import external GPX data, for example from a smartphone or GPS. * * The format is pretty simple for our purposes. We are principally interested * in track points. * * @author David George * @date 2nd May 2014 */ public class GpxImporter extends DefaultHandler { State currentState = State.UNDEFINED; StringBuilder buffer = new StringBuilder(); // GPX files have two data formats private static final SimpleDateFormat msdateFormat = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); private final SimpleDateFormat timestampFormatter = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss'Z'"); ArrayList<Telemetry> data; Telemetry point; double distance; Rolling rSpeed = new Rolling(30); Rolling gAve = new Rolling(30); LowPassFilter powerFilter = new LowPassFilter(2.0); long firstTime = 0; int index = 0; long adjust = 0; int tzOffset = 0; private final UserPreferences userPrefs = UserPreferences.INSTANCE; private static Logger logger = LogManager.getLogger("GPX Importer"); public GpxImporter() { super(); currentState = State.UNDEFINED; data = new ArrayList<Telemetry>(); distance = 0; tzOffset = Calendar.getInstance().getTimeZone().getRawOffset() + Calendar.getInstance().getTimeZone().getDSTSavings(); } public void startElement(String uri, String name, String qName, Attributes atts) { if (currentState == State.UNDEFINED) { if ("trkpt".equalsIgnoreCase(name)) { currentState = State.TRKPT; point = new Telemetry(); point.setLatitude(Double.parseDouble(atts.getValue("lat"))); point.setLongitude(Double.parseDouble(atts.getValue("lon"))); } } else if (currentState == State.TRKPT) { if ("extensions".equalsIgnoreCase(name)) { currentState = State.EXTENSION; } } if (buffer.length() > 0) { buffer = new StringBuilder(); } } public void endElement(String uri, String name, String qName) { if (currentState == State.TRKPT) { if ("time".equalsIgnoreCase(name)) { try { String t = buffer.toString().trim(); if (t.length() == 20) { // there are two different formats of time stamp Date d = timestampFormatter.parse(t); point.setTime(d.getTime() + tzOffset); } else if (t.length() == 24) { Date d = msdateFormat.parse(t); point.setTime(d.getTime() + tzOffset); } } catch (ParseException e) { logger.error(e + " " + buffer); } } else if ("ele".equalsIgnoreCase(name)) { point.setElevation(gAve.add(Double.parseDouble(buffer .toString()))); } else if ("trkpt".equalsIgnoreCase(name)) { index++; int current = data.size(); if (current > 0) { Telemetry last = data.get(current - 1); double d = GPXReader.distance(point.getLatitude(), last.getLatitude(), point.getLongitude(), last.getLongitude(), point.getElevation(), last.getElevation()); // calculate speed, s = d / t double speed = d * 3600 / ((point.getTime() - adjust) - last.getTime()); // TODO only do this if a FITLOG file is present if (speed < 0.5) { // less than 0.5km/h - we're stopped, remove point adjust += ((point.getTime() - adjust) - last.getTime()); currentState = State.UNDEFINED; return; // drop this point } point.setTime(point.getTime() - adjust); speed = rSpeed.add(speed); double gradient = (point.getElevation() - last .getElevation()) / d; int p = (int) Power.getPower(userPrefs.getTotalWeight(), gradient, speed); // filter spikes p = (int) powerFilter.add(p); if (p > userPrefs.getMaxPower() && (p > (last.getPower() * 2.0))) { // We are above FTP and power has doubled, remove power // spikes p = (int) (last.getPower() * 1.05); } if (p > (userPrefs.getMaxPower() * 4)) { // power is 4 x FTP, this is a spike p = last.getPower(); } if (p > 0) { // only set power if it is greater than zero point.setPower(p); } else { point.setPower(0); } point.setSpeed(speed); distance += d; point.setDistanceMeters(distance); point.setGradient((gradient) * 100); } else { point.setDistanceMeters(0); point.setResistance(WorkoutData.GPS); firstTime = point.getTime(); } data.add(point); currentState = State.UNDEFINED; } } else if (currentState == State.EXTENSION) { // System.out.println(name); if ("extensions".equalsIgnoreCase(name)) { currentState = State.TRKPT; } else if ("atemp".equalsIgnoreCase(name)) { System.out.println("temp " + buffer); } else if ("hr".equalsIgnoreCase(name)) { point.setHeartRate(Integer.parseInt(buffer .toString())); } else if ("cad".equalsIgnoreCase(name)) { point.setCadence(Integer.parseInt(buffer .toString())); } } } public void characters(char ch[], int start, int length) { if (buffer != null) { buffer.append(ch, start, length); } } public enum State { TRKPT, EXTENSION, UNDEFINED } }