Files
OpenTracks/src/main/java/de/dennisguse/opentracks/data/models/Statistics.java
T
Dennis Guse a6d4a05839 Statistics requires startTime and stopTime.
This is now enforced for import as wells.
2025-11-25 07:31:48 +01:00

138 lines
4.7 KiB
Java

package de.dennisguse.opentracks.data.models;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.time.Duration;
import java.time.Instant;
import java.util.Objects;
public record Statistics(
@NonNull
Instant startTime,
@NonNull
Instant stopTime,
@NonNull
Duration totalDuration,
@NonNull
Duration movingDuration, // Based on when we believe the user is traveling
@NonNull
Distance totalDistance,
@NonNull
Speed maxSpeed,
@Nullable
AltitudeExtremities altitudeExtremities,
@Nullable
AltitudeGainLoss altitudeGainLoss,
//NOTE: The following values are not persisted.
@Nullable
HeartRate avgHeartRate,
@Nullable
Power avgPower
) {
//TODO: Should not be necessary; refactor and remove.
@Deprecated
public static final Statistics DEFAULT =
new Statistics(
Instant.EPOCH,
Instant.EPOCH,
Duration.ZERO,
Duration.ZERO,
Distance.ZERO,
Speed.ZERO,
null,
null,
null,
null
);
public Duration getStoppedTime() {
return totalDuration.minus(movingDuration);
}
public Speed getAverageSpeed() {
return Speed.of(totalDistance, totalDuration);
}
public Speed getAverageMovingSpeed() {
return Speed.of(totalDistance, movingDuration);
}
/**
* Combines these statistics with those from another object.
* This assumes that the time periods covered by each do not intersect.
*/
//TODO Should be refactored to append only
@NonNull
public Statistics merge(Statistics other) {
if (other == null) return this;
HeartRate newAvgHeartRate = avgHeartRate;
if (avgHeartRate == null) {
newAvgHeartRate = other.avgHeartRate;
} else {
if (other.avgHeartRate != null) {
// Using total time as weights for the averaging.
// Important to do this before total time is updated
newAvgHeartRate = HeartRate.of(
(totalDuration.getSeconds() * avgHeartRate.getBPM() + other.totalDuration.getSeconds() * other.avgHeartRate.getBPM())
/ (totalDuration.getSeconds() + other.totalDuration.getSeconds())
);
}
}
Power newAvgPower = avgPower;
if (avgPower == null) {
newAvgPower = other.avgPower;
} else {
if (other.avgPower != null) {
// Using total time as weights for the averaging.
// Important to do this before total time is updated
newAvgPower = Power.of(
(totalDuration.getSeconds() * avgPower.getW() + other.totalDuration.getSeconds() * other.avgPower.getW())
/ (totalDuration.getSeconds() + other.totalDuration.getSeconds())
);
}
}
AltitudeExtremities newAltitudeExtremities;
if (altitudeExtremities != null && other.altitudeExtremities != null) {
newAltitudeExtremities = new AltitudeExtremities(
Math.min(altitudeExtremities.min_m(), other.altitudeExtremities.min_m()),
Math.max(altitudeExtremities.max_m(), other.altitudeExtremities.max_m())
);
} else {
newAltitudeExtremities = altitudeExtremities != null ? altitudeExtremities : other.altitudeExtremities;
}
AltitudeGainLoss newAltitudeGainLoss;
if (altitudeGainLoss != null && other.altitudeGainLoss != null) {
newAltitudeGainLoss = new AltitudeGainLoss(
altitudeGainLoss.gain_m() + other.altitudeGainLoss.gain_m(),
altitudeGainLoss.loss_m() + other.altitudeGainLoss.loss_m()
);
} else {
newAltitudeGainLoss = altitudeGainLoss != null ? altitudeGainLoss : other.altitudeGainLoss;
}
return new Statistics(
startTime.isBefore(other.startTime) ? startTime : other.startTime,
stopTime.isAfter(other.stopTime) ? stopTime : other.stopTime,
totalDuration.plus(other.totalDuration),
movingDuration.plus(other.movingDuration),
totalDistance.plus(other.totalDistance),
Speed.max(maxSpeed, other.maxSpeed),
newAltitudeExtremities,
newAltitudeGainLoss,
newAvgHeartRate,
newAvgPower
);
}
}