Code cleanup: cleanup CalorieUtils.java

This commit is contained in:
Jimmy Shih
2013-11-04 16:51:59 -08:00
parent d96c705953
commit 17428e1e94
5 changed files with 250 additions and 284 deletions
@@ -182,32 +182,34 @@ public class TrackEditActivity extends AbstractMyTracksActivity
Button save = (Button) findViewById(R.id.track_edit_save);
save.setOnClickListener(new View.OnClickListener() {
@Override
@Override
public void onClick(View v) {
String category = activityType.getText().toString();
track.setName(name.getText().toString());
String category = activityType.getText().toString();
boolean newCategory = !category.equals(track.getCategory());
boolean isRecording = trackId
== PreferencesUtils.getLong(TrackEditActivity.this, R.string.recording_track_id_key);
if (newCategory && !isRecording) {
// Update calorie
double calorie = CalorieUtils.getTrackCalorie(TrackEditActivity.this, track, -1L);
track.getTripStatistics().setCalorie(calorie);
}
track.setCategory(category);
track.setIcon(TrackIconUtils.getIconValue(TrackEditActivity.this, category));
track.setDescription(description.getText().toString());
track.setModifiedTime(System.currentTimeMillis());
if (!category.equals(track.getCategory())) {
track.setCategory(category);
myTracksProviderUtils.updateTrack(track);
// If edit recording track.
if (track.getId() == PreferencesUtils.getLong(getApplicationContext(),
R.string.recording_track_id_key)) {
// Update data without new calorie.
myTracksProviderUtils.updateTrack(track);
TrackRecordingServiceConnectionUtils.updateCalorie(trackRecordingServiceConnection);
} else {
CalorieUtils.updateCalorie(getApplicationContext(), -1L, track);
// Update data with new calorie.
myTracksProviderUtils.updateTrack(track);
}
if (newCategory && isRecording) {
// Update calorie through track recording service
TrackRecordingServiceConnectionUtils.updateCalorie(trackRecordingServiceConnection);
}
boolean driveSync = PreferencesUtils.getBoolean(TrackEditActivity.this,
R.string.drive_sync_key, PreferencesUtils.DRIVE_SYNC_DEFAULT);
boolean driveSync = PreferencesUtils.getBoolean(
TrackEditActivity.this, R.string.drive_sync_key, PreferencesUtils.DRIVE_SYNC_DEFAULT);
if (driveSync) {
PreferencesUtils.addToList(TrackEditActivity.this, R.string.drive_edited_list_key,
PreferencesUtils.DRIVE_EDITED_LIST_DEFAULT, String.valueOf(track.getId()));
@@ -1006,7 +1006,7 @@ public class TrackRecordingService extends Service {
Uri uri = myTracksProviderUtils.insertTrackPoint(location, track.getId());
long trackPointId = Long.parseLong(uri.getLastPathSegment());
trackTripStatisticsUpdater.addLocation(location, recordingDistanceInterval, true,
CalorieUtils.getActivityType(context, track.getId()), PreferencesUtils.getInt(context,
CalorieUtils.getActivityType(context, track.getCategory()), PreferencesUtils.getInt(context,
R.string.stats_weight_key, PreferencesUtils.STATS_WEIGHT_DEFAULT));
markerTripStatisticsUpdater.addLocation(location, recordingDistanceInterval, false,
ActivityType.INVALID, PreferencesUtils.STATS_WEIGHT_DEFAULT);
@@ -1359,37 +1359,36 @@ public class TrackRecordingService extends Service {
return;
}
executorService.submit(new Runnable() {
@Override
@Override
public void run() {
// Update track statistics.
long trackId = PreferencesUtils.getLong(getApplicationContext(),
R.string.recording_track_id_key);
if (trackId == PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) {
if (!isRecording()) {
Log.w(TAG, "Ignore updateCalorie. Not recording.");
return;
}
Track currentTrack = myTracksProviderUtils.getTrack(trackId);
if (currentTrack == null) {
Track track = myTracksProviderUtils.getTrack(recordingTrackId);
if (track == null) {
Log.w(TAG, "Ignore updateCalorie. No track.");
return;
}
double newCalorie = CalorieUtils.updateCalorie(getApplicationContext(), -1L, currentTrack);
trackTripStatisticsUpdater.updateCalorie(newCalorie);
// Update to database.
myTracksProviderUtils.updateTrack(currentTrack);
double calorie = CalorieUtils.getTrackCalorie(context, track, -1L);
// Update marker statistics.
Waypoint lastWayPoint = myTracksProviderUtils.getLastWaypoint(trackId,
WaypointType.WAYPOINT);
if (lastWayPoint == null) {
return;
}
long lastWayPoint_pointId = myTracksProviderUtils.getTrackPointId(trackId,
lastWayPoint.getLocation());
if (lastWayPoint_pointId > 0) {
newCalorie = CalorieUtils.updateCalorie(getApplicationContext(), lastWayPoint_pointId,
currentTrack);
}
markerTripStatisticsUpdater.updateCalorie(newCalorie);
// Update to database
track.getTripStatistics().setCalorie(calorie);
myTracksProviderUtils.updateTrack(track);
// Update track statistics
trackTripStatisticsUpdater.updateCalorie(calorie);
// Update marker statistics
Waypoint wayPoint = myTracksProviderUtils.getLastWaypoint(
recordingTrackId, WaypointType.STATISTICS);
long trackPointId = wayPoint != null ? myTracksProviderUtils.getTrackPointId(
recordingTrackId, wayPoint.getLocation())
: -1L;
calorie = CalorieUtils.getTrackCalorie(context, track, trackPointId);
markerTripStatisticsUpdater.updateCalorie(calorie);
}
});
}
@@ -26,34 +26,44 @@ import android.content.Context;
import android.location.Location;
/**
* Utils to calculate calories.
* Utilities to calculate calories.
*
* @author youtaol
*/
public class CalorieUtils {
/**
* Activity types.
*/
public enum ActivityType {
CYCLING, FOOT, INVALID
}
private CalorieUtils() {}
/**
* Resting VO2 is constant for everyone and is equal to 3.5 milliliters per
* kilogram of body weight per minute.
* Resting VO2 is 3.5 milliliters per kilogram of body weight per minute
* (ml/kg/min). The resting VO2 is the same for everyone.
*/
private static final double RESTING_VO2 = 3.5;
/**
* Ratio of change Liter to kcal. In kcal per Liter.
* Ratio to convert liter to kcal.
*/
private static final double L_TO_KCAL = 5;
private static final double earthGravity = 9.8;
private static final double L_TO_KCAL = 5.0;
/**
* Lumped constant for all frictional losses (tires, bearings, chain)
* Standard gravity in meters per second squared (m/s^2).
*/
private static final double EARTH_GRAVITY = 9.80665;
/**
* Lumped constant for all frictional losses (tires, bearings, chain).
*/
private static final double K1 = 0.0053;
/**
* Lumped constant for aerodynamic drag
* Lumped constant for aerodynamic drag.
*/
private static final double K2 = 0.185;
@@ -61,7 +71,7 @@ public class CalorieUtils {
* Critical speed running in meters per minute. Converts 4.5 miles per hour to
* meters per minute. 4 miles per hour is regarded as the max speed of walking
* and 5 miles per hour is regarded as the minimal speed of running, so we use
* the 4.5 miles per hour as the critical seed. <a href=
* 4.5 miles per hour as the critical speed. <a href=
* "http://www.ideafit.com/fitness-library/calculating-caloric-expenditure-0"
* >Reference</a>
*/
@@ -69,22 +79,194 @@ public class CalorieUtils {
static final double CRTICAL_SPEED_RUNNING = 4.5 * UnitConversions.MI_TO_KM
* UnitConversions.KM_TO_M / UnitConversions.HR_TO_MIN;
public enum ActivityType {
CYCLING, FOOT, INVALID
/**
* Gets the activity type.
*
* @param context the context
* @param activityType the activity type
*/
public static ActivityType getActivityType(Context context, String activityType) {
if (TrackIconUtils.getIconValue(context, activityType).equals(TrackIconUtils.WALK)
|| TrackIconUtils.getIconValue(context, activityType).equals(TrackIconUtils.RUN)) {
return ActivityType.FOOT;
} else if (TrackIconUtils.getIconValue(context, activityType).equals(TrackIconUtils.BIKE)) {
return ActivityType.CYCLING;
}
return ActivityType.INVALID;
}
/**
* Calculates the calorie of walking. This equation is appropriate for fairly
* slow speed ranges—from 1.9 to approximately 4 miles per hour (mph).
* Calculates the track calorie in kcal.
*
* @param speed is calculated in meters per second (m/s)
* @param grade
* @return the VO2 value in ml/kg/min.
* @param context the context
* @param track the track
* @param startTrackPointId the starting track point id. -1L to calculate the
* entire track
*/
public static double getTrackCalorie(Context context, Track track, long startTrackPointId) {
ActivityType activityType = getActivityType(context, track.getCategory());
if (activityType == ActivityType.INVALID) {
return 0.0;
}
MyTracksProviderUtils myTracksProviderUtils = MyTracksProviderUtils.Factory.get(context);
LocationIterator iterator = myTracksProviderUtils.getTrackPointLocationIterator(
track.getId(), startTrackPointId, false, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
TripStatisticsUpdater tripStatisticsUpdater = new TripStatisticsUpdater(
track.getTripStatistics().getStartTime());
int recordingDistanceInterval = PreferencesUtils.getInt(context,
R.string.recording_distance_interval_key,
PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT);
int statsWeight = PreferencesUtils.getInt(
context, R.string.stats_weight_key, PreferencesUtils.STATS_WEIGHT_DEFAULT);
while (iterator.hasNext()) {
tripStatisticsUpdater.addLocation(
iterator.next(), recordingDistanceInterval, true, activityType, statsWeight);
}
return tripStatisticsUpdater.getTripStatistics().getCalorie();
}
/**
* Gets the calorie in kcal between two locations.
*
* @param start the start location
* @param stop the stop location
* @param grade the grade
* @param weight the weight in kilogram. For cycling, weight of the rider plus
* bike. For foot, weight of the user
* @param activityType the activity type
*/
public static double getCalorie(
Location start, Location stop, double grade, int weight, ActivityType activityType) {
if (activityType == ActivityType.INVALID) {
return 0.0;
}
return ActivityType.CYCLING == activityType ? getCyclingCalorie(start, stop, grade, weight)
: getFootCalorie(start, stop, grade, weight);
}
/**
* Gets the cycling calorie in kcal between two locations.
*
* @param start the start location
* @param stop the stop location
* @param grade the grade
* @param weight the weight in kilogram of the rider plus bike
*/
@VisibleForTesting
static double getCyclingCalorie(Location start, Location stop, double grade, int weight) {
// Gets duration in seconds
double duration = (double) (stop.getTime() - start.getTime()) * UnitConversions.MS_TO_S;
// Get speed in meters per second
double speed = (start.getSpeed() + stop.getSpeed()) / 2.0;
if (grade < 0) {
grade = 0.0;
}
return getCyclingCalorie(speed, grade, weight, duration);
}
/**
* Gets the cycling calorie in kcal using the following equation: <br>
* P = g * m * Vg * (K1 + s) + K2 * (Va)^2 * Vg
* <ul>
* <li>P - Power in watt (Joule/second)</li>
* <li>g - gravity, using 9.80665 meter/second^2</li>
* <li>m - mass of the rider plus bike in kilogram</li>
* <li>s - surface grade</li>
* <li>Vg - ground relative speed in meter/second</li>
* <li>Va - air relative speed. We assume the air speed is zero, so Va is
* equal to Vg.</li>
* <li>K1 - a constant which represents frictional losses, using 0.0053.</li>
* <li>K2 - a constant representing aerodynamic drag, using 0.185.</li>
* <li></li>
* </ul>
*
* @param speed the speed in meters per second
* @param grade the grade
* @param weight weight in kilogram of the rider plus bike
* @param duration the duration in seconds
*/
@VisibleForTesting
static double getCyclingCalorie(double speed, double grade, int weight, double duration) {
// Get the power in watt (Joule/second)
double power = EARTH_GRAVITY * weight * speed * (K1 + grade) + K2 * (speed * speed * speed);
// Get the calories in kcal
return power * duration * UnitConversions.J_TO_KCAL;
}
/**
* Gets the foot calorie in kcal between two locations.
*
* @param start the start location
* @param stop the stop location
* @param grade the grade
* @param weight the weight of the user in kilogram
*/
@VisibleForTesting
static double getFootCalorie(Location start, Location stop, double grade, int weight) {
// Get speed in meters per second
double averageSpeed = (start.getSpeed() + stop.getSpeed()) / 2.0;
// Get VO2 in mL/kg/min
double vo2 = getFootVo2(averageSpeed, grade);
// Duration in minutes
double duration = (double) (stop.getTime() - start.getTime()) * UnitConversions.MS_TO_S
* UnitConversions.S_TO_MIN;
/*
* Get the calorie. The unit of calorie is kcal which is came from
* (mL/kg/min * min * kg * L/mL * kcal/L)
*/
return vo2 * duration * weight * UnitConversions.ML_TO_L * L_TO_KCAL;
}
/**
* Gets the foot VO2 value in ml/kg/min.
*
* @param speed the speed in meters per second
* @param grade the grade
*/
@VisibleForTesting
static double getFootVo2(double speed, double grade) {
if (grade < 0) {
grade = 0.0;
}
return speed > CRTICAL_SPEED_RUNNING ? getHighSpeedFootVo2(speed, grade)
: getLowSpeedFootVo2(speed, grade);
}
/**
* Gets the high speed foot VO2 in ml/kg/min. This equation is appropriate for
* speeds greater than 4.5 mph like running.
*
* @param speed the speed in meters per second
* @param grade the grade
*/
@VisibleForTesting
static double getHighSpeedFootVo2(double speed, double grade) {
// Change from meters per second to meters per minute
speed = speed / UnitConversions.S_TO_MIN;
/*
* 0.2 means oxygen cost per meter of moving each kilogram (kg) of body
* weight while running (horizontally). 0.9 means oxygen cost per meter of
* moving total body mass against gravity (vertically).
*/
return 0.2 * speed + 0.9 * speed * Math.abs(grade) + RESTING_VO2;
}
/**
* Gets the low speed foot VO2 in ml/kg/min. This equation is appropriate for
* speed less than 4.5 mph like walking.
*
* @param speed the speed in meters per second
* @param grade the grade
*/
@VisibleForTesting
static double getLowSpeedFootVo2(double speed, double grade) {
// Change meters per second to meters per minute
// Change from meters per second to meters per minute
speed = speed / UnitConversions.S_TO_MIN;
/*
* 0.1 means oxygen cost per meter of moving each kilogram (kg) of body
* weight while walking (horizontally). 1.8 means oxygen cost per meter of
@@ -92,221 +274,4 @@ public class CalorieUtils {
*/
return 0.1 * speed + 1.8 * speed * Math.abs(grade) + RESTING_VO2;
}
/**
* Calculates the calorie of running. This equation is appropriate for speeds
* greater than 5.0 mph (or 3.0 mph or greater if the subject is truly
* jogging).
*
* @param speed is calculated in meters per second (m/s)
* @param grade
* @return the VO2 value in ml/kg/min.
*/
@VisibleForTesting
static double getHighSpeedFootVo2(double speed, double grade) {
// Change meters per second to meters per minute
speed = speed / UnitConversions.S_TO_MIN;
/*
* 0.2 means oxygen cost per meter of moving each kg of body weight while
* running (horizontally). 0.9 means oxygen cost per meter of moving total
* body mass against gravity (vertically).
*/
return 0.2 * speed + 0.9 * speed * Math.abs(grade) + RESTING_VO2;
}
/**
* Calculates the calorie of cycling. Use below equation: <br>
* P = g * m * Vg * (K1 + s) + K2 * (Va)^2 * Vg
* <ul>
* <li>P - Power in watts(Joule/second)</li>
* <li>g - gravity, using 9.8 m / s^2</li>
* <li>m - mass of the rider plus bike, in kg</li>
* <li>s - grade of surface</li>
* <li>Vg - ground relative speed m/s</li>
* <li>Va - air relative speed. We assume the air speed is zero, so Va is
* equal with Vg.</li>
* <li>K1 - a constant which represents frictional losses, using 0.0053.</li>
* <li>K2 - a constant representing aerodynamic drag, using 0.185 kg/m.</li>
* <li></li>
* </ul>
*
* @param speed is calculated in meters per second (m/s)
* @param grade the grade to calculate
* @param weight of rider plus bike, in kilogram
* @param timeUsed how many times used in second
* @return the calorie value in kcal.
*/
@VisibleForTesting
static double calculateCyclingCalorie(double speed, double grade, int weight, double timeUsed) {
// Get the Power, the unit is Watt (Joule/second)
double power = earthGravity * weight * speed * (K1 + grade) + K2 * (speed * speed * speed);
// Get the calories in kcal
return power * timeUsed * UnitConversions.J_TO_KCAL;
}
/**
* Calculates the calories by cycling.
*
* @param start the start location
* @param stop the stop location
* @param grade the grade to calculate
* @param weight the weight of user, in kilogram
* @return the calories between the start and stop location.
*/
@VisibleForTesting
static double calculateCalorieCycling(Location start, Location stop, double grade, int weight) {
// Gets duration in seconds
double duration = (double) (stop.getTime() - start.getTime()) * UnitConversions.MS_TO_S;
// Get speed in meters per second
double speed = (start.getSpeed() + stop.getSpeed()) / 2.0;
if (grade < 0) {
grade = 0.0;
}
return calculateCyclingCalorie(speed, grade, weight, duration);
}
/**
* Calculates the calories by running or walking.
*
* @param start the start location
* @param stop the stop location
* @param grade the grade to calculate
* @param weight the weight of user, in kilogram
* @return the calories between the start and stop location, in calories.
*/
@VisibleForTesting
static double calculateCalorieFoot(Location start, Location stop, double grade, int weight) {
// Get speed in meters per second
double averageSpeed = (start.getSpeed() + stop.getSpeed()) / 2.0;
// Get VO2 in mL/kg/min
double vo2 = getFootVo2(averageSpeed, grade);
// Minutes
double time = (double) (stop.getTime() - start.getTime()) * UnitConversions.MS_TO_S
* UnitConversions.S_TO_MIN;
// Get the calorie. The unit of calorie is kcal which is came from mL/kg/min
// * min * kg * L/mL * kcal/L
return vo2 * time * weight * UnitConversions.ML_TO_L * L_TO_KCAL;
}
/**
* Gets the VO2 value.
*
* @param speed in meters per second
* @param grade the grade to calculate
* @return the VO2 value.
*/
@VisibleForTesting
static double getFootVo2(double speed, double grade) {
if (grade < 0) {
grade = 0.0;
}
return speed > CRTICAL_SPEED_RUNNING ? getHighSpeedFootVo2(speed, grade) : getLowSpeedFootVo2(
speed, grade);
}
/**
* Calculates the calories between two locations.
*
* @param start the start location
* @param stop the stop location
* @param grade the grade to calculate
* @param weight the weight of user
* @param activityType can be foot or cycling
* @return the calories between the start and stop location.
*/
public static double getCalorie(Location start, Location stop, double grade, int weight,
ActivityType activityType) {
if (activityType == ActivityType.INVALID) {
return 0.0;
}
return ActivityType.CYCLING == activityType ? calculateCalorieCycling(start, stop, grade,
weight) : calculateCalorieFoot(start, stop, grade, weight);
}
/**
* Gets the activity type for calculating calorie by the id of track.
*
* @param context current context
* @param trackId the id of track
* @return activityType the activity type of track.
*/
public static ActivityType getActivityType(Context context, long trackId) {
ActivityType activityType = ActivityType.INVALID;
Track track = MyTracksProviderUtils.Factory.get(context).getTrack(trackId);
if (track != null) {
String category = track.getCategory();
if (category.equals(context.getString(R.string.activity_type_walking))
|| category.equals(context.getString(R.string.activity_type_running))) {
activityType = ActivityType.FOOT;
} else if (category.equals(context.getString(R.string.activity_type_cycling))
|| category.equals(context.getString(R.string.activity_type_biking))) {
activityType = ActivityType.CYCLING;
}
}
return activityType;
}
/**
* Gets the activity type for calculating calorie by the category of track.
*
* @param context current context
* @param category the category of track
* @return activityType the activity type of track.
*/
public static ActivityType getActivityType(Context context, String category) {
ActivityType activityType = ActivityType.INVALID;
if (category.equals(context.getString(R.string.activity_type_walking))
|| category.equals(context.getString(R.string.activity_type_running))) {
activityType = ActivityType.FOOT;
} else if (category.equals(context.getString(R.string.activity_type_cycling))
|| category.equals(context.getString(R.string.activity_type_biking))) {
activityType = ActivityType.CYCLING;
}
return activityType;
}
/**
* Calculates the calorie of track.
*
* @param context the context
* @param track the track to calculate
* @param startTrackPointId the starting track point id. Make this value 1L to
* calculate the entire track
* @return the calorie value of track
*/
public static double updateCalorie(Context context,
long startTrackPointId, Track track) {
ActivityType activityType = getActivityType(context, track.getCategory());
MyTracksProviderUtils providerUtils = MyTracksProviderUtils.Factory.get(context);
long trackId = track.getId();
LocationIterator points = providerUtils.getTrackPointLocationIterator(trackId,
startTrackPointId, false, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
TripStatisticsUpdater tripStatisticsUpdater = new TripStatisticsUpdater(track
.getTripStatistics().getStartTime());
while (points.hasNext()) {
if (activityType == ActivityType.INVALID) {
tripStatisticsUpdater.addLocation(points.next(), PreferencesUtils.getInt(context,
R.string.recording_distance_interval_key,
PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT), false, activityType,
PreferencesUtils.STATS_WEIGHT_DEFAULT);
} else {
tripStatisticsUpdater.addLocation(points.next(), PreferencesUtils.getInt(context,
R.string.recording_distance_interval_key,
PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT), true, activityType,
PreferencesUtils.getInt(context, R.string.stats_weight_key,
PreferencesUtils.STATS_WEIGHT_DEFAULT));
}
}
track.setTripStatistics(tripStatisticsUpdater.getTripStatistics());
return tripStatisticsUpdater.getTripStatistics().getCalorie();
}
}
@@ -39,7 +39,7 @@ public class TrackIconUtils {
public static final String BIKE = "BIKE";
private static final String BOAT = "BOAT";
public static final String DRIVE = "DRIVE";
private static final String RUN = "RUN";
public static final String RUN = "RUN";
private static final String SKI = "SKI";
private static final String SNOW_BOARDING = "SNOW_BOARDING";
public static final String WALK = "WALK";
@@ -46,7 +46,7 @@ public class CalorieUtilsTest extends TestCase {
public void testGetCalories_foot() {
double actual = CalorieUtils.getCalorie(start, stop, grade, weight,
CalorieUtils.ActivityType.FOOT);
double expected = CalorieUtils.calculateCalorieFoot(start, stop, grade, weight);
double expected = CalorieUtils.getFootCalorie(start, stop, grade, weight);
assertEquals(expected, actual);
}
@@ -58,7 +58,7 @@ public class CalorieUtilsTest extends TestCase {
double expectGrade = 0;
double actual = CalorieUtils.getCalorie(start, stop, actualGrade, weight,
CalorieUtils.ActivityType.FOOT);
double expected = CalorieUtils.calculateCalorieFoot(start, stop, expectGrade, weight);
double expected = CalorieUtils.getFootCalorie(start, stop, expectGrade, weight);
assertEquals(expected, actual);
}
@@ -68,7 +68,7 @@ public class CalorieUtilsTest extends TestCase {
public void testGetCalories_cycling() {
double actual = CalorieUtils.getCalorie(start, stop, grade, weight,
CalorieUtils.ActivityType.CYCLING);
double expected = CalorieUtils.calculateCalorieCycling(start, stop, grade, weight);
double expected = CalorieUtils.getCyclingCalorie(start, stop, grade, weight);
assertEquals(expected, actual);
}
@@ -106,7 +106,7 @@ public class CalorieUtilsTest extends TestCase {
start.setSpeed(9);
stop.setSpeed(9);
double expected = 175.0 * (TIME_INTERVAL / 1000) * UnitConversions.J_TO_KCAL;
double actual = CalorieUtils.calculateCalorieCycling(start, stop, grade, weight);
double actual = CalorieUtils.getCyclingCalorie(start, stop, grade, weight);
assertTrue((actual - expected) / expected < 0.02);
}
}