diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/GpxImporter.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/GpxImporter.java index 06f155df2..1eb63e69a 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/file/GpxImporter.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/GpxImporter.java @@ -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()); diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/fusiontables/SendFusionTablesAsyncTask.java b/MyTracks/src/com/google/android/apps/mytracks/io/fusiontables/SendFusionTablesAsyncTask.java index 72810f2f0..31370e64a 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/fusiontables/SendFusionTablesAsyncTask.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/fusiontables/SendFusionTablesAsyncTask.java @@ -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( diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/maps/SendMapsAsyncTask.java b/MyTracks/src/com/google/android/apps/mytracks/io/maps/SendMapsAsyncTask.java index 660e0ead4..e46720b8c 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/maps/SendMapsAsyncTask.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/maps/SendMapsAsyncTask.java @@ -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)); diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java b/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java index e50bac9f8..a11a2a969 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java @@ -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() { diff --git a/MyTracks/src/com/google/android/apps/mytracks/stats/DoubleBuffer.java b/MyTracks/src/com/google/android/apps/mytracks/stats/DoubleBuffer.java index 356664617..7c86f0ba8 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/stats/DoubleBuffer.java +++ b/MyTracks/src/com/google/android/apps/mytracks/stats/DoubleBuffer.java @@ -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 diff --git a/MyTracks/src/com/google/android/apps/mytracks/stats/TripStatisticsBuilder.java b/MyTracks/src/com/google/android/apps/mytracks/stats/TripStatisticsBuilder.java index 3f6f7e770..18f7103b9 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/stats/TripStatisticsBuilder.java +++ b/MyTracks/src/com/google/android/apps/mytracks/stats/TripStatisticsBuilder.java @@ -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); } } diff --git a/MyTracksLib/src/com/google/android/apps/mytracks/stats/ExtremityMonitor.java b/MyTracksLib/src/com/google/android/apps/mytracks/stats/ExtremityMonitor.java index bb65ee76d..8a4618588 100644 --- a/MyTracksLib/src/com/google/android/apps/mytracks/stats/ExtremityMonitor.java +++ b/MyTracksLib/src/com/google/android/apps/mytracks/stats/ExtremityMonitor.java @@ -18,19 +18,15 @@ package com.google.android.apps.mytracks.stats; /** * A helper class that tracks a minimum and a maximum of a variable. - * + * * @author Sandor Dornbush */ public class ExtremityMonitor { - /** - * The smallest value seen so far. - */ + // The smallest value seen so far. private double min; - /** - * The largest value seen so far. - */ + // The largest value seen so far. private double max; public ExtremityMonitor() { @@ -38,9 +34,31 @@ public class ExtremityMonitor { } /** - * Updates the min and the max with the new value. - * - * @param value the new value for the monitor + * Resets this object to it's initial state where the min and max are unknown. + */ + public void reset() { + min = Double.POSITIVE_INFINITY; + max = Double.NEGATIVE_INFINITY; + } + + /** + * Gets the minimum value seen. + */ + public double getMin() { + return min; + } + + /** + * Gets the maximum value seen. + */ + public double getMax() { + return max; + } + + /** + * Updates the min and the max with a new value. + * + * @param value the new value * @return true if an extremity was found */ public boolean update(double value) { @@ -56,34 +74,11 @@ public class ExtremityMonitor { return changed; } - /** - * Gets the minimum value seen. - * - * @return The minimum value passed into the update() function - */ - public double getMin() { - return min; - } - - /** - * Gets the maximum value seen. - * - * @return The maximum value passed into the update() function - */ - public double getMax() { - return max; - } - - /** - * Resets this object to it's initial state where the min and max are unknown. - */ - public void reset() { - min = Double.POSITIVE_INFINITY; - max = Double.NEGATIVE_INFINITY; - } - /** * Sets the minimum and maximum values. + * + * @param min the minimum value + * @param max the maximum value */ public void set(double min, double max) { this.min = min; @@ -92,6 +87,8 @@ public class ExtremityMonitor { /** * Sets the minimum value. + * + * @param min the minimum value */ public void setMin(double min) { this.min = min; @@ -99,16 +96,20 @@ public class ExtremityMonitor { /** * Sets the maximum value. + * + * @param max the maximum value */ public void setMax(double max) { this.max = max; } + /** + * Returns true if has data. + */ public boolean hasData() { - return min != Double.POSITIVE_INFINITY - && max != Double.NEGATIVE_INFINITY; + return min != Double.POSITIVE_INFINITY && max != Double.NEGATIVE_INFINITY; } - + @Override public String toString() { return "Min: " + min + " Max: " + max; diff --git a/MyTracksLib/src/com/google/android/apps/mytracks/stats/TripStatistics.java b/MyTracksLib/src/com/google/android/apps/mytracks/stats/TripStatistics.java index 4d82d2814..892c0d590 100644 --- a/MyTracksLib/src/com/google/android/apps/mytracks/stats/TripStatistics.java +++ b/MyTracksLib/src/com/google/android/apps/mytracks/stats/TripStatistics.java @@ -13,182 +13,226 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.google.android.apps.mytracks.stats; import android.os.Parcel; import android.os.Parcelable; /** - * Statistical data about a trip. - * The data in this class should be filled out by TripStatisticsBuilder. - * + * Statistical data about a trip. The data in this class should be filled out by + * TripStatisticsBuilder. + *
* TODO: hashCode and equals
- *
+ *
* @author Rodrigo Damazio
*/
public class TripStatistics implements Parcelable {
- /**
- * The start time for the trip. This is system time which might not match gps
- * time.
- */
+ // The trip start time. This is the system time, might not match the GPs time.
private long startTime = -1L;
- /**
- * The stop time for the trip. This is the system time which might not match
- * gps time.
- */
+ // The trip stop time. This is the system time, might not match the GPS time.
private long stopTime = -1L;
- /**
- * The total time that we believe the user was traveling in milliseconds.
- */
- private long movingTime;
-
- /**
- * The total time of the trip in milliseconds.
- * This is only updated when new points are received, so it may be stale.
- */
- private long totalTime;
-
- /**
- * The total distance in meters that the user traveled on this trip.
- */
+ // The total trip distance (meters).
private double totalDistance;
- /**
- * The total elevation gained on this trip in meters.
- */
- private double totalElevationGain;
+ // The total time (ms). Updated when new points are received, may be stale.
+ private long totalTime;
- /**
- * The maximum speed in meters/second reported that we believe to be a valid
- * speed.
- */
- private double maxSpeed;
+ // The total moving time (ms). Based on when we believe the user is traveling.
+ private long movingTime;
- /**
- * The min and max latitude values seen in this trip.
- */
+ // The min and max latitude seen in this trip.
private final ExtremityMonitor latitudeExtremities = new ExtremityMonitor();
- /**
- * The min and max longitude values seen in this trip.
- */
+ // The min and max longitude seen in this trip.
private final ExtremityMonitor longitudeExtremities = new ExtremityMonitor();
- /**
- * The min and max elevation seen on this trip in meters.
- */
+ // The maximum speed (meters/second) that we believe is valid.
+ private double maxSpeed;
+
+ // The min and max elevation (meters) seen on this trip.
private final ExtremityMonitor elevationExtremities = new ExtremityMonitor();
- /**
- * The minimum and maximum grade calculations on this trip.
- */
+ // The total elevation gained (meters).
+ private double totalElevationGain;
+
+ // The min and max grade seen on this trip.
private final ExtremityMonitor gradeExtremities = new ExtremityMonitor();
/**
* Default constructor.
*/
- public TripStatistics() {
- }
+ public TripStatistics() {}
/**
* Copy constructor.
- *
+ *
* @param other another statistics data object to copy from
*/
public TripStatistics(TripStatistics other) {
- this.maxSpeed = other.maxSpeed;
- this.movingTime = other.movingTime;
this.startTime = other.startTime;
this.stopTime = other.stopTime;
this.totalDistance = other.totalDistance;
- this.totalElevationGain = other.totalElevationGain;
this.totalTime = other.totalTime;
-
- this.latitudeExtremities.set(other.latitudeExtremities.getMin(),
- other.latitudeExtremities.getMax());
- this.longitudeExtremities.set(other.longitudeExtremities.getMin(),
- other.longitudeExtremities.getMax());
- this.elevationExtremities.set(other.elevationExtremities.getMin(),
- other.elevationExtremities.getMax());
- this.gradeExtremities.set(other.gradeExtremities.getMin(),
- other.gradeExtremities.getMax());
+ this.movingTime = other.movingTime;
+ this.latitudeExtremities.set(
+ other.latitudeExtremities.getMin(), other.latitudeExtremities.getMax());
+ this.longitudeExtremities.set(
+ other.longitudeExtremities.getMin(), other.longitudeExtremities.getMax());
+ this.maxSpeed = other.maxSpeed;
+ this.elevationExtremities.set(
+ other.elevationExtremities.getMin(), other.elevationExtremities.getMax());
+ this.totalElevationGain = other.totalElevationGain;
+ this.gradeExtremities.set(other.gradeExtremities.getMin(), other.gradeExtremities.getMax());
}
/**
- * Combines these statistics with those from another object.
- * This assumes that the time periods covered by each do not intersect.
- *
- * @param other the other waypoint
+ * Combines these statistics with those from another object. This assumes that
+ * the time periods covered by each do not intersect.
+ *
+ * @param other another statistics data object
*/
public void merge(TripStatistics other) {
startTime = Math.min(startTime, other.startTime);
stopTime = Math.max(stopTime, other.stopTime);
+ totalDistance += other.totalDistance;
totalTime += other.totalTime;
movingTime += other.movingTime;
- totalDistance += other.totalDistance;
- totalElevationGain += other.totalElevationGain;
- maxSpeed = Math.max(maxSpeed, other.maxSpeed);
-
- latitudeExtremities.update(other.latitudeExtremities.getMax());
latitudeExtremities.update(other.latitudeExtremities.getMin());
- longitudeExtremities.update(other.longitudeExtremities.getMax());
+ latitudeExtremities.update(other.latitudeExtremities.getMax());
longitudeExtremities.update(other.longitudeExtremities.getMin());
- elevationExtremities.update(other.elevationExtremities.getMax());
+ longitudeExtremities.update(other.longitudeExtremities.getMax());
+ maxSpeed = Math.max(maxSpeed, other.maxSpeed);
elevationExtremities.update(other.elevationExtremities.getMin());
- gradeExtremities.update(other.gradeExtremities.getMax());
+ elevationExtremities.update(other.elevationExtremities.getMax());
+ totalElevationGain += other.totalElevationGain;
gradeExtremities.update(other.gradeExtremities.getMin());
+ gradeExtremities.update(other.gradeExtremities.getMax());
}
/**
- * Gets the time that this track started.
- *
- * @return The number of milliseconds since epoch to the time when this track
- * started
+ * Gets the trip start time. The number of milliseconds since epoch.
*/
public long getStartTime() {
return startTime;
}
/**
- * Gets the time that this track stopped.
- *
- * @return The number of milliseconds since epoch to the time when this track
- * stopped
+ * Gets the trip stop time. The number of milliseconds since epoch.
*/
public long getStopTime() {
return stopTime;
}
/**
- * Gets the total time that this track has been active.
- * This statistic is only updated when a new point is added to the statistics,
- * so it may be off. If you need to calculate the proper total time, use
- * {@link #getStartTime} with the current time.
- *
- * @return The total number of milliseconds the track was active
- */
- public long getTotalTime() {
- return totalTime;
- }
-
- /**
- * Gets the total distance the user traveled.
- *
- * @return The total distance traveled in meters
+ * Gets the total distance the user traveled in meters.
*/
public double getTotalDistance() {
return totalDistance;
}
/**
- * Gets the the average speed the user traveled.
- * This calculation only takes into account the displacement until the last
- * point that was accounted for in statistics.
- *
- * @return The average speed in m/s
+ * Gets the total time in milliseconds that this track has been active. This
+ * statistic is only updated when a new point is added to the statistics, so
+ * it may be off. If you need to calculate the proper total time, use
+ * {@link #getStartTime} with the current time.
+ */
+ public long getTotalTime() {
+ return totalTime;
+ }
+
+ /**
+ * Gets the moving time in milliseconds.
+ */
+ public long getMovingTime() {
+ return movingTime;
+ }
+
+ /**
+ * Gets the topmost position (highest latitude) of the track, in signed
+ * degrees.
+ */
+ public double getTopDegrees() {
+ return latitudeExtremities.getMax();
+ }
+
+ /**
+ * Gets the topmost position (highest latitude) of the track, in signed
+ * millions of degrees.
+ */
+ public int getTop() {
+ return (int) (latitudeExtremities.getMax() * 1E6);
+ }
+
+ /**
+ * Gets the bottommost position (lowest latitude) of the track, in signed
+ * degrees.
+ */
+ public double getBottomDegrees() {
+ return latitudeExtremities.getMin();
+ }
+
+ /**
+ * Gets the bottommost position (lowest latitude) of the track, in signed
+ * millions of degrees.
+ */
+ public int getBottom() {
+ return (int) (latitudeExtremities.getMin() * 1E6);
+ }
+
+ /**
+ * Gets the leftmost position (lowest longitude) of the track, in signed
+ * degrees.
+ */
+ public double getLeftDegrees() {
+ return longitudeExtremities.getMin();
+ }
+
+ /**
+ * Gets the leftmost position (lowest longitude) of the track, in signed
+ * millions of degrees.
+ */
+ public int getLeft() {
+ return (int) (longitudeExtremities.getMin() * 1E6);
+ }
+
+ /**
+ * Gets the rightmost position (highest longitude) of the track, in signed
+ * degrees.
+ */
+ public double getRightDegrees() {
+ return longitudeExtremities.getMax();
+ }
+
+ /**
+ * Gets the rightmost position (highest longitude) of the track, in signed
+ * millions of degrees.
+ */
+ public int getRight() {
+ return (int) (longitudeExtremities.getMax() * 1E6);
+ }
+
+ /**
+ * Gets the mean latitude position of the track, in signed degrees.
+ */
+ public double getMeanLatitude() {
+ return (getBottomDegrees() + getTopDegrees()) / 2.0;
+ }
+
+ /**
+ * Gets the mean longitude position of the track, in signed degrees.
+ */
+ public double getMeanLongitude() {
+ return (getLeftDegrees() + getRightDegrees()) / 2.0;
+ }
+
+ /**
+ * Gets the average speed in meters/second. This calculation only takes into
+ * account the displacement until the last point that was accounted for in
+ * statistics.
*/
public double getAverageSpeed() {
if (totalTime == 0L) {
@@ -198,10 +242,7 @@ public class TripStatistics implements Parcelable {
}
/**
- * Gets the the average speed the user traveled when they were actively
- * moving.
- *
- * @return The average moving speed in m/s
+ * Gets the average moving speed in meters/second.
*/
public double getAverageMovingSpeed() {
if (movingTime == 0L) {
@@ -211,261 +252,80 @@ public class TripStatistics implements Parcelable {
}
/**
- * Gets the the maximum speed for this track.
- *
- * @return The maximum speed in m/s
+ * Gets the maximum speed in meters/second.
*/
public double getMaxSpeed() {
return maxSpeed;
}
/**
- * Gets the moving time.
- *
- * @return The total number of milliseconds the user was moving
- */
- public long getMovingTime() {
- return movingTime;
- }
-
- /**
- * Gets the total elevation gain for this trip. This is calculated as the sum
- * of all positive differences in the smoothed elevation.
- *
- * @return The elevation gain in meters for this trip
- */
- public double getTotalElevationGain() {
- return totalElevationGain;
- }
-
- /**
- * Returns the leftmost position (lowest longitude) of the track, in signed degrees.
- */
- public double getLeftDegrees() {
- return longitudeExtremities.getMin();
- }
-
- /**
- * Returns the leftmost position (lowest longitude) of the track, in signed millions of degrees.
- */
- public int getLeft() {
- return (int) (longitudeExtremities.getMin() * 1E6);
- }
-
- /**
- * Returns the rightmost position (highest longitude) of the track, in signed degrees.
- */
- public double getRightDegrees() {
- return longitudeExtremities.getMax();
- }
-
- /**
- * Returns the rightmost position (highest longitude) of the track, in signed millions of degrees.
- */
- public int getRight() {
- return (int) (longitudeExtremities.getMax() * 1E6);
- }
-
- /**
- * Returns the bottommost position (lowest latitude) of the track, in signed degrees.
- */
- public double getBottomDegrees() {
- return latitudeExtremities.getMin();
- }
-
- /**
- * Returns the bottommost position (lowest latitude) of the track, in signed millions of degrees.
- */
- public int getBottom() {
- return (int) (latitudeExtremities.getMin() * 1E6);
- }
-
- /**
- * Returns the topmost position (highest latitude) of the track, in signed degrees.
- */
- public double getTopDegrees() {
- return latitudeExtremities.getMax();
- }
-
- /**
- * Returns the topmost position (highest latitude) of the track, in signed millions of degrees.
- */
- public int getTop() {
- return (int) (latitudeExtremities.getMax() * 1E6);
- }
-
- /**
- * Returns the mean position (center latitude) of the track, in signed degrees.
- */
- public double getMeanLatitude() {
- return (getBottomDegrees() + getTopDegrees()) / 2.0;
- }
-
- /**
- * Returns the mean position (center longitude) of the track, in signed degrees.
- */
- public double getMeanLongitude() {
- return (getLeftDegrees() + getRightDegrees()) / 2.0;
- }
-
- /**
- * Gets the minimum elevation seen on this trip. This is calculated from the
- * smoothed elevation so this can actually be more than the current elevation.
- *
- * @return The smallest elevation reading for this trip in meters
+ * Gets the minimum elevation. This is calculated from the smoothed elevation
+ * so this can actually be more than the current elevation.
*/
public double getMinElevation() {
return elevationExtremities.getMin();
}
/**
- * Gets the maximum elevation seen on this trip. This is calculated from the
- * smoothed elevation so this can actually be less than the current elevation.
- *
- * @return The largest elevation reading for this trip in meters
+ * Gets the maximum elevation. This is calculated from the smoothed elevation
+ * so this can actually be less than the current elevation.
*/
public double getMaxElevation() {
return elevationExtremities.getMax();
}
+ /**
+ * Gets the total elevation gain in meters. This is calculated as the sum of
+ * all positive differences in the smoothed elevation.
+ */
+ public double getTotalElevationGain() {
+ return totalElevationGain;
+ }
+
+ /**
+ * Gets the minimum grade for this trip.
+ */
+ public double getMinGrade() {
+ return gradeExtremities.getMin();
+ }
+
/**
* Gets the maximum grade for this trip.
- *
- * @return The maximum grade for this trip as a fraction
*/
public double getMaxGrade() {
return gradeExtremities.getMax();
}
/**
- * Gets the minimum grade for this trip.
- *
- * @return The minimum grade for this trip as a fraction
- */
- public double getMinGrade() {
- return gradeExtremities.getMin();
- }
-
- // Setters - to be used when restoring state or loading from the DB
-
- /**
- * Sets the start time for this trip.
- *
- * @param startTime the start time, in milliseconds since the epoch
+ * Sets the trip start time.
+ *
+ * @param startTime the trip start time in milliseconds since the epoch
*/
public void setStartTime(long startTime) {
this.startTime = startTime;
}
/**
- * Sets the stop time for this trip.
- *
- * @param stopTime the stop time, in milliseconds since the epoch
+ * Sets the trip stop time.
+ *
+ * @param stopTime the stop time in milliseconds since the epoch
*/
public void setStopTime(long stopTime) {
this.stopTime = stopTime;
}
- /**
- * Sets the total moving time.
- *
- * @param movingTime the moving time in milliseconds
- */
- public void setMovingTime(long movingTime) {
- this.movingTime = movingTime;
- }
-
- /**
- * Sets the total trip time.
- *
- * @param totalTime the total trip time in milliseconds
- */
- public void setTotalTime(long totalTime) {
- this.totalTime = totalTime;
- }
-
/**
* Sets the total trip distance.
- *
+ *
* @param totalDistance the trip distance in meters
*/
public void setTotalDistance(double totalDistance) {
this.totalDistance = totalDistance;
}
- /**
- * Sets the total elevation variation during the trip.
- *
- * @param totalElevationGain the elevation variation in meters
- */
- public void setTotalElevationGain(double totalElevationGain) {
- this.totalElevationGain = totalElevationGain;
- }
-
- /**
- * Sets the maximum speed reached during the trip.
- *
- * @param maxSpeed the maximum speed in meters per second
- */
- public void setMaxSpeed(double maxSpeed) {
- this.maxSpeed = maxSpeed;
- }
-
- /**
- * Sets the minimum elevation reached during the trip.
- *
- * @param elevation the minimum elevation in meters
- */
- public void setMinElevation(double elevation) {
- elevationExtremities.setMin(elevation);
- }
-
- /**
- * Sets the maximum elevation reached during the trip.
- *
- * @param elevation the maximum elevation in meters
- */
- public void setMaxElevation(double elevation) {
- elevationExtremities.setMax(elevation);
- }
-
- /**
- * Sets the minimum grade obtained during the trip.
- *
- * @param grade the grade as a fraction (-1.0 would mean vertical downwards)
- */
- public void setMinGrade(double grade) {
- gradeExtremities.setMin(grade);
- }
-
- /**
- * Sets the maximum grade obtained during the trip).
- *
- * @param grade the grade as a fraction (1.0 would mean vertical upwards)
- */
- public void setMaxGrade(double grade) {
- gradeExtremities.setMax(grade);
- }
-
- /**
- * Sets the bounding box for this trip.
- * The unit for all parameters is signed decimal degrees (degrees * 1E6).
- *
- * @param leftE6 the westmost longitude reached
- * @param topE6 the northmost latitude reached
- * @param rightE6 the eastmost longitude reached
- * @param bottomE6 the southmost latitude reached
- */
- public void setBounds(int leftE6, int topE6, int rightE6, int bottomE6) {
- latitudeExtremities.set(bottomE6 / 1E6, topE6 / 1E6);
- longitudeExtremities.set(leftE6 / 1E6, rightE6 / 1E6);
- }
-
- // Data manipulation methods
-
/**
* Adds to the current total distance.
- *
+ *
* @param distance the distance to add in meters
*/
void addTotalDistance(double distance) {
@@ -473,17 +333,26 @@ public class TripStatistics implements Parcelable {
}
/**
- * Adds to the total elevation variation.
- *
- * @param gain the elevation variation in meters
+ * Sets the trip total time.
+ *
+ * @param totalTime the trip total time in milliseconds
*/
- void addTotalElevationGain(double gain) {
- totalElevationGain += gain;
+ public void setTotalTime(long totalTime) {
+ this.totalTime = totalTime;
}
/**
- * Adds to the total moving time of the trip.
- *
+ * Sets the trip total moving time.
+ *
+ * @param movingTime the trip total moving time in milliseconds
+ */
+ public void setMovingTime(long movingTime) {
+ this.movingTime = movingTime;
+ }
+
+ /**
+ * Adds to the trip total moving time.
+ *
* @param time the time in milliseconds
*/
void addMovingTime(long time) {
@@ -491,8 +360,22 @@ public class TripStatistics implements Parcelable {
}
/**
- * Accounts for a new latitude value for the bounding box.
- *
+ * Sets the bounding box for this trip. The unit for all parameters is signed
+ * millions of degree (degrees * 1E6).
+ *
+ * @param leftE6 the leftmost longitude reached
+ * @param topE6 the topmost latitude reached
+ * @param rightE6 the rightmost longitude reached
+ * @param bottomE6 the bottommost latitude reached
+ */
+ public void setBounds(int leftE6, int topE6, int rightE6, int bottomE6) {
+ latitudeExtremities.set(bottomE6 / 1E6, topE6 / 1E6);
+ longitudeExtremities.set(leftE6 / 1E6, rightE6 / 1E6);
+ }
+
+ /**
+ * Updates a new latitude value.
+ *
* @param latitude the latitude value in signed decimal degrees
*/
void updateLatitudeExtremities(double latitude) {
@@ -500,8 +383,8 @@ public class TripStatistics implements Parcelable {
}
/**
- * Accounts for a new longitude value for the bounding box.
- *
+ * Updates a new longitude value.
+ *
* @param longitude the longitude value in signed decimal degrees
*/
void updateLongitudeExtremities(double longitude) {
@@ -509,8 +392,35 @@ public class TripStatistics implements Parcelable {
}
/**
- * Accounts for a new elevation value for the bounding box.
- *
+ * Sets the maximum speed.
+ *
+ * @param maxSpeed the maximum speed in meters/second
+ */
+ public void setMaxSpeed(double maxSpeed) {
+ this.maxSpeed = maxSpeed;
+ }
+
+ /**
+ * Sets the minimum elevation.
+ *
+ * @param elevation the minimum elevation in meters
+ */
+ public void setMinElevation(double elevation) {
+ elevationExtremities.setMin(elevation);
+ }
+
+ /**
+ * Sets the maximum elevation.
+ *
+ * @param elevation the maximum elevation in meters
+ */
+ public void setMaxElevation(double elevation) {
+ elevationExtremities.setMax(elevation);
+ }
+
+ /**
+ * Updates a new elevation.
+ *
* @param elevation the elevation value in meters
*/
void updateElevationExtremities(double elevation) {
@@ -518,49 +428,77 @@ public class TripStatistics implements Parcelable {
}
/**
- * Accounts for a new grade value.
- *
+ * Sets the total elevation gain.
+ *
+ * @param totalElevationGain the elevation gain in meters
+ */
+ public void setTotalElevationGain(double totalElevationGain) {
+ this.totalElevationGain = totalElevationGain;
+ }
+
+ /**
+ * Adds to the total elevation gain.
+ *
+ * @param gain the elevation gain in meters
+ */
+ void addTotalElevationGain(double gain) {
+ totalElevationGain += gain;
+ }
+
+ /**
+ * Sets the minimum grade.
+ *
+ * @param grade the grade as a fraction (-1.0 would mean vertical downwards)
+ */
+ public void setMinGrade(double grade) {
+ gradeExtremities.setMin(grade);
+ }
+
+ /**
+ * Sets the maximum grade.
+ *
+ * @param grade the grade as a fraction (1.0 would mean vertical upwards)
+ */
+ public void setMaxGrade(double grade) {
+ gradeExtremities.setMax(grade);
+ }
+
+ /**
+ * Updates a new grade value.
+ *
* @param grade the grade value as a fraction
*/
void updateGradeExtremities(double grade) {
gradeExtremities.update(grade);
}
- // String conversion
-
@Override
public String toString() {
- return "TripStatistics { Start Time: " + getStartTime()
- + "; Total Time: " + getTotalTime()
- + "; Moving Time: " + getMovingTime()
- + "; Total Distance: " + getTotalDistance()
- + "; Elevation Gain: " + getTotalElevationGain()
- + "; Min Elevation: " + getMinElevation()
- + "; Max Elevation: " + getMaxElevation()
- + "; Average Speed: " + getAverageMovingSpeed()
- + "; Min Grade: " + getMinGrade()
- + "; Max Grade: " + getMaxGrade()
- + "}";
+ return "TripStatistics { Start Time: " + getStartTime() + "; Stop Time: " + getStopTime()
+ + "; Total Time: " + getTotalTime() + "; Total Distance: " + getTotalDistance()
+ + "; Total Time: " + getTotalTime() + "; Moving Time: " + getMovingTime()
+ + "; Min Latitude: " + getBottomDegrees() + "; Max Latitude: " + getTopDegrees()
+ + "; Min Longitude: " + getLeftDegrees() + "; Max Longitude: " + getRightDegrees()
+ + "; Max Elevation: " + getMaxElevation() + "; Max Speed: " + getMaxSpeed()
+ + "; Min Elevation: " + getMinElevation() + "; Max Elevation: " + getMaxElevation()
+ + "; Elevation Gain: " + getTotalElevationGain() + "; Min Grade: " + getMinGrade()
+ + "; Max Grade: " + getMaxGrade() + "}";
}
- // Parcelable interface and creator
-
/**
* Creator of statistics data from parcels.
*/
- public static class Creator
- implements Parcelable.Creator