Value objects distance and speed.

#679
This commit is contained in:
Dennis Guse
2021-04-09 19:59:44 +02:00
parent f2b1d2a386
commit 6f9c788965
47 changed files with 773 additions and 502 deletions
@@ -22,6 +22,9 @@ import androidx.annotation.Nullable;
import java.time.Duration;
import java.time.Instant;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Speed;
/**
* Statistical data about a {@link de.dennisguse.opentracks.content.data.Track}.
* The data in this class should be filled out by {@link TrackStatisticsUpdater}.
@@ -40,13 +43,13 @@ public class TrackStatistics {
// The track stop time.
private Instant stopTime;
private double totalDistance_m;
private Distance totalDistance;
// Updated when new points are received, may be stale.
private Duration totalTime;
// Based on when we believe the user is traveling.
private Duration movingTime;
// The maximum speed (meters/second) that we believe is valid.
private double maxSpeed_mps;
private Speed maxSpeed;
private Float totalAltitudeGain_m = null;
private Float totalAltitudeLoss_m = null;
@@ -62,10 +65,10 @@ public class TrackStatistics {
public TrackStatistics(TrackStatistics other) {
startTime = other.startTime;
stopTime = other.stopTime;
totalDistance_m = other.totalDistance_m;
totalDistance = other.totalDistance;
totalTime = other.totalTime;
movingTime = other.movingTime;
maxSpeed_mps = other.maxSpeed_mps;
maxSpeed = other.maxSpeed;
altitudeExtremities.set(other.altitudeExtremities.getMin(), other.altitudeExtremities.getMax());
totalAltitudeGain_m = other.totalAltitudeGain_m;
totalAltitudeLoss_m = other.totalAltitudeLoss_m;
@@ -89,10 +92,10 @@ public class TrackStatistics {
stopTime = stopTime.isAfter(other.stopTime) ? stopTime : other.stopTime;
}
totalDistance_m += other.totalDistance_m;
totalDistance = totalDistance.plus(other.totalDistance);
totalTime = totalTime.plus(other.totalTime);
movingTime = movingTime.plus(other.movingTime);
maxSpeed_mps = Math.max(maxSpeed_mps, other.maxSpeed_mps);
maxSpeed = Speed.max(maxSpeed, other.maxSpeed);
if (other.altitudeExtremities.hasData()) {
altitudeExtremities.update(other.altitudeExtremities.getMin());
altitudeExtremities.update(other.altitudeExtremities.getMax());
@@ -121,10 +124,10 @@ public class TrackStatistics {
startTime = null;
stopTime = null;
setTotalDistance(0);
setTotalDistance(Distance.of(0));
setTotalTime(Duration.ofSeconds(0));
setMovingTime(Duration.ofSeconds(0));
setMaxSpeed(0);
setMaxSpeed(Speed.zero());
setTotalAltitudeGain(null);
setTotalAltitudeLoss(null);
}
@@ -157,16 +160,16 @@ public class TrackStatistics {
this.stopTime = stopTime;
}
public double getTotalDistance() {
return totalDistance_m;
public Distance getTotalDistance() {
return totalDistance;
}
public void setTotalDistance(double totalDistance_m) {
this.totalDistance_m = totalDistance_m;
public void setTotalDistance(Distance totalDistance_m) {
this.totalDistance = totalDistance_m;
}
public void addTotalDistance(double distance_m) {
totalDistance_m += distance_m;
public void addTotalDistance(Distance distance_m) {
totalDistance = totalDistance.plus(distance_m);
}
/**
@@ -195,35 +198,26 @@ public class TrackStatistics {
}
/**
* Gets the average speed in meters/second.
* Gets the average speed.
* This calculation only takes into account the displacement until the last point that was accounted for in statistics.
*/
public double getAverageSpeed() {
if (totalTime.isZero()) {
return 0.0;
public Speed getAverageSpeed() {
if (totalDistance.isZero() && totalDistance.isZero()) {
return Speed.of(0);
}
return totalDistance_m / (double) totalTime.getSeconds();
return Speed.of(totalDistance.toM() / totalTime.getSeconds());
}
/**
* Gets the average moving speed in meters/second.
*/
public double getAverageMovingSpeed() {
if (movingTime.isZero()) {
return 0.0;
}
return totalDistance_m / (double) movingTime.getSeconds();
public Speed getAverageMovingSpeed() {
return Speed.of(totalDistance, movingTime);
}
/**
* Gets the maximum speed in meters/second.
*/
public double getMaxSpeed() {
return Math.max(maxSpeed_mps, getAverageMovingSpeed());
public Speed getMaxSpeed() {
return Speed.max(maxSpeed, getAverageMovingSpeed());
}
public void setMaxSpeed(double maxSpeed_mps) {
this.maxSpeed_mps = maxSpeed_mps;
public void setMaxSpeed(Speed maxSpeed) {
this.maxSpeed = maxSpeed;
}
public boolean hasAltitudeMin() {
@@ -23,8 +23,9 @@ import androidx.annotation.VisibleForTesting;
import java.time.Duration;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Speed;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
/**
* Updater for {@link TrackStatistics}.
@@ -103,12 +104,12 @@ public class TrackStatisticsUpdater {
* @param trackPoint the trackPoint
* @param minGPSDistance the min recording distance
*/
public void addTrackPoint(TrackPoint trackPoint, int minGPSDistance) {
public void addTrackPoint(TrackPoint trackPoint, Distance minGPSDistance) {
internalAddTrackPoint(trackPoint, minGPSDistance);
Log.d(TAG, this.toString());
}
private void internalAddTrackPoint(TrackPoint trackPoint, int minGPSDistance) {
private void internalAddTrackPoint(TrackPoint trackPoint, Distance minGPSDistance) {
if (!trackInitialized) {
trackStatistics.setStartTime(trackPoint.getTime());
trackInitialized = true;
@@ -158,8 +159,8 @@ public class TrackStatisticsUpdater {
if (!trackPoint.hasSensorDistance()) {
// GPS-based distance/speed
float movingDistance = lastMovingTrackPoint.distanceToPrevious(trackPoint);
if (movingDistance < minGPSDistance && !trackPoint.isMoving()) {
Distance movingDistance = lastMovingTrackPoint.distanceToPrevious(trackPoint);
if (movingDistance.lessThan(minGPSDistance) && !trackPoint.isMoving()) {
speedBuffer_mps.reset();
lastTrackPoint = trackPoint;
return;
@@ -196,13 +197,6 @@ public class TrackStatisticsUpdater {
speedBuffer_mps.reset();
}
public void addTrackPoint(TrackPointIterator iterator, int minRecordingDistance) {
while (iterator.hasNext()) {
TrackPoint location = iterator.next();
addTrackPoint(location, minRecordingDistance);
}
}
/**
* Gets the smoothed altitude over several readings.
* The altitude readings is noisy so the smoothed altitude is better than the raw altitude for many tasks.
@@ -211,8 +205,8 @@ public class TrackStatisticsUpdater {
return altitudeBuffer_m.getAverage();
}
public double getSmoothedSpeed() {
return speedBuffer_mps.getAverage();
public Speed getSmoothedSpeed() {
return Speed.of(speedBuffer_mps.getAverage());
}
/**
@@ -223,9 +217,10 @@ public class TrackStatisticsUpdater {
if (!trackPoint.isMoving()) {
speedBuffer_mps.reset();
} else if (isValidSpeed(trackPoint, lastTrackPoint)) {
speedBuffer_mps.setNext(trackPoint.getSpeed());
if (speedBuffer_mps.getAverage() > currentSegment.getMaxSpeed()) {
currentSegment.setMaxSpeed(speedBuffer_mps.getAverage());
speedBuffer_mps.setNext(trackPoint.getSpeed().toMPS());
Speed average = Speed.of(speedBuffer_mps.getAverage());
if (average.greaterThan(currentSegment.getMaxSpeed())) {
currentSegment.setMaxSpeed(average);
}
} else {
Log.d(TAG, "Invalid speed. speed: " + trackPoint.getSpeed() + " lastLocationSpeed: " + lastTrackPoint.getSpeed());
@@ -243,27 +238,26 @@ public class TrackStatisticsUpdater {
private boolean isValidSpeed(@NonNull TrackPoint trackPoint, @NonNull TrackPoint lastTrackPoint) {
// There are a lot of noisy speed readings. Do the cheapest checks first, most expensive last.
if (trackPoint.getSpeed() == 0) {
if (trackPoint.getSpeed().isZero()) {
return false;
}
// The following code will ignore unlikely readings. 128 m/s seems to be an internal android error code.
if (Math.abs(trackPoint.getSpeed() - 128) < 1) {
return false;
}
// See if the speed seems physically likely. Ignore any speeds that imply acceleration greater than 2g.
Duration timeDifference = Duration.between(lastTrackPoint.getTime(), trackPoint.getTime());
double speedDifference = Math.abs(lastTrackPoint.getSpeed() - trackPoint.getSpeed());
if (speedDifference > MAX_ACCELERATION * timeDifference.toMillis()) {
return false;
Speed maxAcceleration = Speed.of(MAX_ACCELERATION * timeDifference.toMillis());
{
// See if the speed seems physically likely. Ignore any speeds that imply acceleration greater than 2g.
Speed speedDifference = Speed.absDiff(lastTrackPoint.getSpeed(), trackPoint.getSpeed());
if (speedDifference.greaterThan(maxAcceleration)) {
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_mps.isFull()) {
double average = speedBuffer_mps.getAverage();
double diff = Math.abs(average - trackPoint.getSpeed());
return (trackPoint.getSpeed() < average * 10) && (diff < MAX_ACCELERATION * timeDifference.toMillis());
Speed average = Speed.of(speedBuffer_mps.getAverage());
Speed speedDifference = Speed.absDiff(average, trackPoint.getSpeed());
return trackPoint.getSpeed().lessThan(average.mul(10)) && speedDifference.lessThan(maxAcceleration);
}
return true;