Update formulas in CalorieUtils

This commit is contained in:
Jimmy Shih
2013-11-13 12:56:05 -08:00
parent fb0dac20c1
commit 801b597fa0
4 changed files with 93 additions and 261 deletions
@@ -20,7 +20,6 @@ import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationIt
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.stats.TripStatisticsUpdater;
import com.google.android.maps.mytracks.R;
import com.google.common.annotations.VisibleForTesting;
import android.content.Context;
import android.location.Location;
@@ -36,7 +35,7 @@ public class CalorieUtils {
* Activity types.
*/
public enum ActivityType {
CYCLING, FOOT, INVALID
CYCLING, RUNNING, WALKING, INVALID
}
private CalorieUtils() {}
@@ -47,15 +46,10 @@ public class CalorieUtils {
*/
private static final double RESTING_VO2 = 3.5;
/**
* Ratio to convert liter to kcal.
*/
private static final double L_TO_KCAL = 5.0;
/**
* Standard gravity in meters per second squared (m/s^2).
*/
private static final double EARTH_GRAVITY = 9.80665;
private static final double EARTH_GRAVITY = 9.81;
/**
* Lumped constant for all frictional losses (tires, bearings, chain).
@@ -63,22 +57,10 @@ public class CalorieUtils {
private static final double K1 = 0.0053;
/**
* Lumped constant for aerodynamic drag.
* Lumped constant for aerodynamic drag (kg/m)
*/
private static final double K2 = 0.185;
/**
* 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
* 4.5 miles per hour as the critical speed. <a href=
* "http://www.ideafit.com/fitness-library/calculating-caloric-expenditure-0"
* >Reference</a>
*/
@VisibleForTesting
static final double CRTICAL_SPEED_RUNNING = 4.5 * UnitConversions.MI_TO_KM
* UnitConversions.KM_TO_M / UnitConversions.HR_TO_MIN;
/**
* Gets the activity type.
*
@@ -89,9 +71,10 @@ public class CalorieUtils {
if (activityType == null || activityType.equals("")) {
return ActivityType.INVALID;
}
if (TrackIconUtils.getIconValue(context, activityType).equals(TrackIconUtils.WALK)
|| TrackIconUtils.getIconValue(context, activityType).equals(TrackIconUtils.RUN)) {
return ActivityType.FOOT;
if (TrackIconUtils.getIconValue(context, activityType).equals(TrackIconUtils.WALK)) {
return ActivityType.WALKING;
} else if (TrackIconUtils.getIconValue(context, activityType).equals(TrackIconUtils.RUN)) {
return ActivityType.RUNNING;
} else if (TrackIconUtils.getIconValue(context, activityType).equals(TrackIconUtils.BIKE)) {
return ActivityType.CYCLING;
}
@@ -122,6 +105,7 @@ public class CalorieUtils {
PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT);
double weight = PreferencesUtils.getFloat(
context, R.string.stats_weight_key, PreferencesUtils.STATS_WEIGHT_DEFAULT);
while (iterator.hasNext()) {
tripStatisticsUpdater.addLocation(
iterator.next(), recordingDistanceInterval, true, activityType, weight);
@@ -144,137 +128,83 @@ public class CalorieUtils {
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, double 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, double 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, double 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
// Speed in m/s
double speed = (start.getSpeed() + stop.getSpeed()) / 2.0;
// Duration in min
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;
if (activityType == ActivityType.CYCLING) {
/*
* Power in watt (Joule/second). See
* http://en.wikipedia.org/wiki/Bicycle_performance.
*/
double power = EARTH_GRAVITY * weight * speed * (K1 + grade) + K2 * (speed * speed * speed);
// WorkRate in kgm/min
double workRate = power * UnitConversions.W_TO_KGM;
/*
* VO2 in kgm/min/kg 1.8 = oxygen cost of producing 1 kgm/min of power
* output. 7 = oxygen cost of unloaded cycling plus resting oxygen
* consumption
*/
double vo2 = (1.8 * workRate / weight) + 7;
// Calorie in kcal
return vo2 * duration * weight * UnitConversions.KGM_TO_KCAL;
} else {
double vo2 = activityType == ActivityType.RUNNING ? getRunningVo2(speed, grade)
: getWalkingVo2(speed, grade);
/*
* Calorie in kcal (mL/kg/min * min * kg * L/mL * kcal/L)
*/
return vo2 * duration * weight * UnitConversions.ML_TO_L * UnitConversions.L_TO_KCAL;
}
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.
* Gets the running VO2 in ml/kg/min. This equation is appropriate for speeds
* greater than 5 mi/hr (or 3 mi/hr or greater if the subject is truly
* jogging).
*
* @param speed the speed in meters per second
* @param speed the speed in m/s
* @param grade the grade
*/
@VisibleForTesting
static double getHighSpeedFootVo2(double speed, double grade) {
// Change from meters per second to meters per minute
private static double getRunningVo2(double speed, double grade) {
// Change from m/s to m/min
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).
* 0.2 = oxygen cost per meter of moving each kg of body weight while
* running (horizontally). 0.9 = oxygen cost per meter of moving total body
* mass against gravity (vertically).
*/
return 0.2 * speed + 0.9 * speed * Math.abs(grade) + RESTING_VO2;
return 0.2 * speed + 0.9 * speed * 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.
* Gets the walking VO2 in ml/kg/min. This equation is appropriate for speed
* from 1.9 to 4 mi/hr.
*
* @param speed the speed in meters per second
* @param speed the speed in m/s
* @param grade the grade
*/
@VisibleForTesting
static double getLowSpeedFootVo2(double speed, double grade) {
// Change from meters per second to meters per minute
private static double getWalkingVo2(double speed, double grade) {
// Change from m/s to m/min
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
* moving total body mass against gravity (vertically).
* 0.1 = oxygen cost per meter of moving each kilogram (kg) of body weight
* while walking (horizontally). 1.8 = oxygen cost per meter of moving total
* body mass against gravity (vertically).
*/
return 0.1 * speed + 1.8 * speed * Math.abs(grade) + RESTING_VO2;
return 0.1 * speed + 1.8 * speed * grade + RESTING_VO2;
}
}
@@ -24,9 +24,8 @@ public class UnitConversions {
private UnitConversions() {}
// multiplication factor to convert milliliters to liters
public static final double ML_TO_L = 1 / 1000.0;
// Time
// multiplication factor to convert milliseconds to seconds
public static final double MS_TO_S = 1 / 1000.0;
@@ -42,14 +41,18 @@ public class UnitConversions {
// multiplication factor to convert minutes to hours
public static final double MIN_TO_HR = 1 / HR_TO_MIN;
// multiplication factor to convert kilometers to miles
public static final double KM_TO_MI = 0.621371192;
// Weight
// multiplication factor to convert kilograms to pounds
public static final double KG_TO_LB = 2.2046;
// multiplication factor to convert pounds to kilograms
public static final double LB_TO_KG = 1 / KG_TO_LB;
// Distance
// multiplication factor to convert kilometers to miles
public static final double KM_TO_MI = 0.621371192;
// multiplication factor to convert miles to kilometers
public static final double MI_TO_KM = 1 / KM_TO_MI;
@@ -60,26 +63,37 @@ public class UnitConversions {
// multiplication factor to convert feet to miles
public static final double FT_TO_MI = 1 / MI_TO_FT;
// multiplication factor to covert kilometers to meters
public static final double KM_TO_M = 1000.0;
// multiplication factor to convert meters to kilometers
public static final double M_TO_KM = 1 / 1000.0;
public static final double KM_TO_M = 1 / M_TO_KM;
// multiplication factor to convert meters per second to kilometers per hour
public static final double MS_TO_KMH = 3.6;
public static final double M_TO_KM = 1 / KM_TO_M;
// multiplication factor to convert meters to miles
public static final double M_TO_MI = M_TO_KM * KM_TO_MI;
// multiplication factor to convert meters to feet
public static final double M_TO_FT = M_TO_MI * MI_TO_FT;
// multiplication factor to convert meters per second to kilometers per hour
public static final double MS_TO_KMH = 3.6;
// Calories
// multiplication factor to convert milliliters to liters
public static final double ML_TO_L = 1 / 1000.0;
// multiplication factor to convert liter to kcal
public static final double L_TO_KCAL = 5.0;
// multiplication factor to convert watt to kgm/min
public static final double W_TO_KGM = 6.12;
// multiplication factor to convert kgm to kcal
public static final double KGM_TO_KCAL = 1 / 427.0;
// Others
// multiplication factor to convert degrees to radians
public static final double DEG_TO_RAD = Math.PI / 180.0;
// multiplication factor to convert Kcal to joules.
public static final double KCAL_TO_J = 4184;
// multiplication factor to convert joules to Kcal.
public static final double J_TO_KCAL = 1 / KCAL_TO_J;
}
@@ -225,7 +225,7 @@ public class TripStatisticsUpdaterTest extends TestCase {
Location location = getLocation(i + locationOffset, (i + locationOffset) * .001, MOVING_SPEED,
startTime + (timeOffset + i) * TEN_SECONDS);
tripStatisticsUpdater.addLocation(location,
PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT, true, ActivityType.FOOT,
PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT, true, ActivityType.WALKING,
PreferencesUtils.STATS_WEIGHT_DEFAULT);
tripStatistics = tripStatisticsUpdater.getTripStatistics();
@@ -272,7 +272,7 @@ public class TripStatisticsUpdaterTest extends TestCase {
Location location = getLocation(
locationOffset, locationOffset * .001, 0, startTime + (i + timeOffset) * TEN_SECONDS);
tripStatisticsUpdater.addLocation(location,
PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT, false, ActivityType.FOOT,
PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT, false, ActivityType.WALKING,
PreferencesUtils.STATS_WEIGHT_DEFAULT);
tripStatistics = tripStatisticsUpdater.getTripStatistics();
@@ -310,7 +310,7 @@ public class TripStatisticsUpdaterTest extends TestCase {
Location location = getLocation(i + locationOffset, (i + locationOffset) * .001, 99999,
startTime + (timeOffset + i) * TEN_SECONDS);
tripStatisticsUpdater.addLocation(location,
PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT, true, ActivityType.FOOT,
PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT, true, ActivityType.WALKING,
PreferencesUtils.STATS_WEIGHT_DEFAULT);
tripStatistics = tripStatisticsUpdater.getTripStatistics();
@@ -1,112 +0,0 @@
/*
* Copyright 2013 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.util;
import android.location.Location;
import android.location.LocationManager;
import junit.framework.TestCase;
/**
* A unit test for {@link CalorieUtils}.
*
* @author youtaol
*/
public class CalorieUtilsTest extends TestCase {
Location start = new Location(LocationManager.GPS_PROVIDER);
Location stop = new Location(LocationManager.GPS_PROVIDER);
private double grade = 0.07;
private int weight = 80;
private final long TIME_INTERVAL = 1000l;
@Override
protected void setUp() throws Exception {
super.setUp();
// Make the time interval is not 0
stop.setTime(start.getTime() + TIME_INTERVAL);
}
/**
* Checks using foot calculation equation.
*/
public void testGetCalories_foot() {
double actual = CalorieUtils.getCalorie(start, stop, grade, weight,
CalorieUtils.ActivityType.FOOT);
double expected = CalorieUtils.getFootCalorie(start, stop, grade, weight);
assertEquals(expected, actual);
}
/**
* Checks whether using foot calculation equation while grade is negative.
*/
public void testGetCalories_footNegativeGrade() {
double actualGrade = -5;
double expectGrade = 0;
double actual = CalorieUtils.getCalorie(start, stop, actualGrade, weight,
CalorieUtils.ActivityType.FOOT);
double expected = CalorieUtils.getFootCalorie(start, stop, expectGrade, weight);
assertEquals(expected, actual);
}
/**
* Checks using cycling calculation equation.
*/
public void testGetCalories_cycling() {
double actual = CalorieUtils.getCalorie(start, stop, grade, weight,
CalorieUtils.ActivityType.CYCLING);
double expected = CalorieUtils.getCyclingCalorie(start, stop, grade, weight);
assertEquals(expected, actual);
}
/**
* Checks using running VO2 equation.
*/
public void testGetVO2_running() {
double actual = CalorieUtils.getFootVo2(CalorieUtils.CRTICAL_SPEED_RUNNING * 2, grade);
double expected = CalorieUtils.getHighSpeedFootVo2(CalorieUtils.CRTICAL_SPEED_RUNNING * 2,
grade);
assertEquals(expected, actual);
}
/**
* Checks using walking VO2 equation.
*/
public void testGetVO2_walking() {
// Test at half the critical speed
double footSpeed = CalorieUtils.CRTICAL_SPEED_RUNNING / 2.0;
double actual = CalorieUtils.getFootVo2(footSpeed, grade);
double expected = CalorieUtils.getLowSpeedFootVo2(footSpeed, grade);
assertEquals(expected, actual);
}
/**
* Checks calculating cycling calorie. 175 is the watt usage for a 90 kg bike
* + rider to go 9 m/s (20 mph or 32 km/h) on the flat. <a href=
* "http://www.ideafit.com/fitness-library/calculating-caloric-expenditure-0"
* >Reference</a>
*/
public void testCalculateCalorieCycling() {
grade = 0;
weight = 90;
start.setSpeed(9);
stop.setSpeed(9);
double expected = 175.0 * (TIME_INTERVAL / 1000) * UnitConversions.J_TO_KCAL;
double actual = CalorieUtils.getCyclingCalorie(start, stop, grade, weight);
assertTrue((actual - expected) / expected < 0.02);
}
}