AggregatedStatistics: compute without SegmentStatisticUpdater.

This commit is contained in:
Dennis Guse
2025-11-01 21:30:29 +01:00
parent 7791ff8659
commit a5e5d0ca51
4 changed files with 64 additions and 77 deletions
@@ -23,7 +23,6 @@ import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Statistics;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.stats.SegmentStatisticUpdater;
@RunWith(JUnit4.class)
public class AggregatedStatisticsTest {
@@ -65,9 +64,8 @@ public class AggregatedStatisticsTest {
assertNotNull(aggregatedStatistics.get(biking));
assertEquals(1, aggregatedStatistics.get(biking).getCountTracks());
SegmentStatisticUpdater statistics2 = aggregatedStatistics.get(biking).getTrackStatistics();
assertEquals(totalDistance, statistics2.getTotalDistance());
assertEquals(totalTime, statistics2.getMovingTime());
assertEquals(totalDistance, aggregatedStatistics.get(biking).totalDistance);
assertEquals(totalTime, aggregatedStatistics.get(biking).totalMovingTime);
}
@Test
@@ -119,9 +117,8 @@ public class AggregatedStatisticsTest {
assertNotNull(aggregatedStatistics.get(biking));
assertEquals(2, aggregatedStatistics.get(biking).getCountTracks());
SegmentStatisticUpdater statistics2 = aggregatedStatistics.get(biking).getTrackStatistics();
assertEquals(totalDistance.multipliedBy(2), statistics2.getTotalDistance());
assertEquals(totalTime.multipliedBy(2), statistics2.getMovingTime());
assertEquals(totalDistance.multipliedBy(2), aggregatedStatistics.get(biking).totalDistance);
assertEquals(totalTime.multipliedBy(2), aggregatedStatistics.get(biking).totalMovingTime);
}
@Test
@@ -153,21 +150,18 @@ public class AggregatedStatisticsTest {
assertEquals(1, aggregatedStatistics.get(walking).getCountTracks());
{
SegmentStatisticUpdater statistics2 = aggregatedStatistics.get(biking).getTrackStatistics();
assertEquals(totalDistance, statistics2.getTotalDistance());
assertEquals(totalTime, statistics2.getMovingTime());
assertEquals(totalDistance, aggregatedStatistics.get(biking).totalDistance);
assertEquals(totalTime, aggregatedStatistics.get(biking).totalMovingTime);
}
{
SegmentStatisticUpdater statistics2 = aggregatedStatistics.get(running).getTrackStatistics();
assertEquals(totalDistance, statistics2.getTotalDistance());
assertEquals(totalTime, statistics2.getMovingTime());
assertEquals(totalDistance, aggregatedStatistics.get(running).totalDistance);
assertEquals(totalTime, aggregatedStatistics.get(running).totalMovingTime);
}
{
SegmentStatisticUpdater statistics2 = aggregatedStatistics.get(walking).getTrackStatistics();
assertEquals(totalDistance, statistics2.getTotalDistance());
assertEquals(totalTime, statistics2.getMovingTime());
assertEquals(totalDistance, aggregatedStatistics.get(walking).totalDistance);
assertEquals(totalTime, aggregatedStatistics.get(walking).totalMovingTime);
}
}
@@ -216,30 +210,26 @@ public class AggregatedStatisticsTest {
// Biking.
{
SegmentStatisticUpdater statistics2 = aggregatedStatistics.get(biking).getTrackStatistics();
assertEquals(totalDistance.multipliedBy(5), statistics2.getTotalDistance());
assertEquals(totalTime.multipliedBy(5), statistics2.getMovingTime());
assertEquals(totalDistance.multipliedBy(5), aggregatedStatistics.get(biking).totalDistance);
assertEquals(totalTime.multipliedBy(5), aggregatedStatistics.get(biking).totalMovingTime);
}
// Running.
{
SegmentStatisticUpdater statistics2 = aggregatedStatistics.get(running).getTrackStatistics();
assertEquals(totalDistance.multipliedBy(2), statistics2.getTotalDistance());
assertEquals(totalTime.multipliedBy(2), statistics2.getMovingTime());
assertEquals(totalDistance.multipliedBy(2), aggregatedStatistics.get(running).totalDistance);
assertEquals(totalTime.multipliedBy(2), aggregatedStatistics.get(running).totalMovingTime);
}
// Walking.
{
SegmentStatisticUpdater statistics2 = aggregatedStatistics.get(walking).getTrackStatistics();
assertEquals(totalDistance.multipliedBy(2), statistics2.getTotalDistance());
assertEquals(totalTime.multipliedBy(2), statistics2.getMovingTime());
assertEquals(totalDistance.multipliedBy(2), aggregatedStatistics.get(walking).totalDistance);
assertEquals(totalTime.multipliedBy(2), aggregatedStatistics.get(walking).totalMovingTime);
}
// Driving.
{
SegmentStatisticUpdater statistics2 = aggregatedStatistics.get(driving).getTrackStatistics();
assertEquals(totalDistance, statistics2.getTotalDistance());
assertEquals(totalTime, statistics2.getMovingTime());
assertEquals(totalDistance, aggregatedStatistics.get(driving).totalDistance);
assertEquals(totalTime, aggregatedStatistics.get(driving).totalMovingTime);
}
// Check order
@@ -108,7 +108,7 @@ public class SegmentStatisticUpdater {
*
* @param other another statistics data object
*/
//TODO Should be refactored to append only [mainly due to isIdle] (NOTE: This requires to use a custom value object for AggregatedStatistics; this is anyhow recommended).
//TODO Should be refactored to append only [mainly due to isIdle]
public void merge(SegmentStatisticUpdater other) {
if (startTime == null) {
startTime = other.startTime;
@@ -1,53 +1,47 @@
package de.dennisguse.opentracks.ui.aggregatedStatistics;
import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import de.dennisguse.opentracks.data.models.Statistics;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.stats.SegmentStatisticUpdater;
public class AggregatedStatistics {
private final Map<String, AggregatedStatistic> dataMap = new HashMap<>();
private final List<AggregatedStatistic> dataList = new ArrayList<>();
private final List<AggregatedStatistic> dataList;
public AggregatedStatistics(@NonNull List<Track> tracks) {
for (Track track : tracks) {
aggregate(track);
}
Map<String, List<Track>> groupedByActivityType = tracks.stream().collect(Collectors.groupingBy(Track::getActivityTypeLocalized));
dataList.addAll(dataMap.values());
dataList.sort((o1, o2) -> {
if (o1.getCountTracks() == o2.getCountTracks()) {
return o1.getActivityTypeLocalized().compareTo(o2.getActivityTypeLocalized());
}
return (o1.getCountTracks() < o2.getCountTracks() ? 1 : -1);
});
}
List<Pair<String, List<Track>>> groupedByActivityTypeList = new ArrayList<>();
groupedByActivityType.forEach((activityTypeLocalized, trackList) -> groupedByActivityTypeList.add(new Pair<>(activityTypeLocalized, trackList)));
@VisibleForTesting
public void aggregate(@NonNull Track track) {
String activityTypeLocalized = track.getActivityTypeLocalized();
if (dataMap.containsKey(activityTypeLocalized)) {
dataMap.get(activityTypeLocalized).add(track.getStatistics());
} else {
dataMap.put(activityTypeLocalized, new AggregatedStatistic(activityTypeLocalized, track.getStatistics()));
}
dataList = groupedByActivityTypeList.parallelStream()
.map(i -> new AggregatedStatistic(i.first, i.second))
.sorted((o1, o2) -> {
if (o1.getCountTracks() == o2.getCountTracks()) {
return o1.getActivityTypeLocalized().compareTo(o2.getActivityTypeLocalized());
}
return (o1.getCountTracks() < o2.getCountTracks() ? 1 : -1);
}).toList();
}
public int getCount() {
return dataMap.size();
return dataList.size();
}
public AggregatedStatistic get(String activityType) {
return dataMap.get(activityType);
@VisibleForTesting
public AggregatedStatistic get(String activityTypeLocalized) {
return dataList.stream().filter(i -> i.getActivityTypeLocalized().equals(activityTypeLocalized)).findFirst().get();
}
public AggregatedStatistic getItem(int position) {
@@ -55,30 +49,33 @@ public class AggregatedStatistics {
}
public static class AggregatedStatistic {
private final String activityTypeLocalized;
private final SegmentStatisticUpdater trackStatistics;
private int countTracks = 1;
final String activityTypeLocalized;
final int countTracks;
public AggregatedStatistic(String activityTypeLocalized, Statistics trackStatistics) {
final Duration totalMovingTime;
final Distance totalDistance;
final Speed avgMovingSpeed;
final Speed maxSpeed;
public AggregatedStatistic(String activityTypeLocalized, List<Track> tracks) {
this.activityTypeLocalized = activityTypeLocalized;
this.trackStatistics = new SegmentStatisticUpdater(trackStatistics);
this.countTracks = tracks.size();
totalMovingTime = tracks.stream().map(t -> t.getStatistics().movingTime()).reduce(Duration.ZERO, Duration::plus);
totalDistance = tracks.stream().map(t -> t.getStatistics().totalDistance()).reduce(Distance.of(0), Distance::plus);
maxSpeed = tracks.stream().map(t -> t.getStatistics().maxSpeed()).max((o1, o2) -> (int) (o1.speed_mps() - o2.speed_mps())).orElse(Speed.zero());
avgMovingSpeed = Speed.of(totalDistance, totalMovingTime);
}
public String getActivityTypeLocalized() {
return activityTypeLocalized;
}
public SegmentStatisticUpdater getTrackStatistics() {
return trackStatistics;
}
public int getCountTracks() {
return countTracks;
}
void add(Statistics statistics) {
trackStatistics.merge(new SegmentStatisticUpdater(statistics));
countTracks++;
}
}
}
@@ -86,14 +86,14 @@ public class AggregatedStatisticsAdapter extends RecyclerView.Adapter<RecyclerVi
SpeedFormatter formatter = SpeedFormatter.Builder().setUnit(unitSystem).setReportSpeedOrPace(reportSpeed).build(context);
{
Pair<String, String> parts = formatter.getSpeedParts(aggregatedStatistic.getTrackStatistics().getAverageMovingSpeed());
Pair<String, String> parts = formatter.getSpeedParts(aggregatedStatistic.avgMovingSpeed);
viewBinding.aggregatedStatsAvgRate.setText(parts.first);
viewBinding.aggregatedStatsAvgRateUnit.setText(parts.second);
viewBinding.aggregatedStatsAvgRateLabel.setText(context.getString(R.string.stats_average_moving_speed));
}
{
Pair<String, String> parts = formatter.getSpeedParts(aggregatedStatistic.getTrackStatistics().getMaxSpeed());
Pair<String, String> parts = formatter.getSpeedParts(aggregatedStatistic.maxSpeed);
viewBinding.aggregatedStatsMaxRate.setText(parts.first);
viewBinding.aggregatedStatsMaxRateUnit.setText(parts.second);
viewBinding.aggregatedStatsMaxRateLabel.setText(context.getString(R.string.stats_max_speed));
@@ -105,14 +105,14 @@ public class AggregatedStatisticsAdapter extends RecyclerView.Adapter<RecyclerVi
SpeedFormatter formatter = SpeedFormatter.Builder().setUnit(unitSystem).setReportSpeedOrPace(reportSpeed).build(context);
{
Pair<String, String> parts = formatter.getSpeedParts(aggregatedStatistic.getTrackStatistics().getAverageMovingSpeed());
Pair<String, String> parts = formatter.getSpeedParts(aggregatedStatistic.avgMovingSpeed);
viewBinding.aggregatedStatsAvgRate.setText(parts.first);
viewBinding.aggregatedStatsAvgRateUnit.setText(parts.second);
viewBinding.aggregatedStatsAvgRateLabel.setText(context.getString(R.string.stats_average_moving_pace));
}
{
Pair<String, String> parts = formatter.getSpeedParts(aggregatedStatistic.getTrackStatistics().getMaxSpeed());
Pair<String, String> parts = formatter.getSpeedParts(aggregatedStatistic.maxSpeed);
viewBinding.aggregatedStatsMaxRate.setText(parts.first);
viewBinding.aggregatedStatsMaxRateUnit.setText(parts.second);
viewBinding.aggregatedStatsMaxRateLabel.setText(R.string.stats_fastest_pace);
@@ -132,11 +132,11 @@ public class AggregatedStatisticsAdapter extends RecyclerView.Adapter<RecyclerVi
Pair<String, String> parts = DistanceFormatter.Builder()
.setUnit(unitSystem)
.build(context).getDistanceParts(aggregatedStatistic.getTrackStatistics().getTotalDistance());
.build(context).getDistanceParts(aggregatedStatistic.totalDistance);
viewBinding.aggregatedStatsDistance.setText(parts.first);
viewBinding.aggregatedStatsDistanceUnit.setText(parts.second);
viewBinding.aggregatedStatsTime.setText(StringUtils.formatElapsedTime(aggregatedStatistic.getTrackStatistics().getMovingTime()));
viewBinding.aggregatedStatsTime.setText(StringUtils.formatElapsedTime(aggregatedStatistic.totalMovingTime));
}
private int getIcon(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) {