forked from upstream-mirrors/OpenTracks
Improve elevation and grade calculation to take into account locations without altitude.
This commit is contained in:
@@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+9
-16
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user