Improve elevation and grade calculation to take into account locations without altitude.

This commit is contained in:
Jimmy Shih
2013-07-11 13:53:34 -07:00
parent 9b0c14c9c7
commit 4b6c9a875b
2 changed files with 82 additions and 53 deletions
@@ -35,12 +35,6 @@ import android.util.Log;
*/ */
public class TripStatisticsUpdater { public class TripStatisticsUpdater {
/**
* The number of distance readings to smooth to get a stable signal.
*/
@VisibleForTesting
static final int DISTANCE_SMOOTHING_FACTOR = 25;
/** /**
* The number of elevation readings to smooth to get a somewhat accurate * The number of elevation readings to smooth to get a somewhat accurate
* signal. * signal.
@@ -48,6 +42,18 @@ public class TripStatisticsUpdater {
@VisibleForTesting @VisibleForTesting
static final int ELEVATION_SMOOTHING_FACTOR = 25; static final int ELEVATION_SMOOTHING_FACTOR = 25;
/**
* The number of run readings to smooth for calculating grade.
*/
@VisibleForTesting
static final int RUN_SMOOTHING_FACTOR = 25;
/**
* The number of rise readings to smooth for calculating grade.
*/
@VisibleForTesting
static final int RISE_SMOOTHING_FACTOR = 25;
/** /**
* The number of grade readings to smooth to get a somewhat accurate signal. * The number of grade readings to smooth to get a somewhat accurate signal.
*/ */
@@ -84,18 +90,21 @@ public class TripStatisticsUpdater {
// Current segment's last moving location // Current segment's last moving location
private Location lastMovingLocation; private Location lastMovingLocation;
// A buffer of the recent speed readings (m/s) for calculating max speed
private final DoubleBuffer speedBuffer = new DoubleBuffer(SPEED_SMOOTHING_FACTOR);
// A buffer of the recent elevation readings (m) // A buffer of the recent elevation readings (m)
private final DoubleBuffer elevationBuffer = new DoubleBuffer(ELEVATION_SMOOTHING_FACTOR); private final DoubleBuffer elevationBuffer = new DoubleBuffer(ELEVATION_SMOOTHING_FACTOR);
// A buffer of the recent distance readings (m) for calculating grade // A buffer of the recent run readings (m) for calculating grade
private final DoubleBuffer distanceBuffer = new DoubleBuffer(DISTANCE_SMOOTHING_FACTOR); private final DoubleBuffer runBuffer = new DoubleBuffer(RUN_SMOOTHING_FACTOR);
// A buffer of the recent rise readings (m) for calculating grade
private final DoubleBuffer riseBuffer = new DoubleBuffer(RISE_SMOOTHING_FACTOR);
// A buffer of the recent grade calculations (%) // A buffer of the recent grade calculations (%)
private final DoubleBuffer gradeBuffer = new DoubleBuffer(GRADE_SMOOTHING_FACTOR); private final DoubleBuffer gradeBuffer = new DoubleBuffer(GRADE_SMOOTHING_FACTOR);
// A buffer of the recent speed readings (m/s) for calculating max speed
private final DoubleBuffer speedBuffer = new DoubleBuffer(SPEED_SMOOTHING_FACTOR);
/** /**
* Creates a new trip statistics updater. * Creates a new trip statistics updater.
* *
@@ -128,8 +137,11 @@ public class TripStatisticsUpdater {
* @param minRecordingDistance the min recording distance * @param minRecordingDistance the min recording distance
*/ */
public void addLocation(Location location, int minRecordingDistance) { public void addLocation(Location location, int minRecordingDistance) {
// Always update time
updateTime(location.getTime());
if (!LocationUtils.isValidLocation(location)) { if (!LocationUtils.isValidLocation(location)) {
updateTime(location.getTime()); // Either pause or resume marker
if (location.getLatitude() == TrackRecordingService.PAUSE_LATITUDE) { if (location.getLatitude() == TrackRecordingService.PAUSE_LATITUDE) {
if (lastLocation != null && lastMovingLocation != null if (lastLocation != null && lastMovingLocation != null
&& lastLocation != lastMovingLocation) { && lastLocation != lastMovingLocation) {
@@ -140,40 +152,56 @@ public class TripStatisticsUpdater {
currentSegment = init(location.getTime()); currentSegment = init(location.getTime());
lastLocation = null; lastLocation = null;
lastMovingLocation = null; lastMovingLocation = null;
speedBuffer.reset();
elevationBuffer.reset(); elevationBuffer.reset();
distanceBuffer.reset(); runBuffer.reset();
riseBuffer.reset();
gradeBuffer.reset(); gradeBuffer.reset();
speedBuffer.reset();
return; return;
} }
double elevationDifference = updateElevation(location.getAltitude());
currentSegment.updateLatitudeExtremities(location.getLatitude()); currentSegment.updateLatitudeExtremities(location.getLatitude());
currentSegment.updateLongitudeExtremities(location.getLongitude()); currentSegment.updateLongitudeExtremities(location.getLongitude());
if (location.hasAltitude()) {
updateElevation(location.getAltitude());
}
if (lastLocation == null || lastMovingLocation == null) { if (lastLocation == null || lastMovingLocation == null) {
updateTime(location.getTime());
lastLocation = location; lastLocation = location;
lastMovingLocation = location; lastMovingLocation = location;
return; return;
} }
double movingDistance = lastMovingLocation.distanceTo(location); double movingDistance = lastMovingLocation.distanceTo(location);
if (movingDistance < minRecordingDistance && location.getSpeed() < MAX_NO_MOVEMENT_SPEED) { if (movingDistance < minRecordingDistance) {
updateTime(location.getTime()); if (!location.hasSpeed() || location.getSpeed() < MAX_NO_MOVEMENT_SPEED) {
lastLocation = location; lastLocation = location;
return; return;
}
} }
long movingTime = location.getTime() - lastLocation.getTime(); long movingTime = location.getTime() - lastLocation.getTime();
if (movingTime < 0) { if (movingTime < 0) {
updateTime(location.getTime());
lastLocation = location; lastLocation = location;
return; return;
} }
// Update total distance
currentSegment.addTotalDistance(movingDistance); currentSegment.addTotalDistance(movingDistance);
// Update moving time
currentSegment.addMovingTime(movingTime); currentSegment.addMovingTime(movingTime);
// Update grade
if (lastLocation.hasAltitude() && location.hasAltitude()) {
float run = lastLocation.distanceTo(location);
double rise = location.getAltitude() - lastLocation.getAltitude();
updateGrade(run, rise);
}
// Update max speed
updateSpeed( updateSpeed(
location.getTime(), location.getSpeed(), lastLocation.getTime(), lastLocation.getSpeed()); location.getTime(), location.getSpeed(), lastLocation.getTime(), lastLocation.getSpeed());
updateGrade(lastLocation.distanceTo(location), elevationDifference);
updateTime(location.getTime());
lastLocation = location; lastLocation = location;
lastMovingLocation = location; lastMovingLocation = location;
} }
@@ -217,38 +245,46 @@ public class TripStatisticsUpdater {
* @param elevation the elevation * @param elevation the elevation
*/ */
@VisibleForTesting @VisibleForTesting
double updateElevation(double elevation) { void updateElevation(double elevation) {
currentSegment.updateElevationExtremities(elevation);
// update elevation gain
double oldAverage = elevationBuffer.getAverage(); double oldAverage = elevationBuffer.getAverage();
elevationBuffer.setNext(elevation); elevationBuffer.setNext(elevation);
double newAverage = elevationBuffer.getAverage(); double newAverage = elevationBuffer.getAverage();
currentSegment.updateElevationExtremities(newAverage); if (elevationBuffer.isFull()) {
double elevationDifference = elevationBuffer.isFull() ? newAverage - oldAverage : 0.0; double difference = newAverage - oldAverage;
if (elevationDifference > 0) { if (difference > 0) {
currentSegment.addTotalElevationGain(elevationDifference); currentSegment.addTotalElevationGain(difference);
}
} }
return elevationDifference;
} }
/** /**
* Updates a grade reading. * Updates a grade reading.
* *
* @param distance the distance the user just traveled * @param run the run
* @param elevationDifference the elevation difference between the current * @param rise the rise
* reading and the previous reading
*/ */
@VisibleForTesting @VisibleForTesting
void updateGrade(double distance, double elevationDifference) { void updateGrade(double run, double rise) {
distanceBuffer.setNext(distance); runBuffer.setNext(run);
double smoothedDistance = distanceBuffer.getAverage(); riseBuffer.setNext(rise);
if (!runBuffer.isFull() || !riseBuffer.isFull()) {
return;
}
double smoothedRun = runBuffer.getAverage();
/* /*
* With the error in the altitude measurement it is dangerous to divide by * With the error in the altitude measurement it is dangerous to divide by
* anything less than 5. * anything less than 5.
*/ */
if (!elevationBuffer.isFull() || !distanceBuffer.isFull() || smoothedDistance < 5.0) { if (smoothedRun < 5.0) {
return; return;
} }
gradeBuffer.setNext(elevationDifference / smoothedDistance); gradeBuffer.setNext(riseBuffer.getAverage() / smoothedRun);
currentSegment.updateGradeExtremities(gradeBuffer.getAverage()); currentSegment.updateGradeExtremities(gradeBuffer.getAverage());
} }
@@ -84,7 +84,7 @@ public class TripStatisticsUpdaterTest extends TestCase {
for (double elevation = 0; elevation < 1000; elevation += 10) { for (double elevation = 0; elevation < 1000; elevation += 10) {
tripStatisticsUpdater = new TripStatisticsUpdater(System.currentTimeMillis()); tripStatisticsUpdater = new TripStatisticsUpdater(System.currentTimeMillis());
for (int i = 0; i < 100; i++) { for (int i = 0; i < 100; i++) {
assertEquals(0.0, tripStatisticsUpdater.updateElevation(elevation)); tripStatisticsUpdater.updateElevation(elevation);
assertEquals(elevation, tripStatisticsUpdater.getSmoothedElevation()); assertEquals(elevation, tripStatisticsUpdater.getSmoothedElevation());
TripStatistics tripStatistics = tripStatisticsUpdater.getTripStatistics(); TripStatistics tripStatistics = tripStatisticsUpdater.getTripStatistics();
@@ -109,7 +109,7 @@ public class TripStatisticsUpdaterTest extends TestCase {
} else { } else {
expectedGain = 1.0; expectedGain = 1.0;
} }
assertEquals(expectedGain, tripStatisticsUpdater.updateElevation(i)); tripStatisticsUpdater.updateElevation(i);
assertEquals(i, tripStatisticsUpdater.getSmoothedElevation(), assertEquals(i, tripStatisticsUpdater.getSmoothedElevation(),
TripStatisticsUpdater.ELEVATION_SMOOTHING_FACTOR / 2); TripStatisticsUpdater.ELEVATION_SMOOTHING_FACTOR / 2);
@@ -127,29 +127,22 @@ public class TripStatisticsUpdaterTest extends TestCase {
*/ */
public void testGradeSimple() throws Exception { public void testGradeSimple() throws Exception {
for (double i = 0; i < 1000; i++) { for (double i = 0; i < 1000; i++) {
/*
* The value of the elevation does not matter. This is just to fill the
* elevation buffer.
*/
tripStatisticsUpdater.updateElevation(i);
tripStatisticsUpdater.updateGrade(100, 100); tripStatisticsUpdater.updateGrade(100, 100);
if (i >= TripStatisticsUpdater.GRADE_SMOOTHING_FACTOR if (i >= TripStatisticsUpdater.GRADE_SMOOTHING_FACTOR
&& i >= TripStatisticsUpdater.ELEVATION_SMOOTHING_FACTOR) { && i >= TripStatisticsUpdater.RISE_SMOOTHING_FACTOR
&& i >= TripStatisticsUpdater.RUN_SMOOTHING_FACTOR) {
assertEquals(1.0, tripStatisticsUpdater.getTripStatistics().getMaxGrade()); assertEquals(1.0, tripStatisticsUpdater.getTripStatistics().getMaxGrade());
assertEquals(1.0, tripStatisticsUpdater.getTripStatistics().getMinGrade()); assertEquals(1.0, tripStatisticsUpdater.getTripStatistics().getMinGrade());
} }
} }
for (double i = 0; i < 1000; i++) { for (double i = 0; i < 1000; i++) {
/*
* The value of the elevation does not matter. This is just to fill the
* elevation buffer.
*/
tripStatisticsUpdater.updateElevation(i);
tripStatisticsUpdater.updateGrade(100, -100); tripStatisticsUpdater.updateGrade(100, -100);
if (i >= TripStatisticsUpdater.GRADE_SMOOTHING_FACTOR if (i >= TripStatisticsUpdater.GRADE_SMOOTHING_FACTOR
&& i >= TripStatisticsUpdater.ELEVATION_SMOOTHING_FACTOR) { && i >= TripStatisticsUpdater.RISE_SMOOTHING_FACTOR
&& i >= TripStatisticsUpdater.RUN_SMOOTHING_FACTOR) {
assertEquals(1.0, tripStatisticsUpdater.getTripStatistics().getMaxGrade()); assertEquals(1.0, tripStatisticsUpdater.getTripStatistics().getMaxGrade());
assertEquals(-1.0, tripStatisticsUpdater.getTripStatistics().getMinGrade()); // add 0.1 delta since changing min grade from 1 to -1
assertEquals(-1.0, tripStatisticsUpdater.getTripStatistics().getMinGrade(), 0.1);
} }
} }
} }
@@ -255,7 +248,7 @@ public class TripStatisticsUpdaterTest extends TestCase {
// If there are only moving locations in the track. // If there are only moving locations in the track.
if (locationOffset == 0 if (locationOffset == 0
&& (i + locationOffset) >= TripStatisticsUpdater.DISTANCE_SMOOTHING_FACTOR && (i + locationOffset) >= TripStatisticsUpdater.RUN_SMOOTHING_FACTOR
&& (i + locationOffset) >= TripStatisticsUpdater.ELEVATION_SMOOTHING_FACTOR) { && (i + locationOffset) >= TripStatisticsUpdater.ELEVATION_SMOOTHING_FACTOR) {
// 1 m / 111 m = .009 // 1 m / 111 m = .009
assertEquals(0.009, tripStatistics.getMinGrade(), 0.0001); assertEquals(0.009, tripStatistics.getMinGrade(), 0.0001);