mirror of
https://codeberg.org/OpenTracksApp/OpenTracks.git
synced 2026-10-05 19:13:07 +02:00
@@ -32,8 +32,8 @@ import java.time.Instant;
|
||||
//TODO Check that data ranges are valid (not less than zero etc.)
|
||||
public class TrackStatistics {
|
||||
|
||||
// The min and max elevation (meters) seen on this track.
|
||||
private final ExtremityMonitor elevationExtremities = new ExtremityMonitor();
|
||||
// The min and max altitude (meters) seen on this track.
|
||||
private final ExtremityMonitor altitudeExtremities = new ExtremityMonitor();
|
||||
|
||||
// The track start time.
|
||||
private Instant startTime;
|
||||
@@ -47,10 +47,8 @@ public class TrackStatistics {
|
||||
private Duration movingTime;
|
||||
// The maximum speed (meters/second) that we believe is valid.
|
||||
private double maxSpeed_mps;
|
||||
// The total elevation gained (meters).
|
||||
private Float totalElevationGain_m = null;
|
||||
// The total elevation lost (meters).
|
||||
private Float totalElevationLoss_m = null;
|
||||
private Float totalAltitudeGain_m = null;
|
||||
private Float totalAltitudeLoss_m = null;
|
||||
|
||||
public TrackStatistics() {
|
||||
reset();
|
||||
@@ -68,9 +66,9 @@ public class TrackStatistics {
|
||||
totalTime = other.totalTime;
|
||||
movingTime = other.movingTime;
|
||||
maxSpeed_mps = other.maxSpeed_mps;
|
||||
elevationExtremities.set(other.elevationExtremities.getMin(), other.elevationExtremities.getMax());
|
||||
totalElevationGain_m = other.totalElevationGain_m;
|
||||
totalElevationLoss_m = other.totalElevationLoss_m;
|
||||
altitudeExtremities.set(other.altitudeExtremities.getMin(), other.altitudeExtremities.getMax());
|
||||
totalAltitudeGain_m = other.totalAltitudeGain_m;
|
||||
totalAltitudeLoss_m = other.totalAltitudeLoss_m;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,26 +93,26 @@ public class TrackStatistics {
|
||||
totalTime = totalTime.plus(other.totalTime);
|
||||
movingTime = movingTime.plus(other.movingTime);
|
||||
maxSpeed_mps = Math.max(maxSpeed_mps, other.maxSpeed_mps);
|
||||
if (other.elevationExtremities.hasData()) {
|
||||
elevationExtremities.update(other.elevationExtremities.getMin());
|
||||
elevationExtremities.update(other.elevationExtremities.getMax());
|
||||
if (other.altitudeExtremities.hasData()) {
|
||||
altitudeExtremities.update(other.altitudeExtremities.getMin());
|
||||
altitudeExtremities.update(other.altitudeExtremities.getMax());
|
||||
}
|
||||
if (totalElevationGain_m == null) {
|
||||
if (other.totalElevationGain_m != null) {
|
||||
totalElevationGain_m = other.totalElevationGain_m;
|
||||
if (totalAltitudeGain_m == null) {
|
||||
if (other.totalAltitudeGain_m != null) {
|
||||
totalAltitudeGain_m = other.totalAltitudeGain_m;
|
||||
}
|
||||
} else {
|
||||
if (other.totalElevationGain_m != null) {
|
||||
totalElevationGain_m += other.totalElevationGain_m;
|
||||
if (other.totalAltitudeGain_m != null) {
|
||||
totalAltitudeGain_m += other.totalAltitudeGain_m;
|
||||
}
|
||||
}
|
||||
if (totalElevationLoss_m == null) {
|
||||
if (other.totalElevationLoss_m != null) {
|
||||
totalElevationLoss_m = other.totalElevationLoss_m;
|
||||
if (totalAltitudeLoss_m == null) {
|
||||
if (other.totalAltitudeLoss_m != null) {
|
||||
totalAltitudeLoss_m = other.totalAltitudeLoss_m;
|
||||
}
|
||||
} else {
|
||||
if (other.totalElevationLoss_m != null) {
|
||||
totalElevationLoss_m += other.totalElevationLoss_m;
|
||||
if (other.totalAltitudeLoss_m != null) {
|
||||
totalAltitudeLoss_m += other.totalAltitudeLoss_m;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -127,8 +125,8 @@ public class TrackStatistics {
|
||||
setTotalTime(Duration.ofSeconds(0));
|
||||
setMovingTime(Duration.ofSeconds(0));
|
||||
setMaxSpeed(0);
|
||||
setTotalElevationGain(null);
|
||||
setTotalElevationLoss(null);
|
||||
setTotalAltitudeGain(null);
|
||||
setTotalAltitudeLoss(null);
|
||||
}
|
||||
|
||||
public void reset(Instant startTime) {
|
||||
@@ -224,104 +222,80 @@ public class TrackStatistics {
|
||||
return Math.max(maxSpeed_mps, getAverageMovingSpeed());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum speed.
|
||||
*
|
||||
* @param maxSpeed the maximum speed in meters/second
|
||||
*/
|
||||
public void setMaxSpeed(double maxSpeed) {
|
||||
this.maxSpeed_mps = maxSpeed;
|
||||
public void setMaxSpeed(double maxSpeed_mps) {
|
||||
this.maxSpeed_mps = maxSpeed_mps;
|
||||
}
|
||||
|
||||
public boolean hasElevationMin() {
|
||||
return !Double.isInfinite(getMinElevation());
|
||||
public boolean hasAltitudeMin() {
|
||||
return !Double.isInfinite(getMinAltitude());
|
||||
}
|
||||
|
||||
public double getMinAltitude() {
|
||||
return altitudeExtremities.getMin();
|
||||
}
|
||||
|
||||
public void setMinAltitude(double altitude_m) {
|
||||
altitudeExtremities.setMin(altitude_m);
|
||||
}
|
||||
|
||||
public boolean hasAltitudeMax() {
|
||||
return !Double.isInfinite(getMaxAltitude());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the minimum elevation.
|
||||
* This is calculated from the smoothed elevation, so this can actually be more than the current elevation.
|
||||
* Gets the maximum altitude.
|
||||
* This is calculated from the smoothed altitude, so this can actually be less than the current altitude.
|
||||
*/
|
||||
public double getMinElevation() {
|
||||
return elevationExtremities.getMin();
|
||||
public double getMaxAltitude() {
|
||||
return altitudeExtremities.getMax();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum elevation.
|
||||
*
|
||||
* @param elevation the minimum elevation in meters
|
||||
*/
|
||||
public void setMinElevation(double elevation) {
|
||||
elevationExtremities.setMin(elevation);
|
||||
public void setMaxAltitude(double altitude_m) {
|
||||
altitudeExtremities.setMax(altitude_m);
|
||||
}
|
||||
|
||||
public boolean hasElevationMax() {
|
||||
return !Double.isInfinite(getMaxElevation());
|
||||
public void updateAltitudeExtremities(double altitude_m) {
|
||||
altitudeExtremities.update(altitude_m);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum elevation.
|
||||
*
|
||||
* @param elevation_m the maximum elevation in meters
|
||||
*/
|
||||
public void setMaxElevation(double elevation_m) {
|
||||
elevationExtremities.setMax(elevation_m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a new elevation.
|
||||
*
|
||||
* @param elevation_m the elevation value in meters
|
||||
*/
|
||||
public void updateElevationExtremities(double elevation_m) {
|
||||
elevationExtremities.update(elevation_m);
|
||||
}
|
||||
|
||||
public boolean hasTotalElevationGain() {
|
||||
return totalElevationGain_m != null;
|
||||
public boolean hasTotalAltitudeGain() {
|
||||
return totalAltitudeGain_m != null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Float getTotalElevationGain() {
|
||||
return totalElevationGain_m;
|
||||
public Float getTotalAltitudeGain() {
|
||||
return totalAltitudeGain_m;
|
||||
}
|
||||
|
||||
public void setTotalElevationGain(Float totalElevationGain_m) {
|
||||
this.totalElevationGain_m = totalElevationGain_m;
|
||||
public void setTotalAltitudeGain(Float totalAltitudeGain_m) {
|
||||
this.totalAltitudeGain_m = totalAltitudeGain_m;
|
||||
}
|
||||
|
||||
public void addTotalElevationGain(float gain_m) {
|
||||
if (totalElevationGain_m == null) {
|
||||
totalElevationGain_m = 0f;
|
||||
public void addTotalAltitudeGain(float gain_m) {
|
||||
if (totalAltitudeGain_m == null) {
|
||||
totalAltitudeGain_m = 0f;
|
||||
}
|
||||
totalElevationGain_m += gain_m;
|
||||
totalAltitudeGain_m += gain_m;
|
||||
}
|
||||
|
||||
public boolean hasTotalElevationLoss() {
|
||||
return totalElevationLoss_m != null;
|
||||
public boolean hasTotalAltitudeLoss() {
|
||||
return totalAltitudeLoss_m != null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Float getTotalElevationLoss() {
|
||||
return totalElevationLoss_m;
|
||||
public Float getTotalAltitudeLoss() {
|
||||
return totalAltitudeLoss_m;
|
||||
}
|
||||
|
||||
public void setTotalElevationLoss(Float totalElevationLoss_m) {
|
||||
this.totalElevationLoss_m = totalElevationLoss_m;
|
||||
public void setTotalAltitudeLoss(Float totalAltitudeLoss_m) {
|
||||
this.totalAltitudeLoss_m = totalAltitudeLoss_m;
|
||||
}
|
||||
|
||||
public void addTotalElevationLoss(float loss_m) {
|
||||
if (totalElevationLoss_m == null) {
|
||||
totalElevationLoss_m = 0f;
|
||||
public void addTotalAltitudeLoss(float loss_m) {
|
||||
if (totalAltitudeLoss_m == null) {
|
||||
totalAltitudeLoss_m = 0f;
|
||||
}
|
||||
totalElevationLoss_m += loss_m;
|
||||
totalAltitudeLoss_m += loss_m;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@@ -330,8 +304,8 @@ public class TrackStatistics {
|
||||
return "TrackStatistics { Start Time: " + getStartTime() + "; Stop Time: " + getStopTime()
|
||||
+ "; Total Distance: " + getTotalDistance() + "; Total Time: " + getTotalTime()
|
||||
+ "; Moving Time: " + getMovingTime() + "; Max Speed: " + getMaxSpeed()
|
||||
+ "; Min Elevation: " + getMinElevation() + "; Max Elevation: " + getMaxElevation()
|
||||
+ "; Elevation Gain: " + getTotalElevationGain()
|
||||
+ "; Elevation Loss: " + getTotalElevationLoss() + "}";
|
||||
+ "; Min Altitude: " + getMinAltitude() + "; Max Altitude: " + getMaxAltitude()
|
||||
+ "; Altitude Gain: " + getTotalAltitudeGain()
|
||||
+ "; Altitude Loss: " + getTotalAltitudeLoss() + "}";
|
||||
}
|
||||
}
|
||||
@@ -38,10 +38,10 @@ import de.dennisguse.opentracks.content.provider.TrackPointIterator;
|
||||
public class TrackStatisticsUpdater {
|
||||
|
||||
/**
|
||||
* The number of elevation readings to smooth to get a somewhat accurate signal.
|
||||
* The number of altitude readings to smooth to get a somewhat accurate signal.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
private static final int ELEVATION_SMOOTHING_FACTOR = 25;
|
||||
private static final int ALTITUDE_SMOOTHING_FACTOR = 25;
|
||||
|
||||
/**
|
||||
* The number of speed reading to smooth to get a somewhat accurate signal.
|
||||
@@ -60,13 +60,10 @@ public class TrackStatisticsUpdater {
|
||||
private boolean trackInitialized = false;
|
||||
private boolean segmentInitialized = false;
|
||||
|
||||
// The track's statistics
|
||||
private final TrackStatistics trackStatistics;
|
||||
|
||||
// A buffer of the recent elevation readings (m)
|
||||
private final DoubleRingBuffer elevationBuffer_m = new DoubleRingBuffer(ELEVATION_SMOOTHING_FACTOR);
|
||||
// A buffer of the recent speed readings (m/s) for calculating max speed
|
||||
private final DoubleRingBuffer speedBuffer_ms = new DoubleRingBuffer(SPEED_SMOOTHING_FACTOR);
|
||||
private final DoubleRingBuffer altitudeBuffer_m = new DoubleRingBuffer(ALTITUDE_SMOOTHING_FACTOR);
|
||||
private final DoubleRingBuffer speedBuffer_mps = new DoubleRingBuffer(SPEED_SMOOTHING_FACTOR);
|
||||
|
||||
// The current segment's statistics
|
||||
private final TrackStatistics currentSegment = new TrackStatistics();
|
||||
@@ -131,12 +128,12 @@ public class TrackStatisticsUpdater {
|
||||
}
|
||||
|
||||
// Process sensor data
|
||||
if (trackPoint.hasElevationGain()) {
|
||||
currentSegment.addTotalElevationGain(trackPoint.getElevationGain());
|
||||
if (trackPoint.hasAltitudeGain()) {
|
||||
currentSegment.addTotalAltitudeGain(trackPoint.getAltitudeGain());
|
||||
}
|
||||
|
||||
if (trackPoint.hasElevationLoss()) {
|
||||
currentSegment.addTotalElevationLoss(trackPoint.getElevationLoss());
|
||||
if (trackPoint.hasAltitudeLoss()) {
|
||||
currentSegment.addTotalAltitudeLoss(trackPoint.getAltitudeLoss());
|
||||
}
|
||||
|
||||
if (trackPoint.hasSensorDistance()) {
|
||||
@@ -148,9 +145,9 @@ public class TrackStatisticsUpdater {
|
||||
return;
|
||||
}
|
||||
|
||||
//Update absolute (GPS-based) elevation
|
||||
//Update absolute (GPS-based) altitude
|
||||
if (trackPoint.hasAltitude()) {
|
||||
updateAbsoluteElevation(trackPoint.getAltitude());
|
||||
updateAbsoluteAltitude(trackPoint.getAltitude());
|
||||
}
|
||||
|
||||
if (lastTrackPoint == null || lastMovingTrackPoint == null) {
|
||||
@@ -163,7 +160,7 @@ public class TrackStatisticsUpdater {
|
||||
// GPS-based distance/speed
|
||||
float movingDistance = lastMovingTrackPoint.distanceToPrevious(trackPoint);
|
||||
if (movingDistance < minGPSDistance && !trackPoint.isMoving()) {
|
||||
speedBuffer_ms.reset();
|
||||
speedBuffer_mps.reset();
|
||||
lastTrackPoint = trackPoint;
|
||||
return;
|
||||
}
|
||||
@@ -195,8 +192,8 @@ public class TrackStatisticsUpdater {
|
||||
|
||||
lastTrackPoint = null;
|
||||
lastMovingTrackPoint = null;
|
||||
elevationBuffer_m.reset();
|
||||
speedBuffer_ms.reset();
|
||||
altitudeBuffer_m.reset();
|
||||
speedBuffer_mps.reset();
|
||||
}
|
||||
|
||||
public void addTrackPoint(TrackPointIterator iterator, int minRecordingDistance) {
|
||||
@@ -207,15 +204,15 @@ public class TrackStatisticsUpdater {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
public double getSmoothedElevation() {
|
||||
return elevationBuffer_m.getAverage();
|
||||
public double getSmoothedAltitude() {
|
||||
return altitudeBuffer_m.getAverage();
|
||||
}
|
||||
|
||||
public double getSmoothedSpeed() {
|
||||
return speedBuffer_ms.getAverage();
|
||||
return speedBuffer_mps.getAverage();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -224,27 +221,24 @@ public class TrackStatisticsUpdater {
|
||||
@VisibleForTesting
|
||||
private void updateSpeed(@NonNull TrackPoint trackPoint, @NonNull TrackPoint lastTrackPoint) {
|
||||
if (!trackPoint.isMoving()) {
|
||||
speedBuffer_ms.reset();
|
||||
speedBuffer_mps.reset();
|
||||
} else if (isValidSpeed(trackPoint, lastTrackPoint)) {
|
||||
speedBuffer_ms.setNext(trackPoint.getSpeed());
|
||||
if (speedBuffer_ms.getAverage() > currentSegment.getMaxSpeed()) {
|
||||
currentSegment.setMaxSpeed(speedBuffer_ms.getAverage());
|
||||
speedBuffer_mps.setNext(trackPoint.getSpeed());
|
||||
if (speedBuffer_mps.getAverage() > currentSegment.getMaxSpeed()) {
|
||||
currentSegment.setMaxSpeed(speedBuffer_mps.getAverage());
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "Invalid speed. speed: " + trackPoint.getSpeed() + " lastLocationSpeed: " + lastTrackPoint.getSpeed());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an elevation reading.
|
||||
*/
|
||||
private void updateAbsoluteElevation(double elevation) {
|
||||
// Update elevation using the smoothed average
|
||||
double oldAverage = elevationBuffer_m.getAverage();
|
||||
elevationBuffer_m.setNext(elevation);
|
||||
double newAverage = elevationBuffer_m.getAverage();
|
||||
private void updateAbsoluteAltitude(double altitude) {
|
||||
// Update altitude using the smoothed average
|
||||
double oldAverage = altitudeBuffer_m.getAverage();
|
||||
altitudeBuffer_m.setNext(altitude);
|
||||
double newAverage = altitudeBuffer_m.getAverage();
|
||||
|
||||
currentSegment.updateElevationExtremities(newAverage);
|
||||
currentSegment.updateAltitudeExtremities(newAverage);
|
||||
}
|
||||
|
||||
private boolean isValidSpeed(@NonNull TrackPoint trackPoint, @NonNull TrackPoint lastTrackPoint) {
|
||||
@@ -266,8 +260,8 @@ public class TrackStatisticsUpdater {
|
||||
}
|
||||
|
||||
// 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_ms.isFull()) {
|
||||
double average = speedBuffer_ms.getAverage();
|
||||
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());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user