forked from upstream-mirrors/OpenTracks
Fix TCX export
This commit is contained in:
@@ -17,10 +17,11 @@ package com.google.android.apps.mytracks.io;
|
|||||||
|
|
||||||
import com.google.android.apps.mytracks.content.MyTracksLocation;
|
import com.google.android.apps.mytracks.content.MyTracksLocation;
|
||||||
import com.google.android.apps.mytracks.content.Sensor;
|
import com.google.android.apps.mytracks.content.Sensor;
|
||||||
|
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
|
||||||
import com.google.android.apps.mytracks.content.Track;
|
import com.google.android.apps.mytracks.content.Track;
|
||||||
import com.google.android.apps.mytracks.content.Waypoint;
|
import com.google.android.apps.mytracks.content.Waypoint;
|
||||||
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
|
|
||||||
import com.google.android.apps.mytracks.io.TrackWriterFactory.TrackFileFormat;
|
import com.google.android.apps.mytracks.io.TrackWriterFactory.TrackFileFormat;
|
||||||
|
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
||||||
|
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
@@ -30,6 +31,7 @@ import java.io.PrintWriter;
|
|||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -37,20 +39,32 @@ import java.util.TimeZone;
|
|||||||
* As defined by:
|
* As defined by:
|
||||||
* http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2
|
* http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2
|
||||||
*
|
*
|
||||||
|
* The TCX file written by this class has been verified as compatible with
|
||||||
|
* Garmin Training Center 3.5.3.
|
||||||
|
*
|
||||||
* @author Sandor Dornbush
|
* @author Sandor Dornbush
|
||||||
*/
|
*/
|
||||||
public class TcxTrackWriter implements TrackFormatWriter {
|
public class TcxTrackWriter implements TrackFormatWriter {
|
||||||
|
protected static final String TIMESTAMP_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
|
||||||
|
|
||||||
|
// These are the only sports allowed by the TCX v2 specification for fields
|
||||||
|
// of type Sport_t.
|
||||||
|
private static final String TCX_SPORT_BIKING = "Biking";
|
||||||
|
private static final String TCX_SPORT_RUNNING = "Running";
|
||||||
|
private static final String TCX_SPORT_OTHER = "Other";
|
||||||
|
|
||||||
|
// Values for fields of type Build_t/Type.
|
||||||
|
private static final String TCX_TYPE_RELEASE = "Release";
|
||||||
|
private static final String TCX_TYPE_INTERNAL = "Internal";
|
||||||
|
|
||||||
|
private final SimpleDateFormat timestampFormatter;
|
||||||
|
|
||||||
private PrintWriter pw = null;
|
private PrintWriter pw = null;
|
||||||
private Track track;
|
private Track track;
|
||||||
|
|
||||||
static final SimpleDateFormat TIMESTAMP_FORMAT =
|
|
||||||
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
|
|
||||||
static {
|
|
||||||
TIMESTAMP_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public TcxTrackWriter() {
|
public TcxTrackWriter() {
|
||||||
|
timestampFormatter = new SimpleDateFormat(TIMESTAMP_FORMAT);
|
||||||
|
timestampFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -93,16 +107,13 @@ public class TcxTrackWriter implements TrackFormatWriter {
|
|||||||
if (pw == null) {
|
if (pw == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String startTime = timestampFormatter.format(track.getStatistics().getStartTime());
|
||||||
|
|
||||||
pw.println(" <Activities>");
|
pw.println(" <Activities>");
|
||||||
pw.print(" <Activity Sport=\"");
|
pw.format(" <Activity Sport=\"%s\">\n", categoryToTcxSport(track.getCategory()));
|
||||||
if (track.getCategory() != null) {
|
pw.format(" <Id>%s</Id>\n", startTime);
|
||||||
pw.print(track.getCategory());
|
pw.format(" <Lap StartTime=\"%s\">\n", startTime);
|
||||||
}
|
|
||||||
pw.println("\">");
|
|
||||||
pw.print(" <Id>");
|
|
||||||
pw.print(TIMESTAMP_FORMAT.format(track.getStatistics().getStartTime()));
|
|
||||||
pw.println("</Id>");
|
|
||||||
pw.println(" <Lap>");
|
|
||||||
pw.print(" <TotalTimeSeconds>");
|
pw.print(" <TotalTimeSeconds>");
|
||||||
pw.print(track.getStatistics().getTotalTime() / 1000);
|
pw.print(track.getStatistics().getTotalTime() / 1000);
|
||||||
pw.println("</TotalTimeSeconds>");
|
pw.println("</TotalTimeSeconds>");
|
||||||
@@ -113,7 +124,7 @@ public class TcxTrackWriter implements TrackFormatWriter {
|
|||||||
// Calories are a required element just put in 0.
|
// Calories are a required element just put in 0.
|
||||||
pw.print("<Calories>0</Calories>");
|
pw.print("<Calories>0</Calories>");
|
||||||
pw.println("<Intensity>Active</Intensity>");
|
pw.println("<Intensity>Active</Intensity>");
|
||||||
pw.println("<TriggerMethod>Manual</TriggerMethod>)");
|
pw.println("<TriggerMethod>Manual</TriggerMethod>");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -130,7 +141,7 @@ public class TcxTrackWriter implements TrackFormatWriter {
|
|||||||
}
|
}
|
||||||
pw.println(" <Trackpoint>");
|
pw.println(" <Trackpoint>");
|
||||||
Date d = new Date(location.getTime());
|
Date d = new Date(location.getTime());
|
||||||
pw.println(" <Time>" + TIMESTAMP_FORMAT.format(d) + "</Time>");
|
pw.println(" <Time>" + timestampFormatter.format(d) + "</Time>");
|
||||||
pw.println(" <Position>");
|
pw.println(" <Position>");
|
||||||
|
|
||||||
pw.print(" <LatitudeDegrees>");
|
pw.print(" <LatitudeDegrees>");
|
||||||
@@ -194,10 +205,18 @@ public class TcxTrackWriter implements TrackFormatWriter {
|
|||||||
}
|
}
|
||||||
pw.println(" </Lap>");
|
pw.println(" </Lap>");
|
||||||
pw.print(" <Creator xsi:type=\"Device_t\">");
|
pw.print(" <Creator xsi:type=\"Device_t\">");
|
||||||
pw.print("<Name>");
|
pw.format("<Name>%s</Name>\n", Build.MODEL);
|
||||||
pw.print(Build.MODEL);
|
|
||||||
pw.print("</Name>");
|
// The following code is correct. ID is inconsistently capitalized in the
|
||||||
pw.println("</Creator>)");
|
// TCX schema.
|
||||||
|
pw.println("<UnitId>0</UnitId>");
|
||||||
|
pw.println("<ProductID>0</ProductID>");
|
||||||
|
|
||||||
|
pw.println("<Version>");
|
||||||
|
pw.println("<VersionMajor>0</VersionMajor>");
|
||||||
|
pw.println("<VersionMinor>0</VersionMinor>");
|
||||||
|
pw.println("</Version>");
|
||||||
|
pw.println("</Creator>");
|
||||||
pw.println(" </Activity>");
|
pw.println(" </Activity>");
|
||||||
pw.println(" </Activities>");
|
pw.println(" </Activities>");
|
||||||
}
|
}
|
||||||
@@ -207,8 +226,24 @@ public class TcxTrackWriter implements TrackFormatWriter {
|
|||||||
if (pw == null) {
|
if (pw == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
pw.print(" <Author xsi:type=\"Application_t\">");
|
pw.println(" <Author xsi:type=\"Application_t\">");
|
||||||
pw.print("<Name>My Tracks by Google</Name>");
|
|
||||||
|
// We put the version in the name because there isn't a better place for
|
||||||
|
// it. The TCX schema tightly defined the Version tag, so we can't put it
|
||||||
|
// there. They've similarly constrained the PartNumber tag, so it can't go
|
||||||
|
// there either.
|
||||||
|
pw.format("<Name>My Tracks %s by Google</Name>\n", MyTracksUtils.getMyTracksVersion());
|
||||||
|
|
||||||
|
pw.println("<Build>");
|
||||||
|
pw.println("<Version>");
|
||||||
|
pw.format("<VersionMajor>%d</VersionMajor>\n", MyTracksUtils.getMyTracksVersionCode());
|
||||||
|
pw.println("<VersionMinor>0</VersionMinor>");
|
||||||
|
pw.println("</Version>");
|
||||||
|
pw.format("<Type>%s</Type>\n", MyTracksUtils.isRelease() ? TCX_TYPE_RELEASE
|
||||||
|
: TCX_TYPE_INTERNAL);
|
||||||
|
pw.println("</Build>");
|
||||||
|
pw.format("<LangID>%s</LangID>\n", Locale.getDefault().getLanguage());
|
||||||
|
pw.println("<PartNumber>000-00000-00</PartNumber>");
|
||||||
pw.println("</Author>");
|
pw.println("</Author>");
|
||||||
pw.println("</TrainingCenterDatabase>");
|
pw.println("</TrainingCenterDatabase>");
|
||||||
}
|
}
|
||||||
@@ -217,4 +252,14 @@ public class TcxTrackWriter implements TrackFormatWriter {
|
|||||||
public void writeWaypoint(Waypoint waypoint) {
|
public void writeWaypoint(Waypoint waypoint) {
|
||||||
// TODO Write out the waypoints somewhere.
|
// TODO Write out the waypoints somewhere.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String categoryToTcxSport(String category) {
|
||||||
|
if (category.equalsIgnoreCase(TCX_SPORT_RUNNING)) {
|
||||||
|
return TCX_SPORT_RUNNING;
|
||||||
|
} else if (category.equalsIgnoreCase(TCX_SPORT_BIKING)) {
|
||||||
|
return TCX_SPORT_BIKING;
|
||||||
|
} else {
|
||||||
|
return TCX_SPORT_OTHER;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,10 +15,10 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.apps.mytracks.util;
|
package com.google.android.apps.mytracks.util;
|
||||||
|
|
||||||
|
import com.google.android.apps.mytracks.MyTracks;
|
||||||
import com.google.android.apps.mytracks.MyTracksConstants;
|
import com.google.android.apps.mytracks.MyTracksConstants;
|
||||||
import com.google.android.apps.mytracks.content.Track;
|
import com.google.android.apps.mytracks.content.Track;
|
||||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||||
|
|
||||||
import com.google.android.maps.GeoPoint;
|
import com.google.android.maps.GeoPoint;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
@@ -259,34 +259,66 @@ public class MyTracksUtils {
|
|||||||
* Returns whether or not this is a release build.
|
* Returns whether or not this is a release build.
|
||||||
*/
|
*/
|
||||||
public static boolean isRelease(Context context) {
|
public static boolean isRelease(Context context) {
|
||||||
try {
|
PackageInfo packageInfo = getPackageInfo(context, PackageManager.GET_SIGNATURES);
|
||||||
Signature [] sigs = context.getPackageManager().getPackageInfo(
|
if (packageInfo == null) {
|
||||||
context.getPackageName(), PackageManager.GET_SIGNATURES).signatures;
|
return false;
|
||||||
for (Signature sig : sigs) {
|
|
||||||
if (sig.hashCode() == RELEASE_SIGNATURE_HASHCODE) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (NameNotFoundException e) {
|
|
||||||
Log.e(MyTracksConstants.TAG, "Unable to get signatures", e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (Signature sig: packageInfo.signatures) {
|
||||||
|
if (sig.hashCode() == RELEASE_SIGNATURE_HASHCODE) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see isRelease(Context)
|
||||||
|
*/
|
||||||
|
public static boolean isRelease() {
|
||||||
|
return isRelease(MyTracks.getInstance());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the My Tracks version from the manifest.
|
* Get the My Tracks version from the manifest.
|
||||||
*
|
*
|
||||||
* @return the version, or an empty string in case of failure.
|
* @return the version, or an empty string in case of failure.
|
||||||
*/
|
*/
|
||||||
public static String getMyTracksVersion(Context context) {
|
public static String getMyTracksVersion(Context context) {
|
||||||
|
PackageInfo packageInfo = getPackageInfo(context, PackageManager.GET_META_DATA);
|
||||||
|
return packageInfo == null ? "" : packageInfo.versionName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see getMyTracksVersion(Context)
|
||||||
|
*/
|
||||||
|
public static String getMyTracksVersion() {
|
||||||
|
return getMyTracksVersion(MyTracks.getInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the My Tracks version code from the manifest.
|
||||||
|
*
|
||||||
|
* @return the version code, or an empty string in case of failure.
|
||||||
|
*/
|
||||||
|
public static int getMyTracksVersionCode() {
|
||||||
|
PackageInfo packageInfo = getPackageInfo(MyTracks.getInstance(),
|
||||||
|
PackageManager.GET_META_DATA);
|
||||||
|
return packageInfo == null ? -1 : packageInfo.versionCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PackageInfo getPackageInfo(Context context, int flags) {
|
||||||
|
if (context == null) {
|
||||||
|
Log.w(MyTracksConstants.TAG, "No context found when attempting to get version");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
PackageInfo pi = context.getPackageManager().getPackageInfo(
|
return context.getPackageManager().getPackageInfo(
|
||||||
"com.google.android.maps.mytracks",
|
"com.google.android.maps.mytracks", flags);
|
||||||
PackageManager.GET_META_DATA);
|
|
||||||
return pi.versionName;
|
|
||||||
} catch (NameNotFoundException e) {
|
} catch (NameNotFoundException e) {
|
||||||
Log.w(MyTracksConstants.TAG, "Failed to get version info.", e);
|
Log.w(MyTracksConstants.TAG, "Failed to get version info.", e);
|
||||||
return "";
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,18 +4,29 @@ package com.google.android.apps.mytracks.io;
|
|||||||
import com.google.android.apps.mytracks.content.MyTracksLocation;
|
import com.google.android.apps.mytracks.content.MyTracksLocation;
|
||||||
import com.google.android.apps.mytracks.content.Sensor;
|
import com.google.android.apps.mytracks.content.Sensor;
|
||||||
|
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
import org.w3c.dom.Element;
|
import org.w3c.dom.Element;
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for the GPX track exporter.
|
* Tests for the GPX track exporter.
|
||||||
*
|
*
|
||||||
* @author Sandor Dornbush
|
* @author Sandor Dornbush
|
||||||
*/
|
*/
|
||||||
public class TcxTrackWriterTest extends TrackFormatWriterTest {
|
public class TcxTrackWriterTest extends TrackFormatWriterTest {
|
||||||
|
private DateFormat timestampFormatter;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
super.setUp();
|
||||||
|
|
||||||
|
timestampFormatter = new SimpleDateFormat(TcxTrackWriter.TIMESTAMP_FORMAT);
|
||||||
|
timestampFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||||
|
}
|
||||||
|
|
||||||
public void testXmlOutput() throws Exception {
|
public void testXmlOutput() throws Exception {
|
||||||
TrackFormatWriter writer = new TcxTrackWriter();
|
TrackFormatWriter writer = new TcxTrackWriter();
|
||||||
@@ -58,8 +69,8 @@ public class TcxTrackWriterTest extends TrackFormatWriterTest {
|
|||||||
getChildTextValue(posTag, "LatitudeDegrees"));
|
getChildTextValue(posTag, "LatitudeDegrees"));
|
||||||
assertEquals(Double.toString(loc.getLongitude()),
|
assertEquals(Double.toString(loc.getLongitude()),
|
||||||
getChildTextValue(posTag, "LongitudeDegrees"));
|
getChildTextValue(posTag, "LongitudeDegrees"));
|
||||||
assertEquals(
|
|
||||||
TcxTrackWriter.TIMESTAMP_FORMAT.format(new Date(loc.getTime())),
|
assertEquals(timestampFormatter.format(loc.getTime()),
|
||||||
getChildTextValue(tag, "Time"));
|
getChildTextValue(tag, "Time"));
|
||||||
assertEquals(Double.toString(loc.getAltitude()),
|
assertEquals(Double.toString(loc.getAltitude()),
|
||||||
getChildTextValue(tag, "AltitudeMeters"));
|
getChildTextValue(tag, "AltitudeMeters"));
|
||||||
|
|||||||
Reference in New Issue
Block a user