/* * Copyright 2010 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.google.android.apps.mytracks.io; import com.google.android.apps.mytracks.content.MyTracksLocation; 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.Waypoint; import com.google.android.apps.mytracks.io.TrackWriterFactory.TrackFileFormat; import com.google.android.apps.mytracks.util.MyTracksUtils; import android.content.Context; import android.location.Location; import android.os.Build; import java.io.OutputStream; import java.io.PrintWriter; import java.nio.charset.Charset; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.TimeZone; /** * Write out a a track in the Garmin training center database, tcx format. * As defined by: * 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 */ 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 final Context context; private PrintWriter pw = null; private Track track; public TcxTrackWriter(Context context) { this.context = context; timestampFormatter = new SimpleDateFormat(TIMESTAMP_FORMAT); timestampFormatter.setTimeZone(TimeZone.getTimeZone("UTC")); } @Override public void prepare(Track track, OutputStream out) { this.track = track; this.pw = new PrintWriter(out); } @Override public void close() { if (pw != null) { pw.close(); pw = null; } } @Override public String getExtension() { return TrackFileFormat.TCX.getExtension(); } @Override public void writeHeader() { if (pw == null) { return; } pw.format("\n", Charset.defaultCharset().name()); pw.print(""); pw.println(); } @Override public void writeBeginTrack(Location firstPoint) { if (pw == null) { return; } String startTime = timestampFormatter.format(track.getStatistics().getStartTime()); pw.println(" "); pw.format(" \n", categoryToTcxSport(track.getCategory())); pw.format(" %s\n", startTime); pw.format(" \n", startTime); pw.print(" "); pw.print(track.getStatistics().getTotalTime() / 1000); pw.println(""); pw.print(" "); pw.print(track.getStatistics().getTotalDistance()); pw.println(""); // TODO max speed etc. // Calories are a required element just put in 0. pw.print("0"); pw.println("Active"); pw.println("Manual"); } @Override public void writeOpenSegment() { if (pw != null) { pw.println(" "); } } @Override public void writeLocation(Location location) { if (pw == null) { return; } pw.println(" "); Date d = new Date(location.getTime()); pw.println(" "); pw.println(" "); pw.print(" "); pw.print(location.getLatitude()); pw.println(""); pw.print(" "); pw.print(location.getLongitude()); pw.println(""); pw.println(" "); pw.print(" "); pw.print(location.getAltitude()); pw.println(""); if (location instanceof MyTracksLocation) { SensorDataSet sensorData = ((MyTracksLocation) location).getSensorDataSet(); if (sensorData != null) { if (sensorData.hasHeartRate() && sensorData.getHeartRate().getState() == Sensor.SensorState.SENDING && sensorData.getHeartRate().hasValue()) { pw.print(" "); pw.print(""); pw.print(sensorData.getHeartRate().getValue()); pw.print(""); pw.println(""); } if (sensorData.hasPower() && sensorData.getPower().getState() == Sensor.SensorState.SENDING && sensorData.getPower().hasValue()) { pw.print(" "); pw.print(""); pw.print(""); pw.print(sensorData.getPower().getValue()); pw.print(""); pw.println(""); } if (sensorData.hasCadence() && sensorData.getCadence().getState() == Sensor.SensorState.SENDING && sensorData.getCadence().hasValue()) { pw.print(" "); pw.print(sensorData.getCadence().getValue()); pw.println(""); } } } pw.println(" "); } @Override public void writeCloseSegment() { if (pw != null) { pw.println(" "); } } @Override public void writeEndTrack(Location lastPoint) { if (pw == null) { return; } pw.println(" "); pw.print(" "); pw.format("%s\n", Build.MODEL); // The following code is correct. ID is inconsistently capitalized in the // TCX schema. pw.println("0"); pw.println("0"); pw.println(""); pw.println("0"); pw.println("0"); pw.println(""); pw.println(""); pw.println(" "); pw.println(" "); } @Override public void writeFooter() { if (pw == null) { return; } pw.println(" "); // 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("My Tracks %s by Google\n", MyTracksUtils.getMyTracksVersion(context)); pw.println(""); pw.println(""); pw.format("%d\n", MyTracksUtils.getMyTracksVersionCode(context)); pw.println("0"); pw.println(""); pw.format("%s\n", MyTracksUtils.isRelease(context) ? TCX_TYPE_RELEASE : TCX_TYPE_INTERNAL); pw.println(""); pw.format("%s\n", Locale.getDefault().getLanguage()); pw.println("000-00000-00"); pw.println(""); pw.println(""); } @Override public void writeWaypoint(Waypoint waypoint) { // TODO Write out the waypoints somewhere. } private String categoryToTcxSport(String category) { category = category.trim(); 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; } } }