diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/CalorieUtils.java b/MyTracks/src/com/google/android/apps/mytracks/util/CalorieUtils.java index 3d5b9b6e9..09697499e 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/util/CalorieUtils.java +++ b/MyTracks/src/com/google/android/apps/mytracks/util/CalorieUtils.java @@ -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. Reference - */ - @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:
- * P = g * m * Vg * (K1 + s) + K2 * (Va)^2 * Vg - * - * - * @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; } } \ No newline at end of file diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/UnitConversions.java b/MyTracks/src/com/google/android/apps/mytracks/util/UnitConversions.java index 7f5e0e2ec..003de174a 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/util/UnitConversions.java +++ b/MyTracks/src/com/google/android/apps/mytracks/util/UnitConversions.java @@ -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; } diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/stats/TripStatisticsUpdaterTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/stats/TripStatisticsUpdaterTest.java index efcba5995..ee3f43a3b 100644 --- a/MyTracksTest/src/com/google/android/apps/mytracks/stats/TripStatisticsUpdaterTest.java +++ b/MyTracksTest/src/com/google/android/apps/mytracks/stats/TripStatisticsUpdaterTest.java @@ -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(); diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/util/CalorieUtilsTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/util/CalorieUtilsTest.java deleted file mode 100644 index 6ae3f7748..000000000 --- a/MyTracksTest/src/com/google/android/apps/mytracks/util/CalorieUtilsTest.java +++ /dev/null @@ -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. Reference - */ - 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); - } -} \ No newline at end of file