/* 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.io.FileInputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.TimeZone;
import javax.xml.bind.DatatypeConverter;
import com.garmin.fit.DateTime;
import com.garmin.fit.Decode;
import com.garmin.fit.Field;
import com.garmin.fit.Mesg;
import com.garmin.fit.MesgBroadcaster;
import com.garmin.fit.MesgDefinition;
import com.garmin.fit.MesgDefinitionListener;
import com.garmin.fit.MesgListener;
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 Garmin FIT format files into WattzAp
*
* @author David George
* @date 22nd May 2014
*/
public class FitImporter implements MesgListener, MesgDefinitionListener {
ArrayList<Telemetry> data = new ArrayList<Telemetry>();
Telemetry last = null;
double totalDistance = 0;
private final UserPreferences userPrefs = UserPreferences.INSTANCE;
public static final long OFFSET = 631065600000l; // Offset between Garmin
// (FIT) time and Unix
// time in ms (Dec 31,
// 1989 - 00:00:00
// January 1, 1970).
private boolean isPower = false;
Rolling gAve = new Rolling(30);
public FitImporter(String fileName) {
FileInputStream fitFile = null;
try {
Decode decode = new Decode();
MesgBroadcaster broadcaster = new MesgBroadcaster(decode);
decode.addListener((MesgDefinitionListener) this);
decode.addListener((MesgListener) this);
fitFile = new FileInputStream(fileName);
broadcaster.run(fitFile);
// calculate powers here???
if (!isPower) {
for (Telemetry point : data) {
int p = (int) Power.getPower(userPrefs.getTotalWeight(),
point.getGradient(), point.getSpeedKMH());
if (p > userPrefs.getMaxPower()
&& (p > (last.getPower() * 2))) {
// 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) {
//point.setResistance(p);
point.setPower(p);
} else {
//point.setResistance(0);
point.setPower(0);
}
}// for
}
/* else {
double powerSum1 = 0;
double powerSum2 = 0;
double stdev = 0;
int i = 0;
for (Telemetry point : data) {
double p = point.getPower();
powerSum1 += p;
powerSum2 += Math.pow(p, 2);
stdev = Math.sqrt(i*powerSum2 - Math.pow(powerSum1, 2))/i;
if (p>300)
System.out.println(">>" + p + " stdev " + stdev);
else
System.out.println("" + p + " stdev " + stdev);
i++;
}// for
}*/
} catch (Exception fex) {
try {
fitFile.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
fex.printStackTrace();
} finally {
if (fitFile != null) {
try {
fitFile.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
@Override
public void onMesgDefinition(MesgDefinition arg0) {
// TODO Auto-generated method stub
}
@Override
public void onMesg(Mesg mesg) {
if ((mesg != null) && ("lap".equals(mesg.getName()))) {
// System.out.println("got here");
} else if ((mesg != null) && ("record".equals(mesg.getName()))) {
Telemetry point = new Telemetry();
// Search relevant info
Collection<Field> fields = mesg.getFields();
for (Field f : fields) {
f.getName();
}
Field time = getField("timestamp", fields);
Field cadence = getField("cadence", fields);
if (cadence != null) {
point.setCadence(cadence.getByteValue());
}
Field hr = getField("heart_rate", fields);
if (hr != null) {
point.setHeartRate(hr.getIntegerValue());
}
Field posLat = getField("position_lat", fields);
Field posLon = getField("position_long", fields);
// Convert semicircles latitude to decimals
if (posLat != null) {
BigDecimal posLatDec = semicircleToDms(posLat);
point.setLatitude(posLatDec.doubleValue());
}
if (posLon != null) {
BigDecimal posLonDec = semicircleToDms(posLon);
point.setLongitude(posLonDec.doubleValue());
}
Field elevation = getField("altitude", fields);
if (elevation != null) {
point.setElevation(elevation.getDoubleValue());
}
Field distance = getField("distance", fields);
if (distance != null) {
point.setDistanceMeters(distance.getDoubleValue());
}
if (time != null) {
point.setTime(time.getLongValue() * 1000 + OFFSET);
}
Field speed = getField("speed", fields);
if (speed != null) {
point.setSpeed(speed.getDoubleValue() * 3.6);
}
// use power from file
Field power = getField("power", fields);
if (power != null) {
point.setPower(power.getShortValue());
if (point.getPower() > 0) {
isPower = true; // contains power values
}
}
if (last != null) {
if (distance == null) {
if (posLat == null || posLon == null) {
// no latitude or longitude, drop
// point
return;
}
// calculate distance from GPS points
double d = GPXReader.distance(point.getLatitude(),
last.getLatitude(), point.getLongitude(),
last.getLongitude(), point.getElevation(),
last.getElevation());
if (d > 1000) {
// large value, drop.
return;
}
totalDistance += d;
point.setDistanceMeters(totalDistance);
} else if (point.getDistanceMeters() == last.getDistanceMeters()) {
// no change to distance, drop point
return;
}
if (speed == null) {
// calculate speed, s = d / t
// double speed = d * 3600
// / ((point.getTime() - adjust) - last.getTime());
}
double gradient = (point.getElevation() - last.getElevation())
/ (point.getDistanceMeters() - last.getDistanceMeters());
point.setGradient(gAve.add(gradient));
} else {
// first time through
point.setResistance(WorkoutData.FIT);
}
last = point;
data.add(point);
}
}
/*
* Search the field with name fieldName in the list of fields.
*/
private Field getField(String fieldName, Collection<Field> fields) {
for (Iterator<Field> iterator = fields.iterator(); iterator.hasNext();) {
Field field = (Field) iterator.next();
if (fieldName.equals(field.getName()))
return field;
}
return null; // not found
}
/*
* Convert a fit field coördinate to a GPX coördinate. It will only convert
* fit fields with unit "semicircles".
*
* dms=semicircles*(180/2^31)
*/
public static BigDecimal semicircleToDms(Field field) {
BigDecimal dms = null;
if ((field != null) && ("semicircles".equals(field.getUnits()))) {
dms = semicircleToDms(field.getLongValue());
}
return dms; // 42.059009616
}
private static final BigDecimal MULTIPLICANT = new BigDecimal(
180.0d / Math.pow(2L, 31L));
/**
* Convert a semicircles coördinate (lattitude or longitude) value to a
* degrees value using the formula : dms=semicircles*(180/2^31)
*
* s * (180.0D / 2^31)
*
*
* @see http
* ://www.gps-forums.net/accuracy-converting-semicircles-degrees-t31488
* .html
*/
public static BigDecimal semicircleToDms(long semicircle) {
BigDecimal dms = new BigDecimal(semicircle).multiply(MULTIPLICANT);
return dms;
}
/**
* Convert the fit time field to the gpx time format (standard UTC time).
* Will only convert "s" seconds.
*
* @param time
* @return
*/
public static String convertTime(Field time) {
String result = null;
if ((time != null) && ("s".equals(time.getUnits()))) {
DateTime dateTime = new DateTime(time.getLongValue());
result = convertTime(dateTime.getDate());
}
return result;
}
public static String convertTime(Date date) {
TimeZone zone = TimeZone.getTimeZone("UTC");
Calendar cal = Calendar.getInstance(zone);
cal.setTime(date);
return DatatypeConverter.printDateTime(cal);
}
}