Clean up classes in the stats package

- TripStatisticsBuilder
- TripStatistics
- ExtremityMonitor
- DoubleBuffer
This commit is contained in:
Jimmy Shih
2012-08-08 10:17:32 -07:00
parent a0b3e21b14
commit 40e2aba739
9 changed files with 680 additions and 808 deletions
@@ -306,7 +306,7 @@ public class GpxImporter extends DefaultHandler {
tripStatisticsBuilder = new TripStatisticsBuilder(0);
tripStatisticsBuilder.pauseAt(0);
}
track.setTripStatistics(tripStatisticsBuilder.getStatistics());
track.setTripStatistics(tripStatisticsBuilder.getTripStatistics());
track.setNumberOfPoints(numberOfLocations);
myTracksProviderUtils.updateTrack(track);
tracksIds.add(track.getId());
@@ -253,7 +253,7 @@ public class SendFusionTablesAsyncTask extends AbstractSendAsyncTask {
elevationBuffer.setNext(metricUnits ? location.getAltitude()
: location.getAltitude() * UnitConversions.M_TO_FT);
if (i % elevationSamplingFrequency == 0) {
distances.add(tripStatisticsBuilder.getStatistics().getTotalDistance());
distances.add(tripStatisticsBuilder.getTripStatistics().getTotalDistance());
elevations.add(elevationBuffer.getAverage());
}
lastLocation = location;
@@ -279,7 +279,7 @@ public class SendFusionTablesAsyncTask extends AbstractSendAsyncTask {
// Create an end marker
if (lastLocation != null) {
distances.add(tripStatisticsBuilder.getStatistics().getTotalDistance());
distances.add(tripStatisticsBuilder.getTripStatistics().getTotalDistance());
elevations.add(elevationBuffer.getAverage());
DescriptionGenerator descriptionGenerator = new DescriptionGeneratorImpl(context);
track.setDescription(
@@ -297,7 +297,7 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
elevationBuffer.setNext(metricUnits ? location.getAltitude()
: location.getAltitude() * UnitConversions.M_TO_FT);
if (i % elevationSamplingFrequency == 0) {
distances.add(tripStatisticsBuilder.getStatistics().getTotalDistance());
distances.add(tripStatisticsBuilder.getTripStatistics().getTotalDistance());
elevations.add(elevationBuffer.getAverage());
}
lastLocation = location;
@@ -323,7 +323,7 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
// Create an end marker
if (lastLocation != null) {
distances.add(tripStatisticsBuilder.getStatistics().getTotalDistance());
distances.add(tripStatisticsBuilder.getTripStatistics().getTotalDistance());
elevations.add(elevationBuffer.getAverage());
track.setDescription(getTrackDescription(track, distances, elevations));
@@ -654,7 +654,7 @@ public class TrackRecordingService extends Service {
}
} while (cursor.moveToPrevious());
}
statsBuilder.getStatistics().setMovingTime(stats.getMovingTime());
statsBuilder.getTripStatistics().setMovingTime(stats.getMovingTime());
statsBuilder.pauseAt(stats.getStopTime());
statsBuilder.resumeAt(System.currentTimeMillis());
} else {
@@ -834,7 +834,7 @@ public class TrackRecordingService extends Service {
// Update the current track:
if (lastRecordedLocation != null && lastRecordedLocation.getLatitude() < 90) {
TripStatistics tripStatistics = statsBuilder.getStatistics();
TripStatistics tripStatistics = statsBuilder.getTripStatistics();
tripStatistics.setStopTime(System.currentTimeMillis());
if (recordingTrack.getStartId() < 0) {
@@ -865,8 +865,8 @@ public class TrackRecordingService extends Service {
if (waypoint != null) {
waypoint.setLength(length);
waypoint.setDuration(
System.currentTimeMillis() - statsBuilder.getStatistics().getStartTime());
waypoint.setTripStatistics(waypointStatsBuilder.getStatistics());
System.currentTimeMillis() - statsBuilder.getTripStatistics().getStartTime());
waypoint.setTripStatistics(waypointStatsBuilder.getTripStatistics());
providerUtils.updateWaypoint(waypoint);
}
}
@@ -896,7 +896,7 @@ public class TrackRecordingService extends Service {
}
waypoint.setTrackId(recordingTrackId);
waypoint.setLength(length);
if (lastLocation == null || statsBuilder == null || statsBuilder.getStatistics() == null) {
if (lastLocation == null || statsBuilder == null || statsBuilder.getTripStatistics() == null) {
if (!request.isTrackStatistics()) {
return -1L;
}
@@ -910,7 +910,7 @@ public class TrackRecordingService extends Service {
waypoint.setLocation(location);
} else {
waypoint.setLocation(lastLocation);
waypoint.setDuration(lastLocation.getTime() - statsBuilder.getStatistics().getStartTime());
waypoint.setDuration(lastLocation.getTime() - statsBuilder.getTripStatistics().getStartTime());
}
Uri uri = providerUtils.insertWaypoint(waypoint);
return Long.parseLong(uri.getLastPathSegment());
@@ -958,7 +958,7 @@ public class TrackRecordingService extends Service {
// Override the duration - it's not the duration from the last waypoint, but
// the duration from the beginning of the whole track
waypoint.setDuration(time - statsBuilder.getStatistics().getStartTime());
waypoint.setDuration(time - statsBuilder.getTripStatistics().getStartTime());
// Set the rest of the waypoint data
waypoint.setType(Waypoint.TYPE_STATISTICS);
@@ -973,7 +973,7 @@ public class TrackRecordingService extends Service {
name = getString(R.string.marker_split_name_format, nextMarkerNumber);
}
waypoint.setName(name);
waypoint.setTripStatistics(waypointStatsBuilder.getStatistics());
waypoint.setTripStatistics(waypointStatsBuilder.getTripStatistics());
waypoint.setDescription(descriptionGenerator.generateWaypointDescription(waypoint));
waypoint.setIcon(getString(R.string.marker_statistics_icon_url));
@@ -1050,7 +1050,7 @@ public class TrackRecordingService extends Service {
}
public TripStatistics getTripStatistics() {
return statsBuilder.getStatistics();
return statsBuilder.getTripStatistics();
}
Location getLastLocation() {
@@ -20,66 +20,35 @@ package com.google.android.apps.mytracks.stats;
* This class maintains a buffer of doubles. This buffer is a convenient class
* for storing a series of doubles and calculating information about them. This
* is a FIFO buffer.
*
*
* @author Sandor Dornbush
*/
public class DoubleBuffer {
/**
* The location that the next write will occur at.
*/
// The location that the next write will occur at.
private int index;
/**
* The sliding buffer of doubles.
*/
// The sliding buffer of doubles.
private final double[] buffer;
/**
* Have all of the slots in the buffer been filled?
*/
// True if the buffer is full
private boolean isFull;
/**
* Creates a buffer with size elements.
*
* @param size the number of elements in the buffer
* @throws IllegalArgumentException if the size is not a positive value
* Creates a buffer with a certain size.
*
* @param size the size
*/
public DoubleBuffer(int size) {
if (size < 1) {
throw new IllegalArgumentException("The buffer size must be positive.");
throw new IllegalArgumentException("The buffer size must be greater than 1.");
}
buffer = new double[size];
reset();
}
/**
* Adds a double to the buffer. If the buffer is full the oldest element is
* overwritten.
*
* @param d the double to add
*/
public void setNext(double d) {
if (index == buffer.length) {
index = 0;
}
buffer[index] = d;
index++;
if (index == buffer.length) {
isFull = true;
}
}
/**
* Are all of the entries in the buffer used?
*/
public boolean isFull() {
return isFull;
}
/**
* Resets the buffer to the initial state.
* Resets the buffer.
*/
public void reset() {
index = 0;
@@ -87,16 +56,20 @@ public class DoubleBuffer {
}
/**
* Gets the average of values from the buffer.
*
* @return The average of the buffer
* Returns true if the buffer is full.
*/
public boolean isFull() {
return isFull;
}
/**
* Gets the average of the buffer.
*/
public double getAverage() {
int numberOfEntries = isFull ? buffer.length : index;
if (numberOfEntries == 0) {
return 0;
}
double sum = 0;
for (int i = 0; i < numberOfEntries; i++) {
sum += buffer[i];
@@ -105,15 +78,15 @@ public class DoubleBuffer {
}
/**
* Gets the average and standard deviation of the buffer.
*
* @return An array of two elements - the first is the average, and the second
* is the variance
* Gets the average and the variance of the buffer.
*
* @return an array of two elements - the first is the average, the second is
* the variance
*/
public double[] getAverageAndVariance() {
int numberOfEntries = isFull ? buffer.length : index;
if (numberOfEntries == 0) {
return new double[]{0, 0};
return new double[] { 0, 0 };
}
double sum = 0;
@@ -124,8 +97,25 @@ public class DoubleBuffer {
}
double average = sum / numberOfEntries;
return new double[]{average,
sumSquares / numberOfEntries - Math.pow(average, 2)};
double variance = sumSquares / numberOfEntries - Math.pow(average, 2);
return new double[] { average, variance };
}
/**
* Adds a double to the buffer. If the buffer is full the oldest element is
* overwritten.
*
* @param value the double to add
*/
public void setNext(double value) {
if (index == buffer.length) {
index = 0;
}
buffer[index] = value;
index++;
if (index == buffer.length) {
isFull = true;
}
}
@Override
@@ -20,352 +20,307 @@ import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.common.annotations.VisibleForTesting;
import android.location.Location;
import android.util.Log;
/**
* Statistics keeper for a trip.
* Builder for {@link TripStatistics}. For keeping statistics as a track is
* paused/resumed and new locations are added.
*
* @author Sandor Dornbush
* @author Rodrigo Damazio
*/
public class TripStatisticsBuilder {
/**
* Statistical data about the trip, which can be displayed to the user.
*/
private final TripStatistics data;
/**
* The last location that the gps reported.
*/
private Location lastLocation;
/**
* The last location that contributed to the stats. It is also the last
* location the user was found to be moving.
*/
private Location lastMovingLocation;
/**
* The current speed in meters/second as reported by the gps.
*/
private double currentSpeed;
/**
* The current grade. This value is very noisy and not reported to the user.
*/
private double currentGrade;
/**
* Is the trip currently paused?
* All trips start paused.
*/
private boolean paused = true;
/**
* A buffer of the last speed readings in meters/second.
*/
private final DoubleBuffer speedBuffer =
new DoubleBuffer(Constants.SPEED_SMOOTHING_FACTOR);
/**
* A buffer of the recent elevation readings in meters.
*/
private final DoubleBuffer elevationBuffer =
new DoubleBuffer(Constants.ELEVATION_SMOOTHING_FACTOR);
/**
* A buffer of the distance between recent gps readings in meters.
*/
private final DoubleBuffer distanceBuffer =
new DoubleBuffer(Constants.DISTANCE_SMOOTHING_FACTOR);
/**
* A buffer of the recent grade calculations.
*/
private final DoubleBuffer gradeBuffer =
new DoubleBuffer(Constants.GRADE_SMOOTHING_FACTOR);
/**
* The total number of locations in this trip.
*/
private long totalLocations = 0;
// The trip statistics.
private final TripStatistics tripStatistics;
// The minimum recording distance.
private int minRecordingDistance = PreferencesUtils.MIN_RECORDING_DISTANCE_DEFAULT;
// True if the trip is paused. All trips start as paused.
private boolean paused = true;
// The last location as reported by GPS.
private Location lastLocation;
// The last moving location that contributed to the moving statistics.
private Location lastMovingLocation;
// A buffer of the recent speed readings (m/s) for calculating max speed.
private final DoubleBuffer speedBuffer = new DoubleBuffer(Constants.SPEED_SMOOTHING_FACTOR);
// A buffer of the recent elevation readings (m).
private final DoubleBuffer elevationBuffer = new DoubleBuffer(
Constants.ELEVATION_SMOOTHING_FACTOR);
// A buffer of the recent distance readings for calculating grade.
private final DoubleBuffer distanceBuffer = new DoubleBuffer(Constants.DISTANCE_SMOOTHING_FACTOR);
// A buffer of the recent grade calculations
private final DoubleBuffer gradeBuffer = new DoubleBuffer(Constants.GRADE_SMOOTHING_FACTOR);
/**
* Creates a new trip starting at the given time.
* Creates a new trip starting at a start time.
*
* @param startTime the start time.
* @param startTime the start time
*/
public TripStatisticsBuilder(long startTime) {
data = new TripStatistics();
tripStatistics = new TripStatistics();
resumeAt(startTime);
}
/**
* Creates a new trip, starting with existing statistics data.
*
* @param statsData the statistics data to copy and start from
*/
public TripStatisticsBuilder(TripStatistics statsData) {
data = new TripStatistics(statsData);
if (data.getStartTime() > 0) {
resumeAt(data.getStartTime());
}
}
/**
* Adds a location to the current trip. This will update all of the internal
* variables with this new location.
* Creates a new trip, starting with an existing {@link TripStatistics}.
*
* @param currentLocation the current gps location
* @param systemTime the time used for calculation of totalTime. This should
* be the phone's time (not GPS time)
* @return true if the person is moving
* @param other the existing {@link TripStatistics}
*/
public boolean addLocation(Location currentLocation, long systemTime) {
if (paused) {
Log.w(TAG,
"Tried to account for location while track is paused");
return false;
public TripStatisticsBuilder(TripStatistics other) {
tripStatistics = new TripStatistics(other);
if (tripStatistics.getStartTime() > 0) {
resumeAt(tripStatistics.getStartTime());
}
totalLocations++;
double elevationDifference = updateElevation(currentLocation.getAltitude());
// Update the "instant" values:
data.setTotalTime(systemTime - data.getStartTime());
currentSpeed = currentLocation.getSpeed();
// This was the 1st location added, remember it and do nothing else:
if (lastLocation == null) {
lastLocation = currentLocation;
lastMovingLocation = currentLocation;
return false;
}
updateBounds(currentLocation);
// Don't do anything if we didn't move since last fix:
double distance = lastLocation.distanceTo(currentLocation);
if (distance < minRecordingDistance &&
currentSpeed < Constants.MAX_NO_MOVEMENT_SPEED) {
lastLocation = currentLocation;
return false;
}
data.addTotalDistance(lastMovingLocation.distanceTo(currentLocation));
updateSpeed(currentLocation.getTime(), currentSpeed,
lastLocation.getTime(), lastLocation.getSpeed());
updateGrade(distance, elevationDifference);
lastLocation = currentLocation;
lastMovingLocation = currentLocation;
return true;
}
/**
* Updates the track's bounding box to include the given location.
*/
private void updateBounds(Location location) {
data.updateLatitudeExtremities(location.getLatitude());
data.updateLongitudeExtremities(location.getLongitude());
}
/**
* Updates the elevation measurements.
* Sets the min recording distance.
*
* @param elevation the current elevation
* @param minRecordingDistance the min recording distance
*/
// @VisibleForTesting
double updateElevation(double elevation) {
double oldSmoothedElevation = getSmoothedElevation();
elevationBuffer.setNext(elevation);
double smoothedElevation = getSmoothedElevation();
data.updateElevationExtremities(smoothedElevation);
double elevationDifference = elevationBuffer.isFull()
? smoothedElevation - oldSmoothedElevation
: 0.0;
if (elevationDifference > 0) {
data.addTotalElevationGain(elevationDifference);
}
return elevationDifference;
public void setMinRecordingDistance(int minRecordingDistance) {
this.minRecordingDistance = minRecordingDistance;
}
/**
* Updates the speed measurements.
* Resumes the current track at a given time.
*
* @param updateTime the time of the speed update
* @param speed the current speed
* @param lastLocationTime the time of the last speed update
* @param lastLocationSpeed the speed of the last update
* @param time the time
*/
// @VisibleForTesting
void updateSpeed(long updateTime, double speed, long lastLocationTime,
double lastLocationSpeed) {
// We are now sure the user is moving.
long timeDifference = updateTime - lastLocationTime;
if (timeDifference < 0) {
Log.e(TAG,
"Found negative time change: " + timeDifference);
}
data.addMovingTime(timeDifference);
if (isValidSpeed(updateTime, speed, lastLocationTime, lastLocationSpeed,
speedBuffer)) {
speedBuffer.setNext(speed);
if (speed > data.getMaxSpeed()) {
data.setMaxSpeed(speed);
}
double movingSpeed = data.getAverageMovingSpeed();
if (speedBuffer.isFull() && (movingSpeed > data.getMaxSpeed())) {
data.setMaxSpeed(movingSpeed);
}
} else {
Log.d(TAG,
"TripStatistics ignoring big change: Raw Speed: " + speed
+ " old: " + lastLocationSpeed + " [" + toString() + "]");
}
}
/**
* Checks to see if this is a valid speed.
*
* @param updateTime The time at the current reading
* @param speed The current speed
* @param lastLocationTime The time at the last location
* @param lastLocationSpeed Speed at the last location
* @param speedBuffer A buffer of recent readings
* @return True if this is likely a valid speed
*/
public static boolean isValidSpeed(long updateTime, double speed,
long lastLocationTime, double lastLocationSpeed,
DoubleBuffer speedBuffer) {
// We don't want to count 0 towards the speed.
if (speed == 0) {
return false;
}
// We are now sure the user is moving.
long timeDifference = updateTime - lastLocationTime;
// There are a lot of noisy speed readings.
// Do the cheapest checks first, most expensive last.
// The following code will ignore unlikely to be real readings.
// - 128 m/s seems to be an internal android error code.
if (Math.abs(speed - 128) < 1) {
return false;
}
// Another check for a spurious reading. See if the path seems physically
// likely. Ignore any speeds that imply accelaration greater than 2g's
// Really who can accelerate faster?
double speedDifference = Math.abs(lastLocationSpeed - speed);
if (speedDifference > Constants.MAX_ACCELERATION * timeDifference) {
return false;
}
// There are three additional checks if the reading gets this far:
// - Only use the speed if the buffer is full
// - Check that the current speed is less than 10x the recent smoothed speed
// - Double check that the current speed does not imply crazy acceleration
double smoothedSpeed = speedBuffer.getAverage();
double smoothedDiff = Math.abs(smoothedSpeed - speed);
return !speedBuffer.isFull() ||
(speed < smoothedSpeed * 10
&& smoothedDiff < Constants.MAX_ACCELERATION * timeDifference);
}
/**
* Updates the grade measurements.
*
* @param distance the distance the user just traveled
* @param elevationDifference the elevation difference between the current
* reading and the previous reading
*/
// @VisibleForTesting
void updateGrade(double distance, double elevationDifference) {
distanceBuffer.setNext(distance);
double smoothedDistance = distanceBuffer.getAverage();
// With the error in the altitude measurement it is dangerous to divide
// by anything less than 5.
if (!elevationBuffer.isFull() || !distanceBuffer.isFull()
|| smoothedDistance < 5.0) {
public void resumeAt(long time) {
if (!paused) {
return;
}
currentGrade = elevationDifference / smoothedDistance;
gradeBuffer.setNext(currentGrade);
data.updateGradeExtremities(gradeBuffer.getAverage());
tripStatistics.setStartTime(time);
tripStatistics.setStopTime(-1L);
paused = false;
lastLocation = null;
lastMovingLocation = null;
speedBuffer.reset();
elevationBuffer.reset();
distanceBuffer.reset();
gradeBuffer.reset();
}
/**
* Pauses the track at the given time.
* Pauses the track at a given time.
*
* @param time the time to pause at
*/
public void pauseAt(long time) {
if (paused) { return; }
data.setStopTime(time);
data.setTotalTime(time - data.getStartTime());
lastLocation = null; // Make sure the counter restarts.
if (paused) {
return;
}
tripStatistics.setStopTime(time);
// TODO: total time needs to take into account pauses
tripStatistics.setTotalTime(time - tripStatistics.getStartTime());
paused = true;
}
/**
* Resumes the current track at the given time.
*
* @param time the time to resume at
* Gets the trip statistics.
*/
public void resumeAt(long time) {
if (!paused) { return; }
// TODO: The times are bogus if the track is paused then resumed again
data.setStartTime(time);
data.setStopTime(-1);
paused = false;
}
@Override
public String toString() {
return "TripStatistics { Data: " + data.toString()
+ "; Total Locations: " + totalLocations
+ "; Paused: " + paused
+ "; Current speed: " + currentSpeed
+ "; Current grade: " + currentGrade
+ "}";
public TripStatistics getTripStatistics() {
// Take a snapshot - we don't want anyone messing with our internals
return new TripStatistics(tripStatistics);
}
/**
* Returns the amount of time the user has been idle or 0 if they are moving.
* Returns the amount of time the user has been idle or 0 if he is moving.
*/
public long getIdleTime() {
if (lastLocation == null || lastMovingLocation == null)
if (lastLocation == null || lastMovingLocation == null) {
return 0;
}
return lastLocation.getTime() - lastMovingLocation.getTime();
}
/**
* Gets the current elevation smoothed over several readings. The elevation
* data is very noisy so it is better to use the smoothed elevation than the
* raw elevation for many tasks.
*
* @return The elevation smoothed over several readings
* Gets the smoothed elevation over several readings. The elevation readings
* is noisy so the smoothed elevation is better than the raw elevation for
* many tasks.
*/
public double getSmoothedElevation() {
return elevationBuffer.getAverage();
}
public TripStatistics getStatistics() {
// Take a snapshot - we don't want anyone messing with our internals
return new TripStatistics(data);
/**
* Adds a location. This will update all of the internal variables with this
* new location.
*
* @param location the location
* @param systemTime the system time for calculating totalTime. This should be
* the phone's system time (not GPS time)
* @return true if the person is moving
*/
public boolean addLocation(Location location, long systemTime) {
if (paused) {
Log.w(TAG, "Track is paused. Ignore addLocation.");
return false;
}
tripStatistics.setTotalTime(systemTime - tripStatistics.getStartTime());
double elevationDifference = updateElevation(location.getAltitude());
tripStatistics.updateLatitudeExtremities(location.getLatitude());
tripStatistics.updateLongitudeExtremities(location.getLongitude());
// If this is the first location, remember it and return.
if (lastLocation == null || lastMovingLocation == null) {
lastLocation = location;
lastMovingLocation = location;
return false;
}
// Don't do anything more if we didn't move since the last location.
double distance = lastLocation.distanceTo(location);
if (distance < minRecordingDistance && location.getSpeed() < Constants.MAX_NO_MOVEMENT_SPEED) {
lastLocation = location;
return false;
}
long timeDifference = location.getTime() - lastLocation.getTime();
if (timeDifference < 0) {
Log.e(TAG, "Negative time difference: " + timeDifference);
lastLocation = location;
return false;
}
tripStatistics.addTotalDistance(lastMovingLocation.distanceTo(location));
tripStatistics.addMovingTime(timeDifference);
updateSpeed(
location.getTime(), location.getSpeed(), lastLocation.getTime(), lastLocation.getSpeed());
updateGrade(distance, elevationDifference);
lastLocation = location;
lastMovingLocation = location;
return true;
}
public void setMinRecordingDistance(int minRecordingDistance) {
this.minRecordingDistance = minRecordingDistance;
/**
* Updates a speed reading. Assumes the user is moving.
*
* @param time the time
* @param speed the speed
* @param lastLocationTime the last location time
* @param lastLocationSpeed the last location speed
*/
@VisibleForTesting
void updateSpeed(long time, double speed, long lastLocationTime, double lastLocationSpeed) {
if (!isValidSpeed(time, speed, lastLocationTime, lastLocationSpeed, speedBuffer)) {
Log.d(TAG, "Invalid speed. speed: " + speed + " lastLocationSpeed: " + lastLocationSpeed);
return;
}
speedBuffer.setNext(speed);
if (speed > tripStatistics.getMaxSpeed()) {
tripStatistics.setMaxSpeed(speed);
}
double movingSpeed = tripStatistics.getAverageMovingSpeed();
if (speedBuffer.isFull() && movingSpeed > tripStatistics.getMaxSpeed()) {
tripStatistics.setMaxSpeed(movingSpeed);
}
}
/**
* Updates an elevation reading.
*
* @param elevation the elevation
*/
@VisibleForTesting
double updateElevation(double elevation) {
double oldAverage = elevationBuffer.getAverage();
elevationBuffer.setNext(elevation);
double newAverage = elevationBuffer.getAverage();
tripStatistics.updateElevationExtremities(newAverage);
double elevationDifference = elevationBuffer.isFull() ? newAverage - oldAverage : 0.0;
if (elevationDifference > 0) {
tripStatistics.addTotalElevationGain(elevationDifference);
}
return elevationDifference;
}
/**
* Updates a grade reading.
*
* @param distance the distance the user just traveled
* @param elevationDifference the elevation difference between the current
* reading and the previous reading
*/
@VisibleForTesting
void updateGrade(double distance, double elevationDifference) {
distanceBuffer.setNext(distance);
double smoothedDistance = distanceBuffer.getAverage();
/*
* With the error in the altitude measurement it is dangerous to divide by
* anything less than 5.
*/
if (!elevationBuffer.isFull() || !distanceBuffer.isFull() || smoothedDistance < 5.0) {
return;
}
gradeBuffer.setNext(elevationDifference / smoothedDistance);
tripStatistics.updateGradeExtremities(gradeBuffer.getAverage());
}
/**
* Returns true if the speed is valid.
*
* @param time the time
* @param speed the speed
* @param lastLocationTime the last location time
* @param lastLocationSpeed the last location speed
* @param speedBuffer a buffer of speed readings
*/
public static boolean isValidSpeed(long time, double speed, long lastLocationTime,
double lastLocationSpeed, DoubleBuffer speedBuffer) {
/*
* There are a lot of noisy speed readings. Do the cheapest checks first,
* most expensive last.
*/
if (speed == 0) {
return false;
}
/*
* The following code will ignore unlikely readings. 128 m/s seems to be an
* internal android error code.
*/
if (Math.abs(speed - 128) < 1) {
return false;
}
/*
* See if the speed seems physically likely. Ignore any speeds that imply
* acceleration greater than 2g.
*/
long timeDifference = time - lastLocationTime;
double speedDifference = Math.abs(lastLocationSpeed - speed);
if (speedDifference > Constants.MAX_ACCELERATION * timeDifference) {
return false;
}
/*
* Only check if the speed buffer is full. Check that the speed is less than
* 10X the smoothed average and the speed difference doesn't imply 2g
* acceleration.
*/
if (!speedBuffer.isFull()) {
return true;
}
double average = speedBuffer.getAverage();
double diff = Math.abs(average - speed);
return (speed < average * 10) && (diff < Constants.MAX_ACCELERATION * timeDifference);
}
}