TrackPoint is now a record.

This commit is contained in:
Dennis Guse
2025-12-23 22:58:33 +01:00
committed by Dennis Guse
parent 42ae582cd8
commit 015352c989
48 changed files with 1851 additions and 1310 deletions
@@ -17,9 +17,13 @@ import de.dennisguse.opentracks.data.MarkerIterator;
import de.dennisguse.opentracks.data.TrackPointIterator; import de.dennisguse.opentracks.data.TrackPointIterator;
import de.dennisguse.opentracks.data.models.ActivityType; import de.dennisguse.opentracks.data.models.ActivityType;
import de.dennisguse.opentracks.data.models.Altitude; import de.dennisguse.opentracks.data.models.Altitude;
import de.dennisguse.opentracks.data.models.AltitudeGainLoss;
import de.dennisguse.opentracks.data.models.Cadence;
import de.dennisguse.opentracks.data.models.Distance; import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Marker; import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.models.Position; import de.dennisguse.opentracks.data.models.Position;
import de.dennisguse.opentracks.data.models.Power;
import de.dennisguse.opentracks.data.models.Speed; import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Statistics; import de.dennisguse.opentracks.data.models.Statistics;
import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.data.models.Track;
@@ -92,9 +96,9 @@ public class TestDataUtil {
); );
List<Marker> markers = List.of( List<Marker> markers = List.of(
new Marker(null, trackId, "Marker 1", "Marker description 1", "Marker typeLocalized 3", trackPoints.get(1).getPosition(), null), new Marker(null, trackId, "Marker 1", "Marker description 1", "Marker typeLocalized 3", trackPoints.get(1).position(), null),
new Marker(null, trackId, "Marker 2", "Marker description 2", "Marker typeLocalized 3", trackPoints.get(4).getPosition(), null), new Marker(null, trackId, "Marker 2", "Marker description 2", "Marker typeLocalized 3", trackPoints.get(4).position(), null),
new Marker(null, trackId, "Marker 3", "Marker description 3", "Marker typeLocalized 3", trackPoints.get(5).getPosition(), null) new Marker(null, trackId, "Marker 3", "Marker description 3", "Marker typeLocalized 3", trackPoints.get(5).position(), null)
); );
return new TrackData(track, trackPoints, markers); return new TrackData(track, trackPoints, markers);
@@ -112,8 +116,10 @@ public class TestDataUtil {
return pair.first; return pair.first;
} }
public static TrackPoint createTrackPoint(int i) { public static TrackPoint createTrackPoint(int i, TrackPoint.Type type, AltitudeGainLoss altitudeGainLoss) {
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, return new TrackPoint(
null,
type,
new Position( new Position(
Instant.ofEpochSecond(i), Instant.ofEpochSecond(i),
INITIAL_LATITUDE + (double) i / 10000.0, INITIAL_LATITUDE + (double) i / 10000.0,
@@ -123,21 +129,24 @@ public class TestDataUtil {
null, null,
null, null,
Speed.of(5f + (i / 10f)) Speed.of(5f + (i / 10f))
)); ),
null,
trackPoint.setHeartRate(100f + i % 80); HeartRate.of(100f + i % 80),
trackPoint.setCadence(300f + i); Cadence.of(300f + i),
trackPoint.setPower(400f + i); Power.of(400f + i),
trackPoint.setAltitudeGainLoss(ALTITUDE_GAIN, ALTITUDE_LOSS); altitudeGainLoss
return trackPoint; );
} }
public static TrackPoint createTrackPoint(int i, TrackPoint.Type type) { public static TrackPoint createTrackPoint(int i, TrackPoint.Type type) {
TrackPoint trackPoint = createTrackPoint(i); return createTrackPoint(i, TrackPoint.Type.TRACKPOINT, new AltitudeGainLoss(ALTITUDE_GAIN, ALTITUDE_LOSS));
trackPoint.setType(type);
return trackPoint;
} }
public static TrackPoint createTrackPoint(int i) {
return createTrackPoint(i, TrackPoint.Type.TRACKPOINT);
}
/** /**
* Inserts a track with locations into the database. * Inserts a track with locations into the database.
* *
@@ -164,7 +173,7 @@ public class TestDataUtil {
"Marker name", "Marker name",
description, description,
"Marker typeLocalized", "Marker typeLocalized",
trackPoint.getPosition(), trackPoint.position(),
photoUri); photoUri);
} }
@@ -6,8 +6,12 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import de.dennisguse.opentracks.data.models.Altitude; import de.dennisguse.opentracks.data.models.Altitude;
import de.dennisguse.opentracks.data.models.AltitudeGainLoss;
import de.dennisguse.opentracks.data.models.Cadence;
import de.dennisguse.opentracks.data.models.Distance; import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Position; import de.dennisguse.opentracks.data.models.Position;
import de.dennisguse.opentracks.data.models.Power;
import de.dennisguse.opentracks.data.models.Speed; import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.TrackPoint; import de.dennisguse.opentracks.data.models.TrackPoint;
@@ -20,7 +24,9 @@ public class TestSensorDataUtil {
sensorDataList.add(new TestSensorDataUtil.SensorData(time, hr, cadence, power, type)); sensorDataList.add(new TestSensorDataUtil.SensorData(time, hr, cadence, power, type));
int i = trackPointList.size() + 1; int i = trackPointList.size() + 1;
TrackPoint tp = new TrackPoint(type, TrackPoint tp = new TrackPoint(
null,
type,
new Position( new Position(
time, time,
TestDataUtil.INITIAL_LATITUDE + (double) i / 10000.0, TestDataUtil.INITIAL_LATITUDE + (double) i / 10000.0,
@@ -30,18 +36,14 @@ public class TestSensorDataUtil {
null, null,
null, null,
Speed.of(5f + (i / 10f)) Speed.of(5f + (i / 10f))
) ),
null,
HeartRate.ofOrNull(hr),
cadence != null ? Cadence.of(cadence) : null,
power != null ? Power.of(power) : null,
new AltitudeGainLoss(3, 3)
); );
if (hr != null) {
tp.setHeartRate(hr);
}
if (cadence != null) {
tp.setCadence(cadence);
}
if (power != null) {
tp.setPower(power);
}
tp.setAltitudeGainLoss(3, 3);
trackPointList.add(tp); trackPointList.add(tp);
} }
@@ -54,19 +54,22 @@ import de.dennisguse.opentracks.data.models.ActivityType;
import de.dennisguse.opentracks.data.models.Altitude; import de.dennisguse.opentracks.data.models.Altitude;
import de.dennisguse.opentracks.data.models.AltitudeExtremities; import de.dennisguse.opentracks.data.models.AltitudeExtremities;
import de.dennisguse.opentracks.data.models.AltitudeGainLoss; import de.dennisguse.opentracks.data.models.AltitudeGainLoss;
import de.dennisguse.opentracks.data.models.Cadence;
import de.dennisguse.opentracks.data.models.Distance; import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Marker; import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.models.MarkerBuilder; import de.dennisguse.opentracks.data.models.MarkerBuilder;
import de.dennisguse.opentracks.data.models.Position; import de.dennisguse.opentracks.data.models.Position;
import de.dennisguse.opentracks.data.models.Power;
import de.dennisguse.opentracks.data.models.Speed; import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Statistics; import de.dennisguse.opentracks.data.models.Statistics;
import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackBuilder; import de.dennisguse.opentracks.data.models.TrackBuilder;
import de.dennisguse.opentracks.data.models.TrackPoint; import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.data.statistics.SensorStatistics;
import de.dennisguse.opentracks.data.tables.MarkerColumns; import de.dennisguse.opentracks.data.tables.MarkerColumns;
import de.dennisguse.opentracks.data.tables.TrackPointsColumns; import de.dennisguse.opentracks.data.tables.TrackPointsColumns;
import de.dennisguse.opentracks.data.tables.TracksColumns; import de.dennisguse.opentracks.data.tables.TracksColumns;
import de.dennisguse.opentracks.data.statistics.SensorStatistics;
import de.dennisguse.opentracks.util.FileUtils; import de.dennisguse.opentracks.util.FileUtils;
/** /**
@@ -124,7 +127,7 @@ public class CustomContentProviderUtilsTest {
assertNotNull(trackPoint); assertNotNull(trackPoint);
locations.add(trackPoint); locations.add(trackPoint);
// Make sure the IDs are returned in the right order. // Make sure the IDs are returned in the right order.
assertEquals(lastPointId.id() - numPoints + locations.size(), trackPoint.getId().id()); assertEquals(lastPointId.id() - numPoints + locations.size(), trackPoint.id().id());
} }
assertEquals(numPoints, locations.size()); assertEquals(numPoints, locations.size());
} }
@@ -167,7 +170,7 @@ public class CustomContentProviderUtilsTest {
try (TrackPointIterator it = contentProviderUtils.getTrackPointIterator(id, null)) { try (TrackPointIterator it = contentProviderUtils.getTrackPointIterator(id, null)) {
while (it.hasNext()) { while (it.hasNext()) {
TrackPoint trackPoint = it.next(); TrackPoint trackPoint = it.next();
lastPointId = trackPoint.getId(); lastPointId = trackPoint.id();
counter++; counter++;
} }
} }
@@ -237,7 +240,7 @@ public class CustomContentProviderUtilsTest {
"", "",
"", "",
"", "",
getLastValidTrackPoint(trackId).getPosition(), getLastValidTrackPoint(trackId).position(),
null null
)); ));
@@ -300,7 +303,7 @@ public class CustomContentProviderUtilsTest {
"", "",
"", "",
"", "",
getLastValidTrackPoint(trackId1).getPosition(), getLastValidTrackPoint(trackId1).position(),
null null
)); ));
@@ -473,7 +476,7 @@ public class CustomContentProviderUtilsTest {
"", "",
TEST_DESC, TEST_DESC,
"", "",
getLastValidTrackPoint(trackId).getPosition(), getLastValidTrackPoint(trackId).position(),
null null
)); ));
@@ -553,7 +556,7 @@ public class CustomContentProviderUtilsTest {
"", "",
MOCK_DESC, MOCK_DESC,
"", "",
getLastValidTrackPoint(trackId).getPosition(), getLastValidTrackPoint(trackId).position(),
null null
)); ));
Marker.Id marker2Id = contentProviderUtils.insertMarker(new Marker( Marker.Id marker2Id = contentProviderUtils.insertMarker(new Marker(
@@ -562,7 +565,7 @@ public class CustomContentProviderUtilsTest {
"", "",
MOCK_DESC, MOCK_DESC,
"", "",
getLastValidTrackPoint(trackId).getPosition(), getLastValidTrackPoint(trackId).position(),
null null
)); ));
@@ -589,7 +592,7 @@ public class CustomContentProviderUtilsTest {
"", "",
MOCK_DESC, MOCK_DESC,
"", "",
getLastValidTrackPoint(trackId).getPosition(), getLastValidTrackPoint(trackId).position(),
null null
)); ));
} }
@@ -613,7 +616,7 @@ public class CustomContentProviderUtilsTest {
"", "",
TEST_DESC, TEST_DESC,
"", "",
getLastValidTrackPoint(trackId).getPosition(), getLastValidTrackPoint(trackId).position(),
null null
)); ));
@@ -636,7 +639,7 @@ public class CustomContentProviderUtilsTest {
"", "",
TEST_DESC, TEST_DESC,
"", "",
getLastValidTrackPoint(trackId).getPosition(), getLastValidTrackPoint(trackId).position(),
null null
)); ));
@@ -647,7 +650,7 @@ public class CustomContentProviderUtilsTest {
"", "",
TEST_DESC_NEW, TEST_DESC_NEW,
"", "",
getLastValidTrackPoint(trackId).getPosition(), getLastValidTrackPoint(trackId).position(),
null null
)); ));
@@ -803,20 +806,26 @@ public class CustomContentProviderUtilsTest {
Track.Id trackId = new Track.Id(System.currentTimeMillis()); Track.Id trackId = new Track.Id(System.currentTimeMillis());
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10); TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
TrackPoint trackPoint = TestDataUtil.createTrackPoint(5); TrackPoint trackPoint = new TrackPoint(
trackPoint.setHeartRate(1F); null,
trackPoint.setCadence(2F); TrackPoint.Type.TRACKPOINT,
trackPoint.setPower(3F); TestDataUtil.createTrackPoint(5).position(),
null,
HeartRate.of(1),
Cadence.of(2),
Power.of(3),
null
);
// when // when
contentProviderUtils.insertTrackPoint(trackPoint, trackId); contentProviderUtils.insertTrackPoint(trackPoint, trackId);
// then // then
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId); List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertTrue(trackPoints.get(10).hasHeartRate()); assertNotNull(trackPoints.get(10).heartRate());
assertEquals(trackPoint.getHeartRate(), trackPoints.get(10).getHeartRate()); assertEquals(trackPoint.heartRate(), trackPoints.get(10).heartRate());
assertEquals(trackPoint.getCadence(), trackPoints.get(10).getCadence()); assertEquals(trackPoint.cadence(), trackPoints.get(10).cadence());
assertEquals(trackPoint.getPower(), trackPoints.get(10).getPower()); assertEquals(trackPoint.power(), trackPoints.get(10).power());
} }
@Test @Test
@@ -857,9 +866,9 @@ public class CustomContentProviderUtilsTest {
for (int i = 0; i < trackpointIds.size(); i++) { for (int i = 0; i < trackpointIds.size(); i++) {
assertTrue(trackPointIterator.hasNext()); assertTrue(trackPointIterator.hasNext());
TrackPoint trackPoint = trackPointIterator.next(); TrackPoint trackPoint = trackPointIterator.next();
assertEquals(startTrackPointId.id() + i, trackPoint.getId().id()); assertEquals(startTrackPointId.id() + i, trackPoint.id().id());
Position position = trackPoint.getPosition(); Position position = trackPoint.position();
assertEquals(TestDataUtil.INITIAL_LATITUDE + i / 10000.0, position.latitude(), 0.01); assertEquals(TestDataUtil.INITIAL_LATITUDE + i / 10000.0, position.latitude(), 0.01);
assertEquals(TestDataUtil.INITIAL_LONGITUDE - i / 10000.0, position.longitude(), 0.01); assertEquals(TestDataUtil.INITIAL_LONGITUDE - i / 10000.0, position.longitude(), 0.01);
assertEquals(i / 100.0, position.horizontalAccuracy().distance_m(), 0.01); assertEquals(i / 100.0, position.horizontalAccuracy().distance_m(), 0.01);
@@ -879,11 +888,17 @@ public class CustomContentProviderUtilsTest {
public void testGetSensorStats_noSensorData() { public void testGetSensorStats_noSensorData() {
// given // given
List<TrackPoint> trackPointList = new ArrayList<>(); List<TrackPoint> trackPointList = new ArrayList<>();
TrackPoint trackPoint = TestDataUtil.createTrackPoint(1); TrackPoint trackPoint = new TrackPoint(
trackPoint.setType(TrackPoint.Type.TRACKPOINT); null,
trackPoint.setPower(null); TrackPoint.Type.TRACKPOINT,
trackPoint.setCadence(null); TestDataUtil.createTrackPoint(1).position(),
trackPoint.setHeartRate(null); null,
null,
null,
null,
null
);
trackPointList.add(trackPoint); trackPointList.add(trackPoint);
Track.Id trackId = new Track.Id(System.currentTimeMillis()); Track.Id trackId = new Track.Id(System.currentTimeMillis());
Track track = TestDataUtil.createTrack(trackId); Track track = TestDataUtil.createTrack(trackId);
@@ -1217,7 +1232,7 @@ public class CustomContentProviderUtilsTest {
return trackpoints.reversed() return trackpoints.reversed()
.stream() .stream()
.filter(it -> List.of(TrackPoint.Type.TRACKPOINT, TrackPoint.Type.SEGMENT_START_AUTOMATIC).contains(it.getType())) .filter(it -> List.of(TrackPoint.Type.TRACKPOINT, TrackPoint.Type.SEGMENT_START_AUTOMATIC).contains(it.type()))
.findFirst().orElseThrow(); .findFirst().orElseThrow();
} }
@@ -13,7 +13,7 @@ import de.dennisguse.opentracks.data.models.TrackPoint;
public class TrackPointTest { public class TrackPointTest {
@Test @Test
public void distanceToPrevious() { public void distanceToPrevious_gps() {
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.TRACKPOINT, TrackPoint tp1 = new TrackPoint(TrackPoint.Type.TRACKPOINT,
new Position( new Position(
Instant.ofEpochMilli(0), Instant.ofEpochMilli(0),
@@ -38,20 +38,119 @@ public class TrackPointTest {
// without sensor distance // without sensor distance
assertEquals(11.13, tp2.distanceToPrevious(tp1).toM(), 0.01); assertEquals(11.13, tp2.distanceToPrevious(tp1).toM(), 0.01);
}
@Test
public void distanceToPrevious_sensor_distance_first() {
TrackPoint tp1 = new TrackPoint(
null,
TrackPoint.Type.TRACKPOINT,
new Position(
Instant.ofEpochMilli(0),
0d,
0.0001,
null,
null,
null,
null,
null),
Distance.of(5),
null,
null,
null,
null
);
TrackPoint tp2 = new TrackPoint(TrackPoint.Type.TRACKPOINT,
new Position(
Instant.ofEpochMilli(1),
0d,
0.0002,
null,
null,
null,
null,
null));
// tp1 has sensor distance // tp1 has sensor distance
tp1.setSensorDistance(Distance.of(5));
tp1.setSensorDistance(null);
assertEquals(11.13, tp2.distanceToPrevious(tp1).toM(), 0.01); assertEquals(11.13, tp2.distanceToPrevious(tp1).toM(), 0.01);
}
@Test
public void distanceToPrevious_sensor_distance_second() {
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.TRACKPOINT,
new Position(
Instant.ofEpochMilli(0),
0d,
0.0001,
null,
null,
null,
null,
null));
TrackPoint tp2 = new TrackPoint(
null,
TrackPoint.Type.TRACKPOINT,
new Position(
Instant.ofEpochMilli(1),
0d,
0.0002,
null,
null,
null,
null,
null),
Distance.of(5),
null,
null,
null,
null);
// tp2 has sensor distance // tp2 has sensor distance
tp1.setSensorDistance(null);
tp2.setSensorDistance(Distance.of(5));
assertEquals(5, tp2.distanceToPrevious(tp1).toM(), 0.01); assertEquals(5, tp2.distanceToPrevious(tp1).toM(), 0.01);
}
@Test
public void distanceToPrevious_sensor_distance_both() {
TrackPoint tp1 = new TrackPoint(
null,
TrackPoint.Type.TRACKPOINT,
new Position(
Instant.ofEpochMilli(0),
0d,
0.0001,
null,
null,
null,
null,
null),
Distance.of(10),
null,
null,
null,
null
);
TrackPoint tp2 = new TrackPoint(
null,
TrackPoint.Type.TRACKPOINT,
new Position(
Instant.ofEpochMilli(1),
0d,
0.0002,
null,
null,
null,
null,
null),
Distance.of(5),
null,
null,
null,
null);
// tp1 and tp2 have sensor distance // tp1 and tp2 have sensor distance
tp1.setSensorDistance(Distance.of(10));
tp2.setSensorDistance(Distance.of(5));
assertEquals(5, tp2.distanceToPrevious(tp1).toM(), 0.01); assertEquals(5, tp2.distanceToPrevious(tp1).toM(), 0.01);
} }
} }
@@ -26,7 +26,6 @@ import org.junit.BeforeClass;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mockito;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
@@ -74,7 +73,7 @@ import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingDistanceSpee
import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingPower; import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingPower;
import de.dennisguse.opentracks.sensors.sensorData.AggregatorHeartRate; import de.dennisguse.opentracks.sensors.sensorData.AggregatorHeartRate;
import de.dennisguse.opentracks.sensors.sensorData.Raw; import de.dennisguse.opentracks.sensors.sensorData.Raw;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet; import de.dennisguse.opentracks.sensors.sensorData.SensorDataAggregator;
import de.dennisguse.opentracks.services.TrackRecordingService; import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.handlers.TrackPointCreator; import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
@@ -123,11 +122,13 @@ public class ExportImportTest {
private TrackImporter trackImporter; private TrackImporter trackImporter;
@Before @Before
public void fileSetup() throws IOException { public void fileSetup() throws IOException, TimeoutException {
tmpFile = File.createTempFile("test", "test", context.getFilesDir()); tmpFile = File.createTempFile("test", "test", context.getFilesDir());
tmpFileUri = Uri.fromFile(tmpFile); tmpFileUri = Uri.fromFile(tmpFile);
trackImporter = new TrackImporter(context, contentProviderUtils, Distance.of(200), true); trackImporter = new TrackImporter(context, contentProviderUtils, Distance.of(200), true);
setUp();
} }
@After @After
@@ -158,7 +159,15 @@ public class ExportImportTest {
"Marker 1", "Marker 1",
"Marker 1 desc", "Marker 1 desc",
"Marker 1 typeLocalized", "Marker 1 typeLocalized",
service.getLastStoredTrackPointWithLocation().getPosition(), new Position(
Instant.parse("2020-02-02T02:02:03Z"),
3.1234567, 14.0014567,
Distance.of(10),
Altitude.WGS84.of(1020.25),
Distance.of(13),
null,
Speed.of(15)
),
null null
)); ));
@@ -166,7 +175,7 @@ public class ExportImportTest {
trackPointCreator.setClock("2020-02-02T02:02:04Z"); trackPointCreator.setClock("2020-02-02T02:02:04Z");
mockSensorData(trackPointCreator, 15f, sensorDistance, 66f, 3f, 50f, 1f); mockSensorData(trackPointCreator, 15f, sensorDistance, 66f, 3f, 50f, 1f);
trackPointCreator.setClock("2020-02-02T02:02:14Z"); trackPointCreator.setClock("2020-02-02T02:02:14Z"); //ignored
mockSensorData(trackPointCreator, 15f, null, 67f, 3f, 50f, null); mockSensorData(trackPointCreator, 15f, null, 67f, 3f, 50f, null);
trackPointCreator.setClock("2020-02-02T02:02:15Z"); trackPointCreator.setClock("2020-02-02T02:02:15Z");
mockSensorData(trackPointCreator, null, null, 68f, 3f, 50f, null); mockSensorData(trackPointCreator, null, null, 68f, 3f, 50f, null);
@@ -181,12 +190,12 @@ public class ExportImportTest {
"Marker 2", "Marker 2",
"Marker 2 desc", "Marker 2 desc",
"Marker 2 typeLocalized", "Marker 2 typeLocalized",
service.getLastStoredTrackPointWithLocation().getPosition(), service.getLastStoredTrackPointWithLocation().position(),
null null
)); ));
trackPointCreator.setClock("2020-02-02T02:02:18Z"); trackPointCreator.setClock("2020-02-02T02:02:18Z");
trackPointCreator.getSensorManager().sensorDataSet = new SensorDataSet(trackPointCreator); trackPointCreator.getSensorManager().sensorDataAggregator = new SensorDataAggregator();
service.endCurrentTrack(); service.endCurrentTrack();
trackPointCreator.setClock("2020-02-02T02:03:20Z"); trackPointCreator.setClock("2020-02-02T02:03:20Z");
@@ -201,7 +210,7 @@ public class ExportImportTest {
sendLocation(trackPointCreator, "2020-02-02T02:03:50Z", 3.1234567, 16.001, 10, 27, 15, 999.123, 0f); sendLocation(trackPointCreator, "2020-02-02T02:03:50Z", 3.1234567, 16.001, 10, 27, 15, 999.123, 0f);
trackPointCreator.getSensorManager().sensorDataSet = new SensorDataSet(trackPointCreator); trackPointCreator.getSensorManager().sensorDataAggregator = new SensorDataAggregator();
trackPointCreator.setClock("2020-02-02T02:04:00Z"); trackPointCreator.setClock("2020-02-02T02:04:00Z");
service.endCurrentTrack(); service.endCurrentTrack();
@@ -218,13 +227,126 @@ public class ExportImportTest {
@LargeTest @LargeTest
@Test @Test
public void track() throws TimeoutException { public void track() throws TimeoutException {
setUp();
Track track = contentProviderUtils.getTrack(trackId); Track track = contentProviderUtils.getTrack(trackId);
Statistics trackStatistics = track.statistics(); Statistics trackStatistics = track.statistics();
assertEquals(ZoneOffset.of("+01:00"), track.zoneOffset()); assertEquals(ZoneOffset.of("+01:00"), track.zoneOffset());
List<TrackPoint> actual = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
new TrackPointAssert().assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse("2020-02-02T02:02:02Z")),
new TrackPoint(
null,
TrackPoint.Type.TRACKPOINT,
new Position(
Instant.parse("2020-02-02T02:02:03Z"),
3.123456, 14.001456, Distance.of(10),
Altitude.WGS84.of(1020.25), null,
null,
Speed.of(15)),
null,
null,
null,
null,
new AltitudeGainLoss(1, 1)
),
new TrackPoint(
null,
TrackPoint.Type.TRACKPOINT,
new Position(
Instant.parse("2020-02-02T02:02:04Z"),
null, null, null,
null, null,
null,
Speed.of(15)),
Distance.of(10),
HeartRate.of(66),
Cadence.of(3),
Power.of(50),
new AltitudeGainLoss(1, 1)
),
new TrackPoint(
null,
TrackPoint.Type.TRACKPOINT,
Position.of(Instant.parse("2020-02-02T02:02:15Z")),
null,
HeartRate.of(68),
Cadence.of(3),
Power.of(50),
null
),
new TrackPoint(
null,
TrackPoint.Type.TRACKPOINT,
new Position(
Instant.parse("2020-02-02T02:02:17Z"),
3.123456, 14.001456, Distance.of(10),
Altitude.WGS84.of(1020.25), null,
null,
Speed.of(5)),
Distance.of(2),
HeartRate.of(69),
Cadence.of(3),
Power.of(50),
new AltitudeGainLoss(0, 0)
),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse("2020-02-02T02:02:18Z")),
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse("2020-02-02T02:03:20Z")),
new TrackPoint(
null,
TrackPoint.Type.TRACKPOINT,
new Position(
Instant.parse("2020-02-02T02:03:21Z"),
3.123456, 14.002456, Distance.of(10),
Altitude.WGS84.of(999.1229858398438), null,
null,
Speed.of(15)),
null,
null,
null,
null,
new AltitudeGainLoss(0, 0)
),
new TrackPoint(
null,
TrackPoint.Type.SEGMENT_START_AUTOMATIC,
new Position(Instant.parse("2020-02-02T02:03:22Z"),
3.123456, 16d, Distance.of(10),
Altitude.WGS84.of(999.1229858398438), null,
null,
Speed.of(15)),
null,
null,
null,
null,
new AltitudeGainLoss(0, 0)
),
new TrackPoint(
null,
TrackPoint.Type.IDLE,
Position.of(Instant.parse("2020-02-02T02:03:30Z")),
null,
null,
null,
null,
new AltitudeGainLoss(0, 0)
),
new TrackPoint(
null,
TrackPoint.Type.TRACKPOINT,
new Position(
Instant.parse("2020-02-02T02:03:50Z"),
3.123456, 16.001, Distance.of(10),
Altitude.WGS84.of(999.1229858398438), null,
null, Speed.of(15)),
null,
null,
null,
null,
new AltitudeGainLoss(0, 0)),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse("2020-02-02T02:04:00Z"))
), actual);
assertEquals(new Statistics( assertEquals(new Statistics(
Instant.parse("2020-02-02T02:02:02Z"), Instant.parse("2020-02-02T02:02:02Z"),
Instant.parse("2020-02-02T02:04:00Z"), Instant.parse("2020-02-02T02:04:00Z"),
@@ -238,77 +360,12 @@ public class ExportImportTest {
null null
), ),
trackStatistics); trackStatistics);
List<TrackPoint> actual = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
new TrackPointAssert().assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse("2020-02-02T02:02:02Z")),
new TrackPoint(TrackPoint.Type.TRACKPOINT,
new Position(
Instant.parse("2020-02-02T02:02:03Z"),
3.123456, 14.001456, Distance.of(10),
Altitude.WGS84.of(1020.25), null,
null,
Speed.of(15)))
.setAltitudeGainLoss(1, 1),
new TrackPoint(TrackPoint.Type.TRACKPOINT,
Instant.parse("2020-02-02T02:02:04Z"))
.setSensorDistance(Distance.of(10))
.setSpeed(Speed.of(15))
.setHeartRate(HeartRate.of(66))
.setCadence(3)
.setPower(50).setAltitudeGainLoss(1, 1),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-02-02T02:02:15Z"))
.setHeartRate(HeartRate.of(68))
.setCadence(3)
.setPower(50),
new TrackPoint(TrackPoint.Type.TRACKPOINT,
new Position(
Instant.parse("2020-02-02T02:02:17Z"),
3.123456, 14.001456, Distance.of(10),
Altitude.WGS84.of(1020.25), null,
null,
Speed.of(5)))
.setSensorDistance(Distance.of(2))
.setAltitudeGainLoss(0, 0)
.setHeartRate(HeartRate.of(69))
.setCadence(3)
.setPower(50),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse("2020-02-02T02:02:18Z")),
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse("2020-02-02T02:03:20Z")),
new TrackPoint(TrackPoint.Type.TRACKPOINT,
new Position(
Instant.parse("2020-02-02T02:03:21Z"),
3.123456, 14.002456, Distance.of(10),
Altitude.WGS84.of(999.1229858398438), null,
null,
Speed.of(15)))
.setAltitudeGainLoss(0, 0),
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC,
new Position(Instant.parse("2020-02-02T02:03:22Z"),
3.123456, 16d, Distance.of(10),
Altitude.WGS84.of(999.1229858398438), null,
null,
Speed.of(15)))
.setAltitudeGainLoss(0, 0),
new TrackPoint(TrackPoint.Type.IDLE, Instant.parse("2020-02-02T02:03:30Z"))
.setAltitudeGainLoss(0, 0),
new TrackPoint(TrackPoint.Type.TRACKPOINT,
new Position(
Instant.parse("2020-02-02T02:03:50Z"),
3.123456, 16.001, Distance.of(10),
Altitude.WGS84.of(999.1229858398438), null,
null, Speed.of(15)))
.setAltitudeGainLoss(0, 0),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse("2020-02-02T02:04:00Z"))
), actual);
} }
//TODO Does not test marker images //TODO Does not test marker images
@LargeTest @LargeTest
@Test @Test
public void kmz_with_trackdetail_and_sensordata() throws TimeoutException, IOException { public void kmz_with_trackdetail_and_sensordata() throws TimeoutException, IOException {
setUp();
// given // given
Track track = contentProviderUtils.getTrack(trackId); Track track = contentProviderUtils.getTrack(trackId);
@@ -361,8 +418,6 @@ public class ExportImportTest {
@LargeTest @LargeTest
@Test(expected = ImportAlreadyExistsException.class) @Test(expected = ImportAlreadyExistsException.class)
public void kml_with_trackdetail_and_sensordata_duplicate_trackUUID() throws TimeoutException, IOException { public void kml_with_trackdetail_and_sensordata_duplicate_trackUUID() throws TimeoutException, IOException {
setUp();
// given // given
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit(); SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
editor.putBoolean(context.getString(R.string.import_prevent_reimport_key), true); editor.putBoolean(context.getString(R.string.import_prevent_reimport_key), true);
@@ -387,8 +442,6 @@ public class ExportImportTest {
@LargeTest @LargeTest
@Test @Test
public void gpx() throws TimeoutException, IOException { public void gpx() throws TimeoutException, IOException {
setUp();
// given // given
Track track = contentProviderUtils.getTrack(trackId); Track track = contentProviderUtils.getTrack(trackId);
@@ -421,51 +474,81 @@ public class ExportImportTest {
.setDelta(0.05); // speed is not fully .setDelta(0.05); // speed is not fully
List<TrackPoint> actual = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId); List<TrackPoint> actual = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
a.assertEquals(List.of( a.assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, new TrackPoint(
null,
TrackPoint.Type.SEGMENT_START_AUTOMATIC,
new Position( new Position(
Instant.parse("2020-02-02T02:02:03Z"), Instant.parse("2020-02-02T02:02:03Z"),
3.123456, 14.001456d, Distance.of(10), 3.123456, 14.001456d, Distance.of(10),
Altitude.WGS84.of(1020.2), null, Altitude.WGS84.of(1020.2), null,
null, null,
Speed.of(15))) Speed.of(15)),
.setAltitudeGainLoss(1, 1), null,
new TrackPoint(TrackPoint.Type.TRACKPOINT, null,
null,
null,
new AltitudeGainLoss(1, 1)
),
new TrackPoint(
null,
TrackPoint.Type.TRACKPOINT,
new Position( new Position(
Instant.parse("2020-02-02T02:02:17Z"), Instant.parse("2020-02-02T02:02:17Z"),
3.123456, 14.001456, Distance.of(10), 3.123456, 14.001456, Distance.of(10),
Altitude.WGS84.of(1020.2), null, Altitude.WGS84.of(1020.2), null,
null, null,
Speed.of(5))) Speed.of(5)),
.setAltitudeGainLoss(1, 1) Distance.of(12),
.setSensorDistance(Distance.of(12)) HeartRate.of(69),
.setHeartRate(69) Cadence.of(3),
.setPower(50f) Power.of(50),
.setCadence(3f), new AltitudeGainLoss(1, 1)
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, ),
new TrackPoint(
null,
TrackPoint.Type.SEGMENT_START_AUTOMATIC,
new Position( new Position(
Instant.parse("2020-02-02T02:03:21Z"), Instant.parse("2020-02-02T02:03:21Z"),
3.123456, 14.002456, Distance.of(10), 3.123456, 14.002456, Distance.of(10),
Altitude.WGS84.of(999.0999755859375), null, Altitude.WGS84.of(999.0999755859375), null,
null, null,
Speed.of(15))) Speed.of(15)),
.setAltitudeGainLoss(0, 0), null,
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, null,
null,
null,
new AltitudeGainLoss(0, 0)
),
new TrackPoint(
null,
TrackPoint.Type.SEGMENT_START_AUTOMATIC,
new Position( new Position(
Instant.parse("2020-02-02T02:03:22Z"), Instant.parse("2020-02-02T02:03:22Z"),
3.123456, 16d, Distance.of(10), 3.123456, 16d, Distance.of(10),
Altitude.WGS84.of(999.0999755859375), null, Altitude.WGS84.of(999.0999755859375), null,
null, null,
Speed.of(15))) Speed.of(15)),
.setAltitudeGainLoss(0, 0), null,
new TrackPoint(TrackPoint.Type.TRACKPOINT, null,
null,
null,
new AltitudeGainLoss(0, 0)
),
new TrackPoint(
null,
TrackPoint.Type.TRACKPOINT,
new Position( new Position(
Instant.parse("2020-02-02T02:03:50Z"), Instant.parse("2020-02-02T02:03:50Z"),
3.123456, 16.001, Distance.of(10), 3.123456, 16.001, Distance.of(10),
Altitude.WGS84.of(999.0999755859375), null, Altitude.WGS84.of(999.0999755859375), null,
null, null,
Speed.of(10))) Speed.of(15)),
.setAltitudeGainLoss(0, 0) null,
.setSpeed(Speed.of(15)) null,
null,
null,
new AltitudeGainLoss(0, 0)
)
), actual); ), actual);
// 3. trackstatistics // 3. trackstatistics
@@ -493,8 +576,6 @@ public class ExportImportTest {
@LargeTest @LargeTest
@Test(expected = ImportAlreadyExistsException.class) @Test(expected = ImportAlreadyExistsException.class)
public void gpx_duplicate_trackUUID() throws TimeoutException, IOException { public void gpx_duplicate_trackUUID() throws TimeoutException, IOException {
setUp();
// given // given
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit(); SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
editor.putBoolean(context.getString(R.string.import_prevent_reimport_key), true); editor.putBoolean(context.getString(R.string.import_prevent_reimport_key), true);
@@ -521,8 +602,6 @@ public class ExportImportTest {
@LargeTest @LargeTest
@Test @Test
public void csv_export_only() throws TimeoutException, IOException { public void csv_export_only() throws TimeoutException, IOException {
setUp();
// given // given
Track track = contentProviderUtils.getTrack(trackId); Track track = contentProviderUtils.getTrack(trackId);
@@ -567,16 +646,16 @@ public class ExportImportTest {
} }
private void mockSensorData(TrackPointCreator trackPointCreator, Float speed, Distance distance, float heartRate, float cadence, Float power, Float altitudeGain) { private void mockSensorData(TrackPointCreator trackPointCreator, Float speed, Distance distance, float heartRate, float cadence, Float power, Float altitudeGain) {
SensorDataSet sensorDataSet = trackPointCreator.getSensorManager().sensorDataSet; SensorDataAggregator sensorDataAggregator = trackPointCreator.getSensorManager().sensorDataAggregator;
AggregatorCyclingPower cyclingPower = new AggregatorCyclingPower("", ""); AggregatorCyclingPower cyclingPower = new AggregatorCyclingPower("", "");
cyclingPower.add(new Raw<>(trackPointCreator.createNow(), new BluetoothHandlerManagerCyclingPower.Data(Power.of(power), null))); cyclingPower.add(new Raw<>(trackPointCreator.createNow(), new BluetoothHandlerManagerCyclingPower.Data(Power.of(power), null)));
sensorDataSet.add(cyclingPower); sensorDataAggregator.add(cyclingPower);
AggregatorHeartRate avgHeartRate = new AggregatorHeartRate("", ""); AggregatorHeartRate avgHeartRate = new AggregatorHeartRate("", "");
avgHeartRate.add(new Raw<>(trackPointCreator.createNow(), HeartRate.of(heartRate))); avgHeartRate.add(new Raw<>(trackPointCreator.createNow(), HeartRate.of(heartRate)));
sensorDataSet.add(avgHeartRate); sensorDataAggregator.add(avgHeartRate);
AggregatorCyclingCadence cyclingCadence = new AggregatorCyclingCadence("", "") { AggregatorCyclingCadence cyclingCadence = new AggregatorCyclingCadence("", "") {
@NonNull @NonNull
@@ -590,33 +669,52 @@ public class ExportImportTest {
return true; return true;
} }
}; };
sensorDataSet.add(cyclingCadence); sensorDataAggregator.add(cyclingCadence);
if (distance != null && speed != null) { if (distance != null && speed != null) {
AggregatorCyclingDistanceSpeed distanceSpeed = Mockito.mock(AggregatorCyclingDistanceSpeed.class); AggregatorCyclingDistanceSpeed aggregatorCyclingDistanceSpeed = new AggregatorCyclingDistanceSpeed("", "") {
Mockito.when(distanceSpeed.hasReceivedData()).thenReturn(true); @NonNull
Mockito.when(distanceSpeed.getAggregatedValue(Mockito.any())).thenReturn(new AggregatorCyclingDistanceSpeed.Data(null, distance, Speed.of(speed))); @Override
sensorDataSet.add(distanceSpeed); public Data getAggregatedValue(Instant now) {
return new AggregatorCyclingDistanceSpeed.Data(null, distance, Speed.of(speed));
}
@Override
public boolean hasReceivedData() {
return true;
}
};
sensorDataAggregator.add(aggregatorCyclingDistanceSpeed);
} else { } else {
sensorDataSet.add(new AggregatorCyclingDistanceSpeed("", "")); sensorDataAggregator.add(new AggregatorCyclingDistanceSpeed("", ""));
} }
mockAltitudeChange(trackPointCreator, altitudeGain); mockAltitudeChange(trackPointCreator, altitudeGain);
trackPointCreator.onChange(sensorDataSet); trackPointCreator.onChange();
} }
private void mockAltitudeChange(TrackPointCreator trackPointCreator, Float altitudeGain) { private void mockAltitudeChange(TrackPointCreator trackPointCreator, Float altitudeGain) {
SensorDataSet sensorDataSet = trackPointCreator.getSensorManager().sensorDataSet; SensorDataAggregator sensorDataAggregator = trackPointCreator.getSensorManager().sensorDataAggregator;
if (altitudeGain != null) { if (altitudeGain == null) {
AggregatorBarometer barometer = Mockito.mock(AggregatorBarometer.class); sensorDataAggregator.add(new AggregatorBarometer("test", null));
Mockito.when(barometer.hasReceivedData()).thenReturn(true); return;
Mockito.when(barometer.getAggregatedValue(Mockito.any())).thenReturn(new AltitudeGainLoss(altitudeGain, altitudeGain));
sensorDataSet.add(barometer);
} else {
sensorDataSet.add(new AggregatorBarometer("test", null));
} }
AggregatorBarometer aggregatorBarometer = new AggregatorBarometer("", "") {
@NonNull
@Override
public AltitudeGainLoss getAggregatedValue(Instant now) {
return new AltitudeGainLoss(altitudeGain, altitudeGain);
}
@Override
public boolean hasReceivedData() {
return true;
}
};
sensorDataAggregator.add(aggregatorBarometer);
} }
private void sendLocation(TrackPointCreator trackPointCreator, String time, double latitude, double longitude, float accuracy, float verticalAccuracy, float speed, double altitude, Float altitudeGain) { private void sendLocation(TrackPointCreator trackPointCreator, String time, double latitude, double longitude, float accuracy, float verticalAccuracy, float speed, double altitude, Float altitudeGain) {
@@ -27,8 +27,12 @@ import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.data.ContentProviderUtils; import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.ActivityType; import de.dennisguse.opentracks.data.models.ActivityType;
import de.dennisguse.opentracks.data.models.Altitude; import de.dennisguse.opentracks.data.models.Altitude;
import de.dennisguse.opentracks.data.models.AltitudeGainLoss;
import de.dennisguse.opentracks.data.models.Cadence;
import de.dennisguse.opentracks.data.models.Distance; import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Position; import de.dennisguse.opentracks.data.models.Position;
import de.dennisguse.opentracks.data.models.Power;
import de.dennisguse.opentracks.data.models.Speed; import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Statistics; import de.dennisguse.opentracks.data.models.Statistics;
import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.data.models.Track;
@@ -193,51 +197,81 @@ public class GPXTrackImporterTest {
TrackPointAssert a = new TrackPointAssert() TrackPointAssert a = new TrackPointAssert()
.setDelta(0.05); // speed is not fully .setDelta(0.05); // speed is not fully
a.assertEquals(List.of( a.assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, new TrackPoint(
null,
TrackPoint.Type.SEGMENT_START_AUTOMATIC,
new Position( new Position(
Instant.parse("2020-02-02T02:02:03Z"), Instant.parse("2020-02-02T02:02:03Z"),
3d, 14d, Distance.of(10), 3d, 14d, Distance.of(10),
Altitude.WGS84.of(10), null, Altitude.WGS84.of(10), null,
null, null,
Speed.of(15))) Speed.of(15)),
.setAltitudeGainLoss(1, 1), null,
new TrackPoint(TrackPoint.Type.TRACKPOINT, null,
null,
null,
new AltitudeGainLoss(1, 1)
),
new TrackPoint(
null,
TrackPoint.Type.TRACKPOINT,
new Position( new Position(
Instant.parse("2020-02-02T02:02:17Z"), Instant.parse("2020-02-02T02:02:17Z"),
3d, 14.001, Distance.of(10), 3d, 14.001, Distance.of(10),
Altitude.WGS84.of(10), null, Altitude.WGS84.of(10), null,
null, null,
Speed.of(5))) Speed.of(5)),
.setAltitudeGainLoss(1, 1) Distance.of(12),
.setSensorDistance(Distance.of(12)) HeartRate.of(69),
.setHeartRate(69) Cadence.of(3),
.setPower(50f) Power.of(50),
.setCadence(3f), new AltitudeGainLoss(1, 1)
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, ),
new TrackPoint(
null,
TrackPoint.Type.SEGMENT_START_AUTOMATIC,
new Position( new Position(
Instant.parse("2020-02-02T02:03:21Z"), Instant.parse("2020-02-02T02:03:21Z"),
3d, 14.002, Distance.of(10), 3d, 14.002, Distance.of(10),
Altitude.WGS84.of(10), null, Altitude.WGS84.of(10), null,
null, null,
Speed.of(15))) Speed.of(15)),
.setAltitudeGainLoss(0, 0), null,
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, null,
null,
null,
new AltitudeGainLoss(0, 0)
),
new TrackPoint(
null,
TrackPoint.Type.SEGMENT_START_AUTOMATIC,
new Position( new Position(
Instant.parse("2020-02-02T02:03:22Z"), Instant.parse("2020-02-02T02:03:22Z"),
3d, 16d, Distance.of(10), 3d, 16d, Distance.of(10),
Altitude.WGS84.of(10), null, Altitude.WGS84.of(10), null,
null, null,
Speed.of(15))) Speed.of(15)),
.setAltitudeGainLoss(0, 0), null,
new TrackPoint(TrackPoint.Type.TRACKPOINT, null,
null,
null,
new AltitudeGainLoss(0, 0)
),
new TrackPoint(
null,
TrackPoint.Type.TRACKPOINT,
new Position( new Position(
Instant.parse("2020-02-02T02:03:50Z"), Instant.parse("2020-02-02T02:03:50Z"),
3d, 16.001, Distance.of(10), 3d, 16.001, Distance.of(10),
Altitude.WGS84.of(10), null, Altitude.WGS84.of(10), null,
null, null,
Speed.of(10))) Speed.of(15)),
.setAltitudeGainLoss(0, 0) null,
.setSpeed(Speed.of(15)) null,
null,
null,
new AltitudeGainLoss(0, 0)
)
), actual); ), actual);
} }
@@ -247,7 +281,7 @@ public class GPXTrackImporterTest {
// given // given
XMLImporter importer = new XMLImporter(new GPXTrackImporter(context, trackImporter)); XMLImporter importer = new XMLImporter(new GPXTrackImporter(context, trackImporter));
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx_timezone); InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx_timezone);
try(InputStream inputStreamExpected = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx_timezone)) { try (InputStream inputStreamExpected = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx_timezone)) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// when // when
@@ -16,57 +16,45 @@ public class TrackPointAssert {
public void assertEquals(TrackPoint expected, TrackPoint actual) { public void assertEquals(TrackPoint expected, TrackPoint actual) {
Assert.assertEquals("time", expected.getTime(), actual.getTime()); Assert.assertEquals("time", expected.getTime(), actual.getTime());
Assert.assertEquals("type", expected.getType(), actual.getType()); Assert.assertEquals("type", expected.type(), actual.type());
Assert.assertEquals("has location,", expected.hasLocation(), actual.hasLocation()); Assert.assertEquals("has location,", expected.position().hasLocation(), actual.position().hasLocation());
if (expected.hasLocation()) { if (expected.position().hasLocation()) {
Assert.assertEquals("latitude", expected.getPosition().latitude(), actual.getPosition().latitude(), 0.001); Assert.assertEquals("latitude", expected.position().latitude(), actual.position().latitude(), 0.001);
Assert.assertEquals("longitude", expected.getPosition().longitude(), actual.getPosition().longitude(), 0.001); Assert.assertEquals("longitude", expected.position().longitude(), actual.position().longitude(), 0.001);
} }
Assert.assertEquals("has altitude", expected.hasAltitude(), actual.hasAltitude()); Assert.assertEquals("has altitude", expected.position().hasAltitude(), actual.position().hasAltitude());
if (expected.hasAltitude()) { if (expected.position().hasAltitude()) {
Assert.assertEquals("altitude", expected.getAltitude().toM(), actual.getAltitude().toM(), delta); Assert.assertEquals("altitude", expected.position().altitude().toM(), actual.position().altitude().toM(), delta);
} }
Assert.assertEquals("has altitudeGainLoss", expected.hasAltitudeGainLoss(), actual.hasAltitudeGainLoss()); Assert.assertEquals("altitudeGainLoss", expected.altitudeGainLoss(), actual.altitudeGainLoss());
if (expected.hasAltitudeGainLoss()) {
Assert.assertEquals("altitudeGainLoss", expected.getAltitudeGainLoss(), actual.getAltitudeGainLoss()); Assert.assertEquals("has speed", expected.position().hasSpeed(), actual.position().hasSpeed());
if (expected.position().hasSpeed()) {
Assert.assertEquals("speed", expected.position().speed().toMPS(), actual.position().speed().toMPS(), delta);
} }
Assert.assertEquals("has speed", expected.hasSpeed(), actual.hasSpeed()); Assert.assertEquals("has horizontalAccuracy", expected.position().hasHorizontalAccuracy(), actual.position().hasHorizontalAccuracy());
if (expected.hasSpeed()) { if (expected.position().hasHorizontalAccuracy()) {
Assert.assertEquals("speed", expected.getSpeed().toMPS(), actual.getSpeed().toMPS(), delta); Assert.assertEquals("horizontalAccuracy", expected.position().horizontalAccuracy().toM(), actual.position().horizontalAccuracy().toM(), delta);
}
Assert.assertEquals("has verticalAccuracy", expected.position().hasVerticalAccuracy(), actual.position().hasVerticalAccuracy());
if (expected.position().hasVerticalAccuracy()) {
Assert.assertEquals("verticalAccuracy", expected.position().verticalAccuracy().toM(), actual.position().verticalAccuracy().toM(), delta);
} }
Assert.assertEquals("has horizontalAccuracy", expected.hasHorizontalAccuracy(), actual.hasHorizontalAccuracy()); Assert.assertEquals("has sensorDistance", expected.sensorDistance() != null, actual.sensorDistance() != null);
if (expected.hasHorizontalAccuracy()) { if (expected.sensorDistance() != null) {
Assert.assertEquals("horizontalAccuracy", expected.getHorizontalAccuracy().toM(), actual.getHorizontalAccuracy().toM(), delta); Assert.assertEquals("sensorDistance", expected.sensorDistance().toM(), actual.sensorDistance().toM(), delta);
}
Assert.assertEquals("has verticalAccuracy", expected.hasVerticalAccuracy(), actual.hasVerticalAccuracy());
if (expected.hasVerticalAccuracy()) {
Assert.assertEquals("verticalAccuracy", expected.getVerticalAccuracy().toM(), actual.getVerticalAccuracy().toM(), delta);
} }
Assert.assertEquals("has sensorDistance", expected.hasSensorDistance(), actual.hasSensorDistance()); Assert.assertEquals("heartRate", expected.heartRate(), actual.heartRate());
if (expected.hasSensorDistance()) {
Assert.assertEquals("sensorDistance", expected.getSensorDistance().toM(), actual.getSensorDistance().toM(), delta);
}
Assert.assertEquals("has heartrate", expected.hasHeartRate(), actual.hasHeartRate()); Assert.assertEquals("power", expected.power(), actual.power());
if (expected.hasHeartRate()) {
Assert.assertEquals("heartrate", expected.getHeartRate(), actual.getHeartRate());
}
Assert.assertEquals("has power", expected.hasPower(), actual.hasPower()); Assert.assertEquals("cadence", expected.cadence(), actual.cadence());
if (expected.hasPower()) {
Assert.assertEquals("power", expected.getPower(), actual.getPower());
}
Assert.assertEquals("has cadence", expected.hasCadence(), actual.hasCadence());
if (expected.hasCadence()) {
Assert.assertEquals("cadence", expected.getCadence(), actual.getCadence());
}
} }
public void assertEquals(List<TrackPoint> expected, List<TrackPoint> actual) { public void assertEquals(List<TrackPoint> expected, List<TrackPoint> actual) {
@@ -35,6 +35,7 @@ import de.dennisguse.opentracks.TestUtil;
import de.dennisguse.opentracks.content.data.TestDataUtil; import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.data.ContentProviderUtils; import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.AltitudeGainLoss; import de.dennisguse.opentracks.data.models.AltitudeGainLoss;
import de.dennisguse.opentracks.data.models.Cadence;
import de.dennisguse.opentracks.data.models.Distance; import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate; import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Position; import de.dennisguse.opentracks.data.models.Position;
@@ -126,8 +127,15 @@ public class TrackRecordingServiceRecordingTest {
new TrackPointAssert().assertEquals(List.of( new TrackPointAssert().assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)), new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse(stopTime)) new TrackPoint(
.setAltitudeGainLoss(0, 0) null,
TrackPoint.Type.SEGMENT_END_MANUAL,
Position.of(Instant.parse(stopTime)),
null,
null,
null,
null,
new AltitudeGainLoss(0, 0))
), TestDataUtil.getTrackPoints(contentProviderUtils, trackId)); ), TestDataUtil.getTrackPoints(contentProviderUtils, trackId));
} }
@@ -201,8 +209,15 @@ public class TrackRecordingServiceRecordingTest {
new TrackPointAssert().assertEquals(List.of( new TrackPointAssert().assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)), new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse(pauseTime)) new TrackPoint(
.setAltitudeGainLoss(0, 0) null,
TrackPoint.Type.SEGMENT_END_MANUAL,
Position.of(Instant.parse(pauseTime)),
null,
null,
null,
null,
new AltitudeGainLoss(0, 0))
), TestDataUtil.getTrackPoints(contentProviderUtils, trackId)); ), TestDataUtil.getTrackPoints(contentProviderUtils, trackId));
//when //when
@@ -216,8 +231,16 @@ public class TrackRecordingServiceRecordingTest {
new TrackPointAssert().assertEquals(List.of( new TrackPointAssert().assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)), new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse(pauseTime)) new TrackPoint(
.setAltitudeGainLoss(0, 0), null,
TrackPoint.Type.SEGMENT_END_MANUAL,
Position.of(Instant.parse(pauseTime)),
null,
null,
null,
null,
new AltitudeGainLoss(0, 0)
),
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(resumeTime)) new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(resumeTime))
), TestDataUtil.getTrackPoints(contentProviderUtils, trackId)); ), TestDataUtil.getTrackPoints(contentProviderUtils, trackId));
} }
@@ -249,8 +272,15 @@ public class TrackRecordingServiceRecordingTest {
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId); List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
new TrackPointAssert().assertEquals(List.of( new TrackPointAssert().assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(starTime)), new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(starTime)),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse(pauseTime)) new TrackPoint(
.setAltitudeGainLoss(0, 0) null,
TrackPoint.Type.SEGMENT_END_MANUAL,
Position.of(Instant.parse(pauseTime)),
null,
null,
null,
null,
new AltitudeGainLoss(0, 0))
), trackPoints); ), trackPoints);
} }
@@ -278,8 +308,16 @@ public class TrackRecordingServiceRecordingTest {
// then // then
new TrackPointAssert().assertEquals(List.of( new TrackPointAssert().assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)), new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse(stopTime)) new TrackPoint(
.setAltitudeGainLoss(0, 0), null,
TrackPoint.Type.SEGMENT_END_MANUAL,
Position.of(Instant.parse(stopTime)),
null,
null,
null,
null,
new AltitudeGainLoss(0, 0)
),
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(resumeTime)) new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(resumeTime))
), TestDataUtil.getTrackPoints(contentProviderUtils, trackId)); ), TestDataUtil.getTrackPoints(contentProviderUtils, trackId));
} }
@@ -296,7 +334,7 @@ public class TrackRecordingServiceRecordingTest {
mockAltitudeChange(trackPointCreator, 0); mockAltitudeChange(trackPointCreator, 0);
SensorManager sensorManager = trackPointCreator.getSensorManager(); SensorManager sensorManager = trackPointCreator.getSensorManager();
sensorManager.sensorDataSet.add(new AggregatorHeartRate("", "")); sensorManager.sensorDataAggregator.add(new AggregatorHeartRate("", ""));
// when // when
String sensor1 = "2020-02-02T02:02:03Z"; String sensor1 = "2020-02-02T02:02:03Z";
trackPointCreator.setClock(sensor1); trackPointCreator.setClock(sensor1);
@@ -314,12 +352,26 @@ public class TrackRecordingServiceRecordingTest {
// then // then
new TrackPointAssert().assertEquals(List.of( new TrackPointAssert().assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)), new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(sensor3)) new TrackPoint(
.setAltitudeGainLoss(0, 0) null,
.setHeartRate(HeartRate.of(7)), TrackPoint.Type.TRACKPOINT,
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse(stopTime)) Position.of(Instant.parse(sensor3)),
.setAltitudeGainLoss(0, 0) null,
.setHeartRate(HeartRate.of(7)) HeartRate.of(7),
null,
null,
new AltitudeGainLoss(0, 0)
),
new TrackPoint(
null,
TrackPoint.Type.SEGMENT_END_MANUAL,
Position.of(Instant.parse(stopTime)),
null,
HeartRate.of(7),
null,
null,
new AltitudeGainLoss(0, 0)
)
), TestDataUtil.getTrackPoints(contentProviderUtils, trackId)); ), TestDataUtil.getTrackPoints(contentProviderUtils, trackId));
} }
@@ -369,32 +421,61 @@ public class TrackRecordingServiceRecordingTest {
new TrackPointAssert().assertEquals(List.of( new TrackPointAssert().assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)), new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, new TrackPoint(
null,
TrackPoint.Type.TRACKPOINT,
new Position( new Position(
Instant.parse(gps1), Instant.parse(gps1),
45d, 35d, Distance.of(1), 45d, 35d, Distance.of(1),
null, null, null, null,
null, null,
Speed.of(15))) Speed.of(15)),
.setAltitudeGainLoss(0, 0), null,
new TrackPoint(TrackPoint.Type.TRACKPOINT, null,
null,
null,
new AltitudeGainLoss(0, 0)
),
new TrackPoint(
null,
TrackPoint.Type.TRACKPOINT,
new Position( new Position(
Instant.parse(gps2), Instant.parse(gps2),
45.001, 35d, Distance.of(1), 45.001, 35d, Distance.of(1),
null, null, null, null,
null, null,
Speed.of(15))) Speed.of(15)),
.setAltitudeGainLoss(0, 0), null,
new TrackPoint(TrackPoint.Type.TRACKPOINT, null,
null,
null,
new AltitudeGainLoss(0, 0)
),
new TrackPoint(
null,
TrackPoint.Type.TRACKPOINT,
new Position( new Position(
Instant.parse(gps3), Instant.parse(gps3),
45.001, 35d, Distance.of(1), 45.001, 35d, Distance.of(1),
null, null, null, null,
null, null,
Speed.of(15))) Speed.of(15)),
.setAltitudeGainLoss(0, 0), null,
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse(stopTime)) null,
.setAltitudeGainLoss(0, 0) null,
null,
new AltitudeGainLoss(0, 0)
),
new TrackPoint(
null,
TrackPoint.Type.SEGMENT_END_MANUAL,
Position.of(Instant.parse(stopTime)),
null,
null,
null,
null,
new AltitudeGainLoss(0, 0)
)
), TestDataUtil.getTrackPoints(contentProviderUtils, trackId)); ), TestDataUtil.getTrackPoints(contentProviderUtils, trackId));
} }
@@ -441,30 +522,51 @@ public class TrackRecordingServiceRecordingTest {
new TrackPointAssert().assertEquals(List.of( new TrackPointAssert().assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)), new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, new TrackPoint(
null,
TrackPoint.Type.TRACKPOINT,
new Position( new Position(
Instant.parse(gps1), Instant.parse(gps1),
45d, 35d, Distance.of(1), 45d, 35d, Distance.of(1),
null, null, null, null,
null, null,
Speed.of(15))) Speed.of(15)),
.setAltitudeGainLoss(0, 0), null,
new TrackPoint(TrackPoint.Type.TRACKPOINT, null,
null,
null,
new AltitudeGainLoss(0, 0)
),
new TrackPoint(
null,
TrackPoint.Type.TRACKPOINT,
new Position( new Position(
Instant.parse(gps3), Instant.parse(gps3),
45.00002, 35d, Distance.of(1), 45.00002, 35d, Distance.of(1),
null, null, null, null,
null, null,
Speed.of(15))) Speed.of(15)),
.setAltitudeGainLoss(0, 0), null,
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, null,
null,
null,
new AltitudeGainLoss(0, 0)
),
new TrackPoint(
null,
TrackPoint.Type.SEGMENT_END_MANUAL,
new Position( new Position(
Instant.parse(stopTime), Instant.parse(stopTime),
45.00002, 35d, Distance.of(1), 45.00002, 35d, Distance.of(1),
null, null, null, null,
null, null,
Speed.of(15))) Speed.of(15)),
.setAltitudeGainLoss(0, 0) null,
null,
null,
null,
new AltitudeGainLoss(0, 0)
)
), TestDataUtil.getTrackPoints(contentProviderUtils, trackId)); ), TestDataUtil.getTrackPoints(contentProviderUtils, trackId));
} }
@@ -499,24 +601,44 @@ public class TrackRecordingServiceRecordingTest {
// then // then
new TrackPointAssert().assertEquals(List.of( new TrackPointAssert().assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)), new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, new TrackPoint(
null,
TrackPoint.Type.TRACKPOINT,
new Position( new Position(
Instant.parse(gps1), Instant.parse(gps1),
45d, 35d, Distance.of(1), 45d, 35d, Distance.of(1),
null, null, null, null,
null, null,
Speed.of(15))) Speed.of(15)),
.setAltitudeGainLoss(0, 0), null,
new TrackPoint(TrackPoint.Type.TRACKPOINT, null,
null,
null,
new AltitudeGainLoss(0, 0)
),
new TrackPoint(
null,
TrackPoint.Type.TRACKPOINT,
new Position( new Position(
Instant.parse(gps3), Instant.parse(gps3),
45d, 35d, Distance.of(1), 45d, 35d, Distance.of(1),
null, null, null, null,
null, null,
Speed.of(15))) Speed.of(15)),
.setAltitudeGainLoss(0, 0), null,
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse(stopTime)) null,
.setAltitudeGainLoss(0, 0) null,
null,
new AltitudeGainLoss(0, 0)),
new TrackPoint(
null,
TrackPoint.Type.SEGMENT_END_MANUAL,
Position.of(Instant.parse(stopTime)),
null,
null,
null,
null,
new AltitudeGainLoss(0, 0))
), TestDataUtil.getTrackPoints(contentProviderUtils, trackId)); ), TestDataUtil.getTrackPoints(contentProviderUtils, trackId));
} }
@@ -559,8 +681,15 @@ public class TrackRecordingServiceRecordingTest {
// then // then
new TrackPointAssert().assertEquals(List.of( new TrackPointAssert().assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)), new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse(stopTime)) new TrackPoint(
.setAltitudeGainLoss(0, 0) null,
TrackPoint.Type.SEGMENT_END_MANUAL,
Position.of(Instant.parse(stopTime)),
null,
null,
null,
null,
new AltitudeGainLoss(0, 0))
), TestDataUtil.getTrackPoints(contentProviderUtils, trackId)); ), TestDataUtil.getTrackPoints(contentProviderUtils, trackId));
} }
@@ -603,24 +732,45 @@ public class TrackRecordingServiceRecordingTest {
// then // then
new TrackPointAssert().assertEquals(List.of( new TrackPointAssert().assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)), new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, new TrackPoint(
null,
TrackPoint.Type.TRACKPOINT,
new Position( new Position(
Instant.parse(gps1), Instant.parse(gps1),
45d, 35d, Distance.of(1), 45d, 35d, Distance.of(1),
null, null, null, null,
null, null,
Speed.of(15))) Speed.of(15)),
.setAltitudeGainLoss(0, 0), null,
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, null,
null,
null,
new AltitudeGainLoss(0, 0)
),
new TrackPoint(
null,
TrackPoint.Type.SEGMENT_START_AUTOMATIC,
new Position( new Position(
Instant.parse(gps2), Instant.parse(gps2),
45.1, 35d, Distance.of(1), 45.1, 35d, Distance.of(1),
null, null, null, null,
null, null,
Speed.of(15))) Speed.of(15)),
.setAltitudeGainLoss(0, 0), null,
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse(stopTime)) null,
.setAltitudeGainLoss(0, 0) null,
null,
new AltitudeGainLoss(0, 0)
),
new TrackPoint(
null,
TrackPoint.Type.SEGMENT_END_MANUAL,
Position.of(Instant.parse(stopTime)),
null,
null,
null,
null,
new AltitudeGainLoss(0, 0))
), TestDataUtil.getTrackPoints(contentProviderUtils, trackId)); ), TestDataUtil.getTrackPoints(contentProviderUtils, trackId));
} }
@@ -638,18 +788,18 @@ public class TrackRecordingServiceRecordingTest {
Track.Id trackId = service.startNewTrack(); Track.Id trackId = service.startNewTrack();
SensorManager sensorManager = trackPointCreator.getSensorManager(); SensorManager sensorManager = trackPointCreator.getSensorManager();
sensorManager.sensorDataSet.add(new AggregatorRunning("", "")); sensorManager.sensorDataAggregator.add(new AggregatorRunning("", ""));
sensorManager.sensorDataSet.barometer = null; sensorManager.sensorDataAggregator.barometer = null;
// when // when
String sensor1 = "2020-02-02T02:02:03Z"; String sensor1 = "2020-02-02T02:02:03Z";
trackPointCreator.setClock(sensor1); trackPointCreator.setClock(sensor1);
sensorManager.onChanged(new Raw<>(sensor1, new BluetoothHandlerRunningSpeedAndCadence.Data(Speed.of(5), null, Distance.ZERO))); //Should be ignored sensorManager.onChanged(new Raw<>(sensor1, new BluetoothHandlerRunningSpeedAndCadence.Data(Speed.of(5), Cadence.of(1), Distance.ZERO))); //Should be ignored
// when // when
String sensor2 = "2020-02-02T02:02:04Z"; String sensor2 = "2020-02-02T02:02:04Z";
trackPointCreator.setClock(sensor2); trackPointCreator.setClock(sensor2);
sensorManager.onChanged(new Raw<>(sensor2, new BluetoothHandlerRunningSpeedAndCadence.Data(Speed.of(5), null, Distance.of(2)))); sensorManager.onChanged(new Raw<>(sensor2, new BluetoothHandlerRunningSpeedAndCadence.Data(Speed.of(5), Cadence.of(2), Distance.of(2))));
// when // when
String gps1 = "2020-02-02T02:02:05Z"; String gps1 = "2020-02-02T02:02:05Z";
@@ -658,12 +808,12 @@ public class TrackRecordingServiceRecordingTest {
// when // when
String sensor3 = "2020-02-02T02:02:06Z"; String sensor3 = "2020-02-02T02:02:06Z";
trackPointCreator.setClock(sensor3); trackPointCreator.setClock(sensor3);
sensorManager.onChanged(new Raw<>(sensor3, new BluetoothHandlerRunningSpeedAndCadence.Data(Speed.of(5), null, Distance.of(12)))); sensorManager.onChanged(new Raw<>(sensor3, new BluetoothHandlerRunningSpeedAndCadence.Data(Speed.of(5), Cadence.of(3), Distance.of(12))));
// when // when
String sensor4 = "2020-02-02T02:02:07Z"; String sensor4 = "2020-02-02T02:02:07Z";
trackPointCreator.setClock(sensor4); trackPointCreator.setClock(sensor4);
sensorManager.onChanged(new Raw<>(sensor4, new BluetoothHandlerRunningSpeedAndCadence.Data(Speed.of(5), null, Distance.of(14)))); //Should be ignored sensorManager.onChanged(new Raw<>(sensor4, new BluetoothHandlerRunningSpeedAndCadence.Data(Speed.of(5), Cadence.of(4), Distance.of(14)))); //Should be ignored
// when // when
String gps2 = "2020-02-02T02:02:08Z"; String gps2 = "2020-02-02T02:02:08Z";
@@ -672,7 +822,7 @@ public class TrackRecordingServiceRecordingTest {
// when // when
String sensor5 = "2020-02-02T02:02:10Z"; String sensor5 = "2020-02-02T02:02:10Z";
trackPointCreator.setClock(sensor5); trackPointCreator.setClock(sensor5);
sensorManager.onChanged(new Raw<>(sensor5, new BluetoothHandlerRunningSpeedAndCadence.Data(Speed.of(5), null, Distance.of(16)))); //Should be ignored sensorManager.onChanged(new Raw<>(sensor5, new BluetoothHandlerRunningSpeedAndCadence.Data(Speed.of(5), Cadence.of(5), Distance.of(16)))); //Should be ignored
// when // when
String gps3 = "2020-02-02T02:02:12Z"; String gps3 = "2020-02-02T02:02:12Z";
@@ -691,49 +841,101 @@ public class TrackRecordingServiceRecordingTest {
// then // then
new TrackPointAssert().assertEquals(List.of( new TrackPointAssert().assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)), new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(sensor2)) //First moving TrackPoint: store as the time might be interesting. new TrackPoint( //First moving TrackPoint: stored as the time might be interesting.
.setSpeed(Speed.of(5)) null,
.setSensorDistance(Distance.of(2)), TrackPoint.Type.TRACKPOINT,
new TrackPoint(TrackPoint.Type.TRACKPOINT, new Position(
Instant.parse(sensor2),
null, null, null,
null, null,
null,
Speed.of(5)),
Distance.of(2),
null,
Cadence.of(2),
null,
null
),
new TrackPoint(
null,
TrackPoint.Type.TRACKPOINT,
new Position( new Position(
Instant.parse(gps1), Instant.parse(gps1),
45d, 35d, Distance.of(1), 45d, 35d, Distance.of(1),
null, null, null, null,
null, null,
Speed.of(5))) Speed.of(5)),
.setSensorDistance(Distance.ZERO), Distance.ZERO,
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(sensor3)) null,
.setSpeed(Speed.of(5)) Cadence.of(2),
.setSensorDistance(Distance.of(10)), null,
new TrackPoint(TrackPoint.Type.TRACKPOINT, null
),
new TrackPoint(
null,
TrackPoint.Type.TRACKPOINT,
new Position(
Instant.parse(sensor3),
null, null, null,
null, null,
null,
Speed.of(5)),
Distance.of(10),
null,
Cadence.of(3),
null,
null
),
new TrackPoint(
null,
TrackPoint.Type.TRACKPOINT,
new Position( new Position(
Instant.parse(gps3), Instant.parse(gps3),
45.001, 35d, Distance.of(1), 45.001, 35d, Distance.of(1),
null, null, null, null,
null, null,
Speed.of(5))) Speed.of(5)),
.setSensorDistance(Distance.of(4.0)), Distance.of(4.0),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null,
Cadence.of(5),
null,
null
),
new TrackPoint(
null,
TrackPoint.Type.TRACKPOINT,
new Position( new Position(
Instant.parse(gps4), Instant.parse(gps4),
45.001, 35d, Distance.of(1), 45.001, 35d, Distance.of(1),
null, null, null, null,
null, null,
Speed.of(5))) Speed.of(5)),
.setSensorDistance(Distance.ZERO), Distance.ZERO,
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, null,
Cadence.of(5),
null,
null
),
new TrackPoint(
null,
TrackPoint.Type.SEGMENT_END_MANUAL,
new Position( new Position(
Instant.parse(stopTime), Instant.parse(stopTime),
45.001, 35d, Distance.of(1), 45.001, 35d, Distance.of(1),
null, null, null, null,
null, null,
Speed.ZERO)) //Sensor data is now outdated, but we do not fall back to GPS. Speed.ZERO), //Sensor data is now outdated, but we do not fall back to GPS.
.setSensorDistance(Distance.ZERO) Distance.ZERO,
null,
Cadence.of(0), //TODO This could be null, right?
null,
null
)
), TestDataUtil.getTrackPoints(contentProviderUtils, trackId)); ), TestDataUtil.getTrackPoints(contentProviderUtils, trackId));
} }
private void mockAltitudeChange(TrackPointCreator trackPointCreator, float altitudeGain) { private void mockAltitudeChange(TrackPointCreator trackPointCreator, float altitudeGain) {
trackPointCreator.getSensorManager().sensorDataSet.barometer = new AggregatorBarometer("", "") { trackPointCreator.getSensorManager().sensorDataAggregator.barometer = new AggregatorBarometer("", "") {
@Override @Override
public boolean hasReceivedData() { public boolean hasReceivedData() {
return true; return true;
@@ -45,6 +45,7 @@ import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import de.dennisguse.opentracks.TestUtil;
import de.dennisguse.opentracks.data.ContentProviderUtils; import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.sensors.GpsStatusValue; import de.dennisguse.opentracks.sensors.GpsStatusValue;
@@ -60,7 +61,7 @@ public class TrackRecordingServiceStateMachineTest {
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS); public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
@Rule @Rule
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION); public GrantPermissionRule mRuntimePermissionRule = TestUtil.createGrantPermissionRule();
private final Context context = ApplicationProvider.getApplicationContext(); private final Context context = ApplicationProvider.getApplicationContext();
private ContentProviderUtils contentProviderUtils; private ContentProviderUtils contentProviderUtils;
@@ -15,7 +15,6 @@ import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mockito;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
@@ -34,10 +33,11 @@ import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Statistics; import de.dennisguse.opentracks.data.models.Statistics;
import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint; import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.data.statistics.SensorStatistics;
import de.dennisguse.opentracks.sensors.sensorData.SensorData;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet; import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
import de.dennisguse.opentracks.settings.PreferencesUtils; import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.settings.UnitSystem; import de.dennisguse.opentracks.settings.UnitSystem;
import de.dennisguse.opentracks.data.statistics.SensorStatistics;
import de.dennisguse.opentracks.ui.intervals.IntervalStatisticsUpdater; import de.dennisguse.opentracks.ui.intervals.IntervalStatisticsUpdater;
//Due to DateTimeFormatter using NBSP, these tests only work in SDK34+ //Due to DateTimeFormatter using NBSP, these tests only work in SDK34+
@@ -84,8 +84,15 @@ public class VoiceAnnouncementUtilsTest {
null null
)); ));
SensorDataSet dataSet = Mockito.mock(SensorDataSet.class); SensorDataSet dataSet = new SensorDataSet(
Mockito.when(dataSet.getHeartRate()).thenReturn(new Pair<>(HeartRate.of(60), "unused")); null,
null,
null,
new SensorData<>(HeartRate.of(60), "unused"),
null,
null,
null
);
// when // when
String announcement = VoiceAnnouncementUtils.createStatistics(context, track, dataSet, UnitSystem.METRIC, true, null, null).toString(); String announcement = VoiceAnnouncementUtils.createStatistics(context, track, dataSet, UnitSystem.METRIC, true, null, null).toString();
@@ -117,8 +124,15 @@ public class VoiceAnnouncementUtilsTest {
null null
)); ));
SensorDataSet dataSet = Mockito.mock(SensorDataSet.class); SensorDataSet dataSet = new SensorDataSet(
Mockito.when(dataSet.getHeartRate()).thenReturn(new Pair<>(HeartRate.of(60), "unused")); null,
null,
null,
new SensorData<>(HeartRate.of(60), "unused"),
null,
null,
null
);
// when // when
String announcement = VoiceAnnouncementUtils.createStatistics(context, track, dataSet, UnitSystem.METRIC, true, null, null).toString(); String announcement = VoiceAnnouncementUtils.createStatistics(context, track, dataSet, UnitSystem.METRIC, true, null, null).toString();
@@ -150,8 +164,15 @@ public class VoiceAnnouncementUtilsTest {
null null
)); ));
SensorDataSet dataSet = Mockito.mock(SensorDataSet.class); SensorDataSet dataSet = new SensorDataSet(
Mockito.when(dataSet.getHeartRate()).thenReturn(new Pair<>(HeartRate.of(60), "unused")); null,
null,
null,
new SensorData<>(HeartRate.of(60), "unused"),
null,
null,
null
);
// when // when
String announcement = VoiceAnnouncementUtils.createStatistics(context, track, dataSet, UnitSystem.METRIC, true, null, null).toString(); String announcement = VoiceAnnouncementUtils.createStatistics(context, track, dataSet, UnitSystem.METRIC, true, null, null).toString();
@@ -183,8 +204,15 @@ public class VoiceAnnouncementUtilsTest {
null null
)); ));
SensorDataSet dataSet = Mockito.mock(SensorDataSet.class); SensorDataSet dataSet = new SensorDataSet(
Mockito.when(dataSet.getHeartRate()).thenReturn(new Pair<>(HeartRate.of(60), "unused")); null,
null,
null,
new SensorData<>(HeartRate.of(60), "unused"),
null,
null,
null
);
// when // when
String announcement = VoiceAnnouncementUtils.createStatistics(context, track, dataSet, UnitSystem.METRIC, true, null, null).toString(); String announcement = VoiceAnnouncementUtils.createStatistics(context, track, dataSet, UnitSystem.METRIC, true, null, null).toString();
@@ -198,8 +226,15 @@ public class VoiceAnnouncementUtilsTest {
// given // given
Pair<Track, Statistics> data = buildTrackWithTrackPoints(); Pair<Track, Statistics> data = buildTrackWithTrackPoints();
SensorDataSet dataSet = Mockito.mock(SensorDataSet.class); SensorDataSet dataSet = new SensorDataSet(
Mockito.when(dataSet.getHeartRate()).thenReturn(new Pair<>(HeartRate.of(60), "unused")); null,
null,
null,
new SensorData<>(HeartRate.of(60), "unused"),
null,
null,
null
);
// when // when
String announcement = VoiceAnnouncementUtils.createStatistics(context, data.first, dataSet, UnitSystem.METRIC, true, data.second, null).toString(); String announcement = VoiceAnnouncementUtils.createStatistics(context, data.first, dataSet, UnitSystem.METRIC, true, data.second, null).toString();
@@ -231,8 +266,15 @@ public class VoiceAnnouncementUtilsTest {
null null
)); ));
SensorDataSet dataSet = Mockito.mock(SensorDataSet.class); SensorDataSet dataSet = new SensorDataSet(
Mockito.when(dataSet.getHeartRate()).thenReturn(new Pair<>(HeartRate.of(60), "unused")); null,
null,
null,
new SensorData<>(HeartRate.of(60), "unused"),
null,
null,
null
);
// when // when
String announcement = VoiceAnnouncementUtils.createStatistics(context, track, dataSet, UnitSystem.METRIC, false, null, null).toString(); String announcement = VoiceAnnouncementUtils.createStatistics(context, track, dataSet, UnitSystem.METRIC, false, null, null).toString();
@@ -246,8 +288,15 @@ public class VoiceAnnouncementUtilsTest {
// given // given
Pair<Track, Statistics> data = buildTrackWithTrackPoints(); Pair<Track, Statistics> data = buildTrackWithTrackPoints();
SensorDataSet dataSet = Mockito.mock(SensorDataSet.class); SensorDataSet dataSet = new SensorDataSet(
Mockito.when(dataSet.getHeartRate()).thenReturn(new Pair<>(HeartRate.of(60), "unused")); null,
null,
null,
new SensorData<>(HeartRate.of(60), "unused"),
null,
null,
null
);
// when // when
String announcement = VoiceAnnouncementUtils.createStatistics(context, data.first, dataSet, UnitSystem.METRIC, false, data.second, null).toString(); String announcement = VoiceAnnouncementUtils.createStatistics(context, data.first, dataSet, UnitSystem.METRIC, false, data.second, null).toString();
@@ -279,8 +328,15 @@ public class VoiceAnnouncementUtilsTest {
null null
)); ));
SensorDataSet dataSet = Mockito.mock(SensorDataSet.class); SensorDataSet dataSet = new SensorDataSet(
Mockito.when(dataSet.getHeartRate()).thenReturn(new Pair<>(HeartRate.of(60), "unused")); null,
null,
null,
new SensorData<>(HeartRate.of(60), "unused"),
null,
null,
null
);
// when // when
String announcement = VoiceAnnouncementUtils.createStatistics(context, track, dataSet, UnitSystem.IMPERIAL_FEET, true, null, null).toString(); String announcement = VoiceAnnouncementUtils.createStatistics(context, track, dataSet, UnitSystem.IMPERIAL_FEET, true, null, null).toString();
@@ -312,8 +368,15 @@ public class VoiceAnnouncementUtilsTest {
null null
)); ));
SensorDataSet dataSet = Mockito.mock(SensorDataSet.class); SensorDataSet dataSet = new SensorDataSet(
Mockito.when(dataSet.getHeartRate()).thenReturn(new Pair<>(HeartRate.of(60), "unused")); null,
null,
null,
new SensorData<>(HeartRate.of(60), "unused"),
null,
null,
null
);
// when // when
String announcement = VoiceAnnouncementUtils.createStatistics(context, track, dataSet, UnitSystem.IMPERIAL_FEET, true, null, null).toString(); String announcement = VoiceAnnouncementUtils.createStatistics(context, track, dataSet, UnitSystem.IMPERIAL_FEET, true, null, null).toString();
@@ -345,8 +408,15 @@ public class VoiceAnnouncementUtilsTest {
null null
)); ));
SensorDataSet dataSet = Mockito.mock(SensorDataSet.class); SensorDataSet dataSet = new SensorDataSet(
Mockito.when(dataSet.getHeartRate()).thenReturn(new Pair<>(HeartRate.of(60), "unused")); null,
null,
null,
new SensorData<>(HeartRate.of(60), "unused"),
null,
null,
null
);
// when // when
String announcement = VoiceAnnouncementUtils.createStatistics(context, track, dataSet, UnitSystem.IMPERIAL_METER, true, null, null).toString(); String announcement = VoiceAnnouncementUtils.createStatistics(context, track, dataSet, UnitSystem.IMPERIAL_METER, true, null, null).toString();
@@ -378,8 +448,15 @@ public class VoiceAnnouncementUtilsTest {
null null
)); ));
SensorDataSet dataSet = Mockito.mock(SensorDataSet.class); SensorDataSet dataSet = new SensorDataSet(
Mockito.when(dataSet.getHeartRate()).thenReturn(new Pair<>(HeartRate.of(60), "unused")); null,
null,
null,
new SensorData<>(HeartRate.of(60), "unused"),
null,
null,
null
);
// when // when
String announcement = VoiceAnnouncementUtils.createStatistics(context, track, dataSet, UnitSystem.METRIC, true, null, null).toString(); String announcement = VoiceAnnouncementUtils.createStatistics(context, track, dataSet, UnitSystem.METRIC, true, null, null).toString();
@@ -393,8 +470,15 @@ public class VoiceAnnouncementUtilsTest {
// given // given
Pair<Track, Statistics> data = buildTrackWithTrackPoints(); Pair<Track, Statistics> data = buildTrackWithTrackPoints();
SensorDataSet dataSet = Mockito.mock(SensorDataSet.class); SensorDataSet dataSet = new SensorDataSet(
Mockito.when(dataSet.getHeartRate()).thenReturn(new Pair<>(HeartRate.of(60), "unused")); null,
null,
null,
new SensorData<>(HeartRate.of(60), "unused"),
null,
null,
null
);
// when // when
String announcement = VoiceAnnouncementUtils.createStatistics(context, data.first, dataSet, UnitSystem.IMPERIAL_FEET, true, data.second, null).toString(); String announcement = VoiceAnnouncementUtils.createStatistics(context, data.first, dataSet, UnitSystem.IMPERIAL_FEET, true, data.second, null).toString();
@@ -426,8 +510,15 @@ public class VoiceAnnouncementUtilsTest {
null null
)); ));
SensorDataSet dataSet = Mockito.mock(SensorDataSet.class); SensorDataSet dataSet = new SensorDataSet(
Mockito.when(dataSet.getHeartRate()).thenReturn(new Pair<>(HeartRate.of(60), "unused")); null,
null,
null,
new SensorData<>(HeartRate.of(60), "unused"),
null,
null,
null
);
// when // when
String announcement = VoiceAnnouncementUtils.createStatistics(context, track, dataSet, UnitSystem.IMPERIAL_FEET, false, null, null).toString(); String announcement = VoiceAnnouncementUtils.createStatistics(context, track, dataSet, UnitSystem.IMPERIAL_FEET, false, null, null).toString();
@@ -441,8 +532,15 @@ public class VoiceAnnouncementUtilsTest {
// given // given
Pair<Track, Statistics> data = buildTrackWithTrackPoints(); Pair<Track, Statistics> data = buildTrackWithTrackPoints();
SensorDataSet dataSet = Mockito.mock(SensorDataSet.class); SensorDataSet dataSet = new SensorDataSet(
Mockito.when(dataSet.getHeartRate()).thenReturn(new Pair<>(HeartRate.of(60), "unused")); null,
null,
null,
new SensorData<>(HeartRate.of(60), "unused"),
null,
null,
null
);
// when // when
String announcement = VoiceAnnouncementUtils.createStatistics(context, data.first, dataSet, UnitSystem.IMPERIAL_FEET, false, data.second, null).toString(); String announcement = VoiceAnnouncementUtils.createStatistics(context, data.first, dataSet, UnitSystem.IMPERIAL_FEET, false, data.second, null).toString();
@@ -461,8 +559,15 @@ public class VoiceAnnouncementUtilsTest {
SensorStatistics sensorStatistics = new SensorStatistics(HeartRate.of(180f), HeartRate.of(180f), null, null, null, null); SensorStatistics sensorStatistics = new SensorStatistics(HeartRate.of(180f), HeartRate.of(180f), null, null, null, null);
SensorDataSet dataSet = Mockito.mock(SensorDataSet.class); SensorDataSet dataSet = new SensorDataSet(
Mockito.when(dataSet.getHeartRate()).thenReturn(new Pair<>(HeartRate.of(60), "unused")); null,
null,
null,
new SensorData<>(HeartRate.of(60), "unused"),
null,
null,
null
);
// when // when
String announcement = VoiceAnnouncementUtils.createStatistics(context, data.first, dataSet, UnitSystem.METRIC, true, data.second, sensorStatistics).toString(); String announcement = VoiceAnnouncementUtils.createStatistics(context, data.first, dataSet, UnitSystem.METRIC, true, data.second, sensorStatistics).toString();
@@ -487,8 +592,15 @@ public class VoiceAnnouncementUtilsTest {
SensorStatistics sensorStatistics = new SensorStatistics(HeartRate.of(180f), HeartRate.of(180f), null, null, null, null); SensorStatistics sensorStatistics = new SensorStatistics(HeartRate.of(180f), HeartRate.of(180f), null, null, null, null);
SensorDataSet dataSet = Mockito.mock(SensorDataSet.class); SensorDataSet dataSet = new SensorDataSet(
Mockito.when(dataSet.getHeartRate()).thenReturn(new Pair<>(HeartRate.of(60), "unused")); null,
null,
null,
new SensorData<>(HeartRate.of(60), "unused"),
null,
null,
null
);
// when // when
String announcement = VoiceAnnouncementUtils.createStatistics(context, data.first, dataSet, UnitSystem.METRIC, true, data.second, sensorStatistics).toString(); String announcement = VoiceAnnouncementUtils.createStatistics(context, data.first, dataSet, UnitSystem.METRIC, true, data.second, sensorStatistics).toString();
@@ -89,8 +89,7 @@ public class TrackStatisticsUpdaterTest {
// given // given
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000)); TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
TrackPoint tp2 = createTrackPoint(0, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(2000)); TrackPoint tp2 = createTrackPoint(0, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(2000));
TrackPoint tp3 = createTrackPoint(0.00001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(3000)); TrackPoint tp3 = createTrackPoint(0.00001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(3000), Speed.of(5f), null);
tp3.setSpeed(Speed.of(5f));
// when // when
TrackStatisticsUpdater subject = new TrackStatisticsUpdater(List.of(tp1, tp2, tp3)); TrackStatisticsUpdater subject = new TrackStatisticsUpdater(List.of(tp1, tp2, tp3));
@@ -103,15 +102,17 @@ public class TrackStatisticsUpdaterTest {
public void addTrackPoint_distance_from_GPS_moving_and_sensor_moving() { public void addTrackPoint_distance_from_GPS_moving_and_sensor_moving() {
// given // given
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000)); TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
TrackPoint tp2 = createTrackPoint(0, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(2000)); TrackPoint tp2 = createTrackPoint(0, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(2000), Speed.of(5f), null);
tp2.setSpeed(Speed.of(5f)); TrackPoint tp3 = createTrackPoint(0.001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(3000), Speed.of(5f), null);
TrackPoint tp3 = createTrackPoint(0.001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(3000)); TrackPoint tp4 = createTrackPoint(0.001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(4000), Speed.of(5f), Distance.of(5));
tp3.setSpeed(Speed.of(5f)); TrackPoint tp5 = new TrackPoint(null,
TrackPoint tp4 = createTrackPoint(0.001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(4000)); TrackPoint.Type.SEGMENT_END_MANUAL,
tp4.setSpeed(Speed.of(5f)); Position.of(Instant.ofEpochMilli(5000)),
tp4.setSensorDistance(Distance.of(5f)); Distance.of(10f),
TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000)); null,
tp5.setSensorDistance(Distance.of(10f)); null,
null,
null);
// when // when
TrackStatisticsUpdater subject = new TrackStatisticsUpdater(List.of(tp1, tp2, tp3)); TrackStatisticsUpdater subject = new TrackStatisticsUpdater(List.of(tp1, tp2, tp3));
@@ -131,13 +132,10 @@ public class TrackStatisticsUpdaterTest {
public void addTrackPoint_distance_from_GPS_moving_and_sensor_disconnecting() { public void addTrackPoint_distance_from_GPS_moving_and_sensor_disconnecting() {
// given // given
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000)); TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
TrackPoint tp2 = createTrackPoint(0, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(2000)); TrackPoint tp2 = createTrackPoint(0, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(2000), Speed.of(5f), null);
tp2.setSpeed(Speed.of(5f));
TrackPoint tp3 = createTrackPoint(0.00001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(3000)); TrackPoint tp3 = createTrackPoint(0.00001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(3000), Speed.of(5f), Distance.of(5));
tp3.setSensorDistance(Distance.of(5f)); TrackPoint tp4 = createTrackPoint(0.0005, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(4000), Speed.of(5f), null);
tp3.setSpeed(Speed.of(5f));
TrackPoint tp4 = createTrackPoint(0.0005, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(4000));
tp4.setSpeed(Speed.of(5f));
TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000)); TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000));
// when // when
@@ -158,10 +156,8 @@ public class TrackStatisticsUpdaterTest {
public void addTrackPoint_maxSpeed_multiple_segments() { public void addTrackPoint_maxSpeed_multiple_segments() {
TrackStatisticsUpdater subject = new TrackStatisticsUpdater(List.of( TrackStatisticsUpdater subject = new TrackStatisticsUpdater(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochSecond(0)), new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochSecond(0)),
createTrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(1)) createTrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(1), Speed.of(2), null),
.setSpeed(Speed.of(2f)), createTrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(2), Speed.of(2), null),
createTrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(2))
.setSpeed(Speed.of(2f)),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochSecond(4)) new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochSecond(4))
)); ));
assertEquals(Speed.of(2), subject.getTrackStatistics().maxSpeed()); assertEquals(Speed.of(2), subject.getTrackStatistics().maxSpeed());
@@ -169,10 +165,8 @@ public class TrackStatisticsUpdaterTest {
// when // when
List.of( List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochSecond(5)), new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochSecond(5)),
createTrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(6)) createTrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(6), Speed.of(1), null),
.setSpeed(Speed.of(1f)), createTrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(7), Speed.of(1), null),
createTrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(7))
.setSpeed(Speed.of(1f)),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochSecond(8)) new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochSecond(8))
).forEach(subject::addTrackPoint); ).forEach(subject::addTrackPoint);
@@ -185,16 +179,30 @@ public class TrackStatisticsUpdaterTest {
// when // when
TrackStatisticsUpdater subject = new TrackStatisticsUpdater(List.of( TrackStatisticsUpdater subject = new TrackStatisticsUpdater(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochSecond(0)), new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochSecond(0)),
createTrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(1)) createTrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(1), Speed.of(2), null),
.setSpeed(Speed.of(2f)), createTrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(2), Speed.of(2), null),
createTrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(2))
.setSpeed(Speed.of(2f)),
new TrackPoint(TrackPoint.Type.IDLE, Instant.ofEpochSecond(30)), new TrackPoint(TrackPoint.Type.IDLE, Instant.ofEpochSecond(30)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochSecond(40)) new TrackPoint(
.setHeartRate(50), null,
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochSecond(45)) TrackPoint.Type.TRACKPOINT,
.setHeartRate(50), Position.of(Instant.ofEpochSecond(40)),
null,
HeartRate.of(50),
null,
null,
null
),
new TrackPoint(
null,
TrackPoint.Type.TRACKPOINT,
Position.of(Instant.ofEpochSecond(45)),
null,
HeartRate.of(50),
null,
null,
null
),
createTrackPoint(0, 1, Altitude.WGS84.of(0), Instant.ofEpochSecond(50)), createTrackPoint(0, 1, Altitude.WGS84.of(0), Instant.ofEpochSecond(50)),
createTrackPoint(0, 2, Altitude.WGS84.of(0), Instant.ofEpochSecond(55)), createTrackPoint(0, 2, Altitude.WGS84.of(0), Instant.ofEpochSecond(55)),
@@ -210,21 +218,40 @@ public class TrackStatisticsUpdaterTest {
// when // when
TrackStatisticsUpdater subject = new TrackStatisticsUpdater(List.of( TrackStatisticsUpdater subject = new TrackStatisticsUpdater(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochSecond(0)), new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochSecond(0)),
createTrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(1)) createTrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(1), null, Distance.of(10)),
.setSensorDistance(Distance.of(10)), createTrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(2), null, Distance.of(10)),
createTrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(2)) new TrackPoint(
.setSensorDistance(Distance.of(10)), null,
TrackPoint.Type.IDLE,
new TrackPoint(TrackPoint.Type.IDLE, Instant.ofEpochSecond(30)) Position.of(Instant.ofEpochSecond(30)),
.setSensorDistance(Distance.ofKilometer(1)), Distance.ofKilometer(1),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochSecond(40)) null,
.setHeartRate(50), null,
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochSecond(45)) null,
.setHeartRate(50), null
createTrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(50)) ),
.setSensorDistance(Distance.of(10)), new TrackPoint(
createTrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(55)) null,
.setSensorDistance(Distance.of(10)), TrackPoint.Type.TRACKPOINT,
Position.of(Instant.ofEpochSecond(40)),
null,
HeartRate.of(50),
null,
null,
null
),
new TrackPoint(
null,
TrackPoint.Type.TRACKPOINT,
Position.of(Instant.ofEpochSecond(45)),
null,
HeartRate.of(50),
null,
null,
null
),
createTrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(50), null, Distance.of(10)),
createTrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(55), null, Distance.of(10)),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochSecond(60)) new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochSecond(60))
)); ));
@@ -240,8 +267,8 @@ public class TrackStatisticsUpdaterTest {
// when // when
TrackStatisticsUpdater subject = new TrackStatisticsUpdater(new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochSecond(0))); TrackStatisticsUpdater subject = new TrackStatisticsUpdater(new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochSecond(0)));
subject.addTrackPoint( subject.addTrackPoint(
createTrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(10)) createTrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(10), null, Distance.of(10))
.setSensorDistance(Distance.of(10))); );
subject.addTrackPoint(new TrackPoint(TrackPoint.Type.IDLE, Instant.ofEpochSecond(30))); subject.addTrackPoint(new TrackPoint(TrackPoint.Type.IDLE, Instant.ofEpochSecond(30)));
// then // then
@@ -252,8 +279,17 @@ public class TrackStatisticsUpdaterTest {
// when // when
subject.addTrackPoint( subject.addTrackPoint(
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochSecond(40)) new TrackPoint(
.setSensorDistance(Distance.ZERO)); null,
TrackPoint.Type.TRACKPOINT,
Position.of(Instant.ofEpochSecond(40)),
Distance.ZERO,
null,
null,
null,
null
)
);
// then // then
assertTrue(subject.isIdle()); assertTrue(subject.isIdle());
assertEquals(Duration.ofSeconds(30), subject.getTrackStatistics().movingDuration()); assertEquals(Duration.ofSeconds(30), subject.getTrackStatistics().movingDuration());
@@ -262,8 +298,17 @@ public class TrackStatisticsUpdaterTest {
// when // when
subject.addTrackPoint( subject.addTrackPoint(
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochSecond(45)) new TrackPoint(
.setSensorDistance(Distance.of(1))); null,
TrackPoint.Type.TRACKPOINT,
Position.of(Instant.ofEpochSecond(45)),
Distance.of(1),
null,
null,
null,
null
)
);
// then // then
assertTrue(subject.isIdle()); assertTrue(subject.isIdle());
assertEquals(Duration.ofSeconds(30), subject.getTrackStatistics().movingDuration()); assertEquals(Duration.ofSeconds(30), subject.getTrackStatistics().movingDuration());
@@ -272,8 +317,17 @@ public class TrackStatisticsUpdaterTest {
// when // when
subject.addTrackPoint( subject.addTrackPoint(
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochSecond(50)) new TrackPoint(
.setSensorDistance(Distance.of(10))); null,
TrackPoint.Type.TRACKPOINT,
Position.of(Instant.ofEpochSecond(50)),
Distance.of(10),
null,
null,
null,
null
)
);
// then // then
assertFalse(subject.isIdle()); assertFalse(subject.isIdle());
assertEquals(Duration.ofSeconds(30), subject.getTrackStatistics().movingDuration()); assertEquals(Duration.ofSeconds(30), subject.getTrackStatistics().movingDuration());
@@ -294,15 +348,12 @@ public class TrackStatisticsUpdaterTest {
public void copy_constructor() { public void copy_constructor() {
// given // given
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000)); TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
TrackPoint tp2 = createTrackPoint(0, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(2000)); TrackPoint tp2 = createTrackPoint(0, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(2000), Speed.of(5f), null);
tp2.setSpeed(Speed.of(5f)); TrackPoint tp3 = createTrackPoint(0.00001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(3000), Speed.of(5f), null);
TrackPoint tp3 = createTrackPoint(0.00001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(3000)); TrackPoint tp4 = createTrackPoint(0.0005, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(4000), Speed.of(5f), null);
tp3.setSpeed(Speed.of(5f));
TrackPoint tp4 = createTrackPoint(0.0005, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(4000));
tp4.setSpeed(Speed.of(5f));
TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000)); TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000));
TrackStatisticsUpdater initial = new TrackStatisticsUpdater(List.of(tp1, tp2,tp3, tp4)); TrackStatisticsUpdater initial = new TrackStatisticsUpdater(List.of(tp1, tp2, tp3, tp4));
// when // when
TrackStatisticsUpdater subject = new TrackStatisticsUpdater(initial, tp5); TrackStatisticsUpdater subject = new TrackStatisticsUpdater(initial, tp5);
@@ -335,7 +386,13 @@ public class TrackStatisticsUpdaterTest {
} }
public TrackPoint createTrackPoint(double latitude, double longitude, Altitude altitude, Instant time) { public TrackPoint createTrackPoint(double latitude, double longitude, Altitude altitude, Instant time) {
return new TrackPoint(TrackPoint.Type.TRACKPOINT, return createTrackPoint(latitude, longitude, altitude, time, null, null);
}
public TrackPoint createTrackPoint(double latitude, double longitude, Altitude altitude, Instant time, Speed speed, Distance sensorDistance) {
return new TrackPoint(
null,
TrackPoint.Type.TRACKPOINT,
new Position( new Position(
time, time,
latitude, latitude,
@@ -344,7 +401,13 @@ public class TrackStatisticsUpdaterTest {
altitude, altitude,
null, null,
null, null,
null speed
)); ),
sensorDistance,
null,
null,
null,
null
);
} }
} }
@@ -30,6 +30,7 @@ import org.junit.runner.RunWith;
import java.util.List; import java.util.List;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TestUtil;
import de.dennisguse.opentracks.TrackListActivity; import de.dennisguse.opentracks.TrackListActivity;
import de.dennisguse.opentracks.content.data.TestDataUtil; import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.data.ContentProviderUtils; import de.dennisguse.opentracks.data.ContentProviderUtils;
@@ -47,7 +48,7 @@ public class EspressoAggregatedFilterTest {
public ActivityScenarioRule<TrackListActivity> mActivityTestRule = new ActivityScenarioRule<>(TrackListActivity.class); public ActivityScenarioRule<TrackListActivity> mActivityTestRule = new ActivityScenarioRule<>(TrackListActivity.class);
@Rule @Rule
public GrantPermissionRule mGrantPermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION); public GrantPermissionRule mGrantPermissionRule = TestUtil.createGrantPermissionRule();
@Before @Before
public void setUp() { public void setUp() {
@@ -104,8 +104,7 @@ public class IntervalStatisticsUpdaterTest {
ArrayList<TrackPoint> trackPoints = new ArrayList<>(); ArrayList<TrackPoint> trackPoints = new ArrayList<>();
for (int i = 0; i < numberOfPoints; i++) { for (int i = 0; i < numberOfPoints; i++) {
trackPoints.add(TestDataUtil.createTrackPoint(i) trackPoints.add(TestDataUtil.createTrackPoint(i, TrackPoint.Type.TRACKPOINT, null));
.setAltitudeGainLoss(null));
} }
TrackStatisticsUpdater trackStatisticsUpdater = new TrackStatisticsUpdater(trackPoints); TrackStatisticsUpdater trackStatisticsUpdater = new TrackStatisticsUpdater(trackPoints);
@@ -278,7 +278,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
Intent intent = IntentUtils Intent intent = IntentUtils
.newIntent(this, MarkerEditActivity.class) .newIntent(this, MarkerEditActivity.class)
.putExtra(MarkerEditActivity.EXTRA_TRACK_ID, trackId) .putExtra(MarkerEditActivity.EXTRA_TRACK_ID, trackId)
.putExtra(MarkerEditActivity.EXTRA_LOCATION, trackPoint.getPosition().toLocation()); .putExtra(MarkerEditActivity.EXTRA_LOCATION, trackPoint.position().toLocation());
startActivity(intent); startActivity(intent);
return true; return true;
} }
@@ -35,11 +35,11 @@ import de.dennisguse.opentracks.data.TrackDataHub;
import de.dennisguse.opentracks.data.models.Statistics; import de.dennisguse.opentracks.data.models.Statistics;
import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint; import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.data.statistics.SegmentStatisticUpdater;
import de.dennisguse.opentracks.data.statistics.TrackStatisticsUpdater;
import de.dennisguse.opentracks.databinding.ChartBinding; import de.dennisguse.opentracks.databinding.ChartBinding;
import de.dennisguse.opentracks.settings.PreferencesUtils; import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.settings.UnitSystem; import de.dennisguse.opentracks.settings.UnitSystem;
import de.dennisguse.opentracks.data.statistics.SegmentStatisticUpdater;
import de.dennisguse.opentracks.data.statistics.TrackStatisticsUpdater;
/** /**
* A fragment to display track chart to the user. * A fragment to display track chart to the user.
@@ -204,7 +204,7 @@ public class ChartFragment extends Fragment implements TrackDataHub.Listener {
public void onSampledInTrackPoint(@NonNull TrackPoint trackPoint, @NonNull Statistics trackStatistics) { public void onSampledInTrackPoint(@NonNull TrackPoint trackPoint, @NonNull Statistics trackStatistics) {
if (isResumed()) { if (isResumed()) {
ChartPoint point = ChartPoint.create(trackStatistics, trackPoint, trackPoint.getSpeed(), chartByDistance, viewBinding.chartView.getUnitSystem()); ChartPoint point = ChartPoint.create(trackStatistics, trackPoint, trackPoint.position().speed(), chartByDistance, viewBinding.chartView.getUnitSystem());
pendingPoints.add(point); pendingPoints.add(point);
} }
} }
@@ -27,8 +27,8 @@ public record ChartPoint(
chartByDistance chartByDistance
? trackStatistics.totalDistance().toKM_Miles(unitSystem) ? trackStatistics.totalDistance().toKM_Miles(unitSystem)
: trackStatistics.totalDuration().toMillis(), : trackStatistics.totalDuration().toMillis(),
trackPoint.hasAltitude() trackPoint.position().hasAltitude()
? Distance.of(trackPoint.getAltitude().toM()).toM_FT(unitSystem) ? Distance.of(trackPoint.position().altitude().toM()).toM_FT(unitSystem)
: null, : null,
smoothedSpeed != null smoothedSpeed != null
? smoothedSpeed.to(unitSystem) ? smoothedSpeed.to(unitSystem)
@@ -36,14 +36,14 @@ public record ChartPoint(
smoothedSpeed != null smoothedSpeed != null
? smoothedSpeed.toPace(unitSystem).toSeconds() / 60d ? smoothedSpeed.toPace(unitSystem).toSeconds() / 60d
: null, : null,
trackPoint.hasHeartRate() trackPoint.heartRate() != null
? (double) trackPoint.getHeartRate().getBPM() ? (double) trackPoint.heartRate().getBPM()
: null, : null,
trackPoint.hasCadence() trackPoint.cadence() != null
? (double) trackPoint.getCadence().getRPM() ? (double) trackPoint.cadence().getRPM()
: null, : null,
trackPoint.hasPower() trackPoint.power() != null
? (double) trackPoint.getPower().getW() ? (double) trackPoint.power().getW()
: null : null
); );
} }
@@ -51,10 +51,10 @@ import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Statistics; import de.dennisguse.opentracks.data.models.Statistics;
import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint; import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.data.statistics.SensorStatistics;
import de.dennisguse.opentracks.data.tables.MarkerColumns; import de.dennisguse.opentracks.data.tables.MarkerColumns;
import de.dennisguse.opentracks.data.tables.TrackPointsColumns; import de.dennisguse.opentracks.data.tables.TrackPointsColumns;
import de.dennisguse.opentracks.data.tables.TracksColumns; import de.dennisguse.opentracks.data.tables.TracksColumns;
import de.dennisguse.opentracks.data.statistics.SensorStatistics;
import de.dennisguse.opentracks.ui.markers.MarkerUtils; import de.dennisguse.opentracks.ui.markers.MarkerUtils;
import de.dennisguse.opentracks.util.FileUtils; import de.dennisguse.opentracks.util.FileUtils;
@@ -544,7 +544,12 @@ public class ContentProviderUtils {
int altitudeGainIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ALTITUDE_GAIN); int altitudeGainIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ALTITUDE_GAIN);
int altitudeLossIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ALTITUDE_LOSS); int altitudeLossIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ALTITUDE_LOSS);
TrackPoint trackPoint = new TrackPoint( AltitudeGainLoss altitudeGainLoss = null;
if (!cursor.isNull(altitudeGainIndex) && !cursor.isNull(altitudeLossIndex)) {
altitudeGainLoss = new AltitudeGainLoss(cursor.getFloat(altitudeGainIndex), cursor.getFloat(altitudeLossIndex));
}
return new TrackPoint(
new TrackPoint.Id(cursor.getInt(idIndex)), new TrackPoint.Id(cursor.getInt(idIndex)),
TrackPoint.Type.getById(cursor.getInt(typeIndex)), TrackPoint.Type.getById(cursor.getInt(typeIndex)),
new Position( new Position(
@@ -556,26 +561,13 @@ public class ContentProviderUtils {
!cursor.isNull(accuracyVerticalIndex) ? Distance.of(cursor.getFloat(accuracyVerticalIndex)) : null, !cursor.isNull(accuracyVerticalIndex) ? Distance.of(cursor.getFloat(accuracyVerticalIndex)) : null,
!cursor.isNull(bearingIndex) ? cursor.getFloat(bearingIndex) : null, !cursor.isNull(bearingIndex) ? cursor.getFloat(bearingIndex) : null,
!cursor.isNull(speedIndex) ? Speed.of(cursor.getFloat(speedIndex)) : null !cursor.isNull(speedIndex) ? Speed.of(cursor.getFloat(speedIndex)) : null
)); ),
!cursor.isNull(sensorDistanceIndex) ? Distance.of(cursor.getFloat(sensorDistanceIndex)) : null,
if (!cursor.isNull(sensorHeartRateIndex)) { !cursor.isNull(sensorHeartRateIndex) ? HeartRate.of(cursor.getFloat(sensorHeartRateIndex)) : null,
trackPoint.setHeartRate(cursor.getFloat(sensorHeartRateIndex)); !cursor.isNull(sensorCadenceIndex) ? Cadence.of(cursor.getFloat(sensorCadenceIndex)) : null,
} !cursor.isNull(sensorPowerIndex) ? Power.of(cursor.getFloat(sensorPowerIndex)) : null,
if (!cursor.isNull(sensorCadenceIndex)) { altitudeGainLoss
trackPoint.setCadence(cursor.getFloat(sensorCadenceIndex)); );
}
if (!cursor.isNull(sensorDistanceIndex)) {
trackPoint.setSensorDistance(Distance.of(cursor.getFloat(sensorDistanceIndex)));
}
if (!cursor.isNull(sensorPowerIndex)) {
trackPoint.setPower(cursor.getFloat(sensorPowerIndex));
}
if (!cursor.isNull(altitudeGainIndex) && !cursor.isNull(altitudeLossIndex)) {
trackPoint.setAltitudeGainLoss(cursor.getFloat(altitudeGainIndex), cursor.getFloat(altitudeLossIndex));
}
return trackPoint;
} }
//TODO Only used for file import; might be better to replace it. //TODO Only used for file import; might be better to replace it.
@@ -637,42 +629,42 @@ public class ContentProviderUtils {
private ContentValues createContentValues(TrackPoint trackPoint, Track.Id trackId) { private ContentValues createContentValues(TrackPoint trackPoint, Track.Id trackId) {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(TrackPointsColumns.TRACKID, trackId.id()); values.put(TrackPointsColumns.TRACKID, trackId.id());
values.put(TrackPointsColumns.TYPE, trackPoint.getType().type_db); values.put(TrackPointsColumns.TYPE, trackPoint.type().type_db);
if (trackPoint.hasLocation()) { if (trackPoint.position().hasLocation()) {
values.put(TrackPointsColumns.LATITUDE, (int) (trackPoint.getPosition().latitude() * 1E6)); values.put(TrackPointsColumns.LATITUDE, (int) (trackPoint.position().latitude() * 1E6));
values.put(TrackPointsColumns.LONGITUDE, (int) (trackPoint.getPosition().longitude() * 1E6)); values.put(TrackPointsColumns.LONGITUDE, (int) (trackPoint.position().longitude() * 1E6));
} }
values.put(TrackPointsColumns.TIME, trackPoint.getTime().toEpochMilli()); values.put(TrackPointsColumns.TIME, trackPoint.getTime().toEpochMilli());
if (trackPoint.hasAltitude()) { if (trackPoint.position().hasAltitude()) {
values.put(TrackPointsColumns.ALTITUDE, trackPoint.getAltitude().toM()); values.put(TrackPointsColumns.ALTITUDE, trackPoint.position().altitude().toM());
} }
if (trackPoint.hasHorizontalAccuracy()) { if (trackPoint.position().hasHorizontalAccuracy()) {
values.put(TrackPointsColumns.HORIZONTAL_ACCURACY, trackPoint.getHorizontalAccuracy().toM()); values.put(TrackPointsColumns.HORIZONTAL_ACCURACY, trackPoint.position().horizontalAccuracy().toM());
} }
if (trackPoint.hasSpeed()) { if (trackPoint.position().hasSpeed()) {
values.put(TrackPointsColumns.SPEED, trackPoint.getSpeed().toMPS()); values.put(TrackPointsColumns.SPEED, trackPoint.position().speed().toMPS());
} }
if (trackPoint.hasBearing()) { if (trackPoint.position().hasBearing()) {
values.put(TrackPointsColumns.BEARING, trackPoint.getBearing()); values.put(TrackPointsColumns.BEARING, trackPoint.position().bearing());
} }
if (trackPoint.hasHeartRate()) { if (trackPoint.heartRate() != null) {
values.put(TrackPointsColumns.SENSOR_HEARTRATE, trackPoint.getHeartRate().getBPM()); values.put(TrackPointsColumns.SENSOR_HEARTRATE, trackPoint.heartRate().getBPM());
} }
if (trackPoint.hasCadence()) { if (trackPoint.cadence() != null) {
values.put(TrackPointsColumns.SENSOR_CADENCE, trackPoint.getCadence().getRPM()); values.put(TrackPointsColumns.SENSOR_CADENCE, trackPoint.cadence().getRPM());
} }
if (trackPoint.hasSensorDistance()) { if (trackPoint.sensorDistance() != null) {
values.put(TrackPointsColumns.SENSOR_DISTANCE, trackPoint.getSensorDistance().toM()); values.put(TrackPointsColumns.SENSOR_DISTANCE, trackPoint.sensorDistance().toM());
} }
if (trackPoint.hasPower()) { if (trackPoint.power() != null) {
values.put(TrackPointsColumns.SENSOR_POWER, trackPoint.getPower().getW()); values.put(TrackPointsColumns.SENSOR_POWER, trackPoint.power().getW());
} }
if (trackPoint.hasAltitudeGainLoss()) { if (trackPoint.altitudeGainLoss() != null) {
values.put(TrackPointsColumns.ALTITUDE_GAIN, trackPoint.getAltitudeGainLoss().gain_m()); values.put(TrackPointsColumns.ALTITUDE_GAIN, trackPoint.altitudeGainLoss().gain_m());
values.put(TrackPointsColumns.ALTITUDE_LOSS, trackPoint.getAltitudeGainLoss().loss_m()); values.put(TrackPointsColumns.ALTITUDE_LOSS, trackPoint.altitudeGainLoss().loss_m());
} }
return values; return values;
} }
@@ -33,12 +33,12 @@ import java.util.Set;
import de.dennisguse.opentracks.data.models.Statistics; import de.dennisguse.opentracks.data.models.Statistics;
import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint; import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.data.statistics.TrackStatisticsUpdater;
import de.dennisguse.opentracks.data.tables.TrackPointsColumns; import de.dennisguse.opentracks.data.tables.TrackPointsColumns;
import de.dennisguse.opentracks.data.tables.TracksColumns; import de.dennisguse.opentracks.data.tables.TracksColumns;
import de.dennisguse.opentracks.services.RecordingStatus; import de.dennisguse.opentracks.services.RecordingStatus;
import de.dennisguse.opentracks.services.TrackRecordingService; import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.handlers.AltitudeCorrectionManager; import de.dennisguse.opentracks.services.handlers.AltitudeCorrectionManager;
import de.dennisguse.opentracks.data.statistics.TrackStatisticsUpdater;
/** /**
* Track data hub. * Track data hub.
@@ -288,14 +288,14 @@ public class TrackDataHub {
} }
trackPoint = trackPointIterator.next(); trackPoint = trackPointIterator.next();
TrackPoint.Id trackPointId = trackPoint.getId(); TrackPoint.Id trackPointId = trackPoint.id();
// Stop if past the last wanted point // Stop if past the last wanted point
if (maxPointId != null && trackPointId.id() > maxPointId.id()) { if (maxPointId != null && trackPointId.id() > maxPointId.id()) {
break; break;
} }
egm2008Correction.correctAltitude(context, trackPoint); trackPoint = egm2008Correction.correctAltitude(context, trackPoint);
if (localFirstSeenTrackPointId == null) { if (localFirstSeenTrackPointId == null) {
localFirstSeenTrackPointId = trackPointId; localFirstSeenTrackPointId = trackPointId;
@@ -319,7 +319,7 @@ public class TrackDataHub {
} }
if (trackPoint != null) { if (trackPoint != null) {
localLastSeenTrackPointIdId = trackPoint.getId(); localLastSeenTrackPointIdId = trackPoint.id();
} }
if (updateSamplingState) { if (updateSamplingState) {
@@ -1,5 +1,7 @@
package de.dennisguse.opentracks.data.models; package de.dennisguse.opentracks.data.models;
import androidx.annotation.Nullable;
public record HeartRate(float value) { public record HeartRate(float value) {
public static HeartRate INVALID = HeartRate.of(0.0f); public static HeartRate INVALID = HeartRate.of(0.0f);
@@ -7,6 +9,12 @@ public record HeartRate(float value) {
return new HeartRate(value); return new HeartRate(value);
} }
@Nullable
public static HeartRate ofOrNull(Float value) {
if (value == null) return null;
return new HeartRate(value);
}
public float getBPM() { public float getBPM() {
return value; return value;
} }
@@ -51,10 +51,10 @@ public final class MarkerBuilder {
this.typeLocalized = ""; this.typeLocalized = "";
this.photoUrl = null; this.photoUrl = null;
if (!trackPoint.hasLocation()) if (!trackPoint.position().hasLocation())
throw new RuntimeException("Marker requires a trackpoint with a location."); throw new RuntimeException("Marker requires a trackpoint with a location.");
this.position = trackPoint.getPosition(); this.position = trackPoint.position();
} }
public MarkerBuilder(@Nullable Track.Id trackId, @NonNull TrackPoint trackPoint, String name, String description, String typeLocalized, Uri photoUrl) { public MarkerBuilder(@Nullable Track.Id trackId, @NonNull TrackPoint trackPoint, String name, String description, String typeLocalized, Uri photoUrl) {
@@ -17,8 +17,9 @@ public record Position(
@Nullable Float bearing, @Nullable Float bearing,
@Nullable Speed speed @Nullable Speed speed
) { ) {
@Deprecated
public static Position empty() { public static Position empty() {
return of((Instant) null); return of(null);
} }
public static Position of(@NonNull Instant time) { public static Position of(@NonNull Instant time) {
@@ -34,10 +35,6 @@ public record Position(
); );
} }
public static Position of(@NonNull Location location) {
return of(location, Instant.ofEpochMilli(location.getTime()));
}
public static Position of(@NonNull Location location, @NonNull Instant time) { public static Position of(@NonNull Location location, @NonNull Instant time) {
return new Position( return new Position(
time, time,
@@ -132,9 +129,8 @@ public record Position(
return new Position(time, latitude, longitude, horizontalAccuracy, altitude, verticalAccuracy, bearing, speed); return new Position(time, latitude, longitude, horizontalAccuracy, altitude, verticalAccuracy, bearing, speed);
} }
//TODO Use double public Position withCoordinates(Position position) {
public Position withCoordinates(Double latitude, Double longitude) { return new Position(time, position.latitude, position.longitude, horizontalAccuracy, altitude, verticalAccuracy, bearing, speed);
return new Position(time, latitude, longitude, horizontalAccuracy, altitude, verticalAccuracy, bearing, speed);
} }
//TODO Use float //TODO Use float
@@ -22,23 +22,168 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import java.time.Instant; import java.time.Instant;
import java.util.Optional;
/** /**
* Sensor and/or location information for a specific point in time. * Sensor and/or location information for a specific point in time.
* <p> * <p>
* Time is created using the {@link de.dennisguse.opentracks.services.handlers.MonotonicClock}, because system time jump backwards. * Time is created using the {@link de.dennisguse.opentracks.services.handlers.MonotonicClock}, because system time jump backwards.
* GPS time is ignored as for non-GPS events, we could not create GPS-based timestamps. * GPS time is ignored as for non-GPS events, we cannot create GPS-based timestamps.
*/ */
//TODO Should be a record (with final properties) public record TrackPoint(
public class TrackPoint {
@Nullable @Nullable
private final TrackPoint.Id id; TrackPoint.Id id,
@NonNull
TrackPoint.Type type,
//Requires: position.time must be non-null
@NonNull
Position position,
Distance sensorDistance,
HeartRate heartRate,
Cadence cadence,
Power power,
AltitudeGainLoss altitudeGainLoss
) {
public TrackPoint(Type type, Position position) {
this(
null,
type,
position,
null,
null,
null,
null,
null
);
}
@Deprecated //TOOD Private?
public TrackPoint(Type type, Instant now) {
this(
null,
type,
Position.of(now),
null,
null,
null,
null,
null
);
}
public static TrackPoint createSegmentStartManualWithTime(Instant time) {
return new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, time);
}
public static TrackPoint with(TrackPoint trackPoint, Altitude.EGM2008 correctAltitude) {
return new TrackPoint(
trackPoint.id,
trackPoint.type,
new Position(
trackPoint.position.time(),
trackPoint.position.latitude(),
trackPoint.position.longitude(),
trackPoint.position.horizontalAccuracy(),
correctAltitude,
trackPoint.position.verticalAccuracy(),
trackPoint.position.bearing(),
trackPoint.position.speed()
),
trackPoint.sensorDistance,
trackPoint.heartRate,
trackPoint.cadence,
trackPoint.power,
trackPoint.altitudeGainLoss
);
}
public TrackPoint with(Type newType) {
return new TrackPoint(
id,
newType,
position,
sensorDistance,
heartRate,
cadence,
power,
altitudeGainLoss
);
}
public TrackPoint with(Position newPosition) {
return new TrackPoint(
id,
type,
newPosition,
sensorDistance,
heartRate,
cadence,
power,
altitudeGainLoss
);
}
//Requires: position.time must be non-null
@NonNull @NonNull
private Position position; public Instant getTime() {
return position.time();
}
public TrackPoint minusCumulativeSensorData(@NonNull TrackPoint lastTrackPoint) {
Distance newSensorDistance = sensorDistance;
if (sensorDistance != null && lastTrackPoint.sensorDistance != null) {
newSensorDistance = sensorDistance.minus(lastTrackPoint.sensorDistance);
}
AltitudeGainLoss newAltitudeGainLoss = altitudeGainLoss;
if (altitudeGainLoss != null && lastTrackPoint.altitudeGainLoss() != null) {
newAltitudeGainLoss = new AltitudeGainLoss(
altitudeGainLoss.gain_m() - lastTrackPoint.altitudeGainLoss.gain_m(),
altitudeGainLoss.loss_m() - lastTrackPoint.altitudeGainLoss.loss_m());
}
return new TrackPoint(
id,
type,
position,
newSensorDistance,
heartRate,
cadence,
power,
newAltitudeGainLoss
);
}
@NonNull
public Distance distanceToPrevious(@NonNull TrackPoint previous) {
if (sensorDistance() != null) {
return sensorDistance();
}
return distanceToPreviousFromLocation(previous);
}
@NonNull
public Distance distanceToPreviousFromLocation(@NonNull TrackPoint previous) {
if (!position.hasLocation() || !previous.position().hasLocation()) {
throw new RuntimeException("Cannot compute distance.");
}
return Distance.of(position().toLocation().distanceTo(previous.position().toLocation()));
}
public boolean isIdleTriggered() {
return type == TrackPoint.Type.IDLE;
}
public boolean isSegmentManualStart() {
return type == TrackPoint.Type.SEGMENT_START_MANUAL;
}
public boolean isSegmentManualEnd() {
return type == TrackPoint.Type.SEGMENT_END_MANUAL;
}
public enum Type { public enum Type {
SEGMENT_START_MANUAL(-2), //Start of a segment due to user interaction (start, resume) SEGMENT_START_MANUAL(-2), //Start of a segment due to user interaction (start, resume)
@@ -75,298 +220,6 @@ public class TrackPoint {
} }
} }
@NonNull
private Type type;
private Distance sensorDistance;
private HeartRate heartRate = null;
private Cadence cadence = null;
private Power power = null;
private AltitudeGainLoss altitudeGainLoss = null;
public TrackPoint(@Nullable TrackPoint.Id id, @NonNull Type type, @NonNull Position position) {
this.id = id;
this.type = type;
this.position = position;
}
public TrackPoint(@NonNull Type type, @NonNull Instant time) {
this(null, type, Position.of(time));
}
public TrackPoint(@NonNull Type type, @NonNull Position position) {
this(null, type, position);
}
public static TrackPoint createSegmentStartManualWithTime(Instant time) {
return new TrackPoint(Type.SEGMENT_START_MANUAL, time);
}
public static TrackPoint createSegmentEndWithTime(Instant time) {
return new TrackPoint(Type.SEGMENT_END_MANUAL, time);
}
@NonNull
public Type getType() {
return type;
}
@Deprecated //Should not be needed.
public TrackPoint setType(@NonNull Type type) {
this.type = type;
return this;
}
public boolean isIdleTriggered() {
return type == Type.IDLE;
}
public boolean isSegmentManualStart() {
return type == Type.SEGMENT_START_MANUAL;
}
public boolean isSegmentManualEnd() {
return type == Type.SEGMENT_END_MANUAL;
}
//TODO Check if naming is correct.
public boolean wasCreatedManually() {
return hasLocation() || hasSpeed();
}
/**
* May be null if the track was not loaded from the database.
*/
@Nullable
public TrackPoint.Id getId() {
return id;
}
public boolean hasLocation() {
return position.hasLocation();
}
@NonNull
public Position getPosition() {
return position;
}
public TrackPoint setPosition(Position position) {
this.position = position.with(this.position.time());
return this;
}
public boolean hasAltitudeGainLoss() {
return altitudeGainLoss != null;
}
public AltitudeGainLoss getAltitudeGainLoss() {
return altitudeGainLoss;
}
public TrackPoint setAltitudeGainLoss(AltitudeGainLoss altitudeGainLoss) {
this.altitudeGainLoss = altitudeGainLoss;
return this;
}
public TrackPoint setAltitudeGainLoss(float altitudeGain_m, float altitudeLoss_m) {
setAltitudeGainLoss(new AltitudeGainLoss(altitudeGain_m, altitudeLoss_m));
return this;
}
@NonNull
public Instant getTime() {
return position.time();
}
public boolean hasAltitude() {
return position.hasAltitude();
}
public Altitude getAltitude() {
return position.altitude();
}
@Deprecated
public TrackPoint setAltitude(Altitude altitude) {
position = position.with(altitude);
return this;
}
public boolean hasSpeed() {
return position.hasSpeed();
}
public Speed getSpeed() {
return position.speed();
}
@Deprecated
public TrackPoint setSpeed(Speed speed) {
this.position = position.with(speed);
return this;
}
public boolean hasBearing() {
return position.hasBearing();
}
public float getBearing() {
return position.bearing();
}
public TrackPoint setBearing(Float bearing) {
this.position = this.position.withBearing(bearing);
return this;
}
public boolean hasHorizontalAccuracy() {
return position.hasHorizontalAccuracy();
}
public Distance getHorizontalAccuracy() {
return position.horizontalAccuracy();
}
@Deprecated
public TrackPoint setHorizontalAccuracy(Distance horizontalAccuracy) {
this.position = this.position.withHorizontalAccuracy(horizontalAccuracy);
return this;
}
public boolean hasVerticalAccuracy() {
return position.hasVerticalAccuracy();
}
public Distance getVerticalAccuracy() {
return position.verticalAccuracy();
}
public TrackPoint setVerticalAccuracy(Distance verticalAccuracy) {
this.position = this.position.withVerticalAccuracy(verticalAccuracy);
return this;
}
@NonNull
public Distance distanceToPrevious(@NonNull TrackPoint previous) {
if (hasSensorDistance()) {
return getSensorDistance();
}
return distanceToPreviousFromLocation(previous);
}
@NonNull
public Distance distanceToPreviousFromLocation(@NonNull TrackPoint previous) {
if (!hasLocation() || hasLocation() != previous.hasLocation()) {
throw new RuntimeException("Cannot compute distance.");
}
return Distance.of(getPosition().toLocation().distanceTo(previous.getPosition().toLocation()));
}
public boolean fulfillsAccuracy(Distance thresholdHorizontalAccuracy) {
return position.fulfillsAccuracy(thresholdHorizontalAccuracy);
}
public Optional<Float> bearingTo(@NonNull TrackPoint dest) {
if (!dest.hasLocation() || !hasLocation()) {
return Optional.empty();
}
return Optional.of(getPosition().toLocation().bearingTo(dest.getPosition().toLocation()));
}
// Sensor data
public boolean hasSensorDistance() {
return sensorDistance != null;
}
public Distance getSensorDistance() {
return sensorDistance;
}
public TrackPoint setSensorDistance(Distance distance_m) {
this.sensorDistance = distance_m;
return this;
}
public TrackPoint minusCumulativeSensorData(@NonNull TrackPoint lastTrackPoint) {
if (hasSensorDistance() && lastTrackPoint.hasSensorDistance()) {
sensorDistance = sensorDistance.minus(lastTrackPoint.getSensorDistance());
}
if (hasAltitudeGainLoss() && lastTrackPoint.hasAltitudeGainLoss()) {
altitudeGainLoss = new AltitudeGainLoss(
altitudeGainLoss.gain_m() - lastTrackPoint.altitudeGainLoss.gain_m(),
altitudeGainLoss.loss_m() - lastTrackPoint.altitudeGainLoss.loss_m());
}
return this;
}
public boolean hasHeartRate() {
return heartRate != null;
}
public HeartRate getHeartRate() {
return heartRate;
}
public TrackPoint setHeartRate(HeartRate heartRate) {
this.heartRate = heartRate;
return this;
}
public TrackPoint setHeartRate(float heartRate) {
return setHeartRate(HeartRate.of(heartRate));
}
public boolean hasCadence() {
return cadence != null;
}
public Cadence getCadence() {
return cadence;
}
public TrackPoint setCadence(Cadence cadence) {
this.cadence = cadence;
return this;
}
public TrackPoint setCadence(float cadence) {
return setCadence(Cadence.of(cadence));
}
public boolean hasPower() {
return power != null;
}
public Power getPower() {
return power;
}
public TrackPoint setPower(Power power) {
this.power = power;
return this;
}
public TrackPoint setPower(float power) {
return setPower(Power.of(power));
}
@NonNull
@Override
public String toString() {
return "TrackPoint{" +
"id=" + id +
", type=" + type +
", position=" + position +
", sensorDistance=" + sensorDistance +
", heartRate=" + heartRate +
", cadence=" + cadence +
", power=" + power +
", altitudeGainLoss=" + altitudeGainLoss +
'}';
}
public record Id(long id) implements Parcelable { public record Id(long id) implements Parcelable {
@Override @Override
@@ -154,7 +154,7 @@ public class SegmentStatisticUpdater {
Duration movingDuration = Duration.between(lastTrackPoint.getTime(), trackPoint.getTime()); Duration movingDuration = Duration.between(lastTrackPoint.getTime(), trackPoint.getTime());
if (movingDuration.isNegative()) { if (movingDuration.isNegative()) {
throw new RuntimeException("Moving time cannot be negative"); throw new RuntimeException("Moving time cannot be negative: " + lastTrackPoint.getTime() + " is after " + trackPoint.getTime());
} }
this.movingDuration = this.movingDuration.plus(movingDuration); this.movingDuration = this.movingDuration.plus(movingDuration);
} }
@@ -111,32 +111,32 @@ public class TrackStatisticsUpdater {
currentSegment.updateTotalTime(trackPoint.getTime()); currentSegment.updateTotalTime(trackPoint.getTime());
// Process sensor data: barometer // Process sensor data: barometer
if (trackPoint.hasAltitudeGainLoss()) { if (trackPoint.altitudeGainLoss() != null) {
currentSegment.addTotalAltitudeGainLoss(trackPoint.getAltitudeGainLoss()); currentSegment.addTotalAltitudeGainLoss(trackPoint.altitudeGainLoss());
} }
//Update absolute (GPS-based) altitude // Update absolute (GPS-based) altitude
if (trackPoint.hasAltitude()) { if (trackPoint.position().hasAltitude()) {
currentSegment.updateAltitudeExtremities(trackPoint.getAltitude()); currentSegment.updateAltitudeExtremities(trackPoint.position().altitude());
} }
// Update heart rate // Update heart rate
if (trackPoint.hasHeartRate() && lastTrackPoint != null) { if (trackPoint.heartRate() != null && lastTrackPoint != null) {
Duration trackPointDuration = Duration.between(lastTrackPoint.getTime(), trackPoint.getTime()); Duration trackPointDuration = Duration.between(lastTrackPoint.getTime(), trackPoint.getTime());
Duration newTotalDuration = totalHeartRateDuration.plus(trackPointDuration); Duration newTotalDuration = totalHeartRateDuration.plus(trackPointDuration);
averageHeartRateBPM = (totalHeartRateDuration.toMillis() * averageHeartRateBPM + trackPointDuration.toMillis() * trackPoint.getHeartRate().getBPM()) / newTotalDuration.toMillis(); averageHeartRateBPM = (totalHeartRateDuration.toMillis() * averageHeartRateBPM + trackPointDuration.toMillis() * trackPoint.heartRate().getBPM()) / newTotalDuration.toMillis();
totalHeartRateDuration = newTotalDuration; totalHeartRateDuration = newTotalDuration;
currentSegment.setAverageHeartRate(HeartRate.of(averageHeartRateBPM)); currentSegment.setAverageHeartRate(HeartRate.of(averageHeartRateBPM));
} }
// Update power // Update power
if (trackPoint.hasPower() && lastTrackPoint != null) { if (trackPoint.power() != null && lastTrackPoint != null) {
Duration trackPointDuration = Duration.between(lastTrackPoint.getTime(), trackPoint.getTime()); Duration trackPointDuration = Duration.between(lastTrackPoint.getTime(), trackPoint.getTime());
Duration newTotalDuration = totalPowerDuration.plus(trackPointDuration); Duration newTotalDuration = totalPowerDuration.plus(trackPointDuration);
averagePowerW = (totalPowerDuration.toMillis() * averagePowerW + trackPointDuration.toMillis() * trackPoint.getPower().getW()) / newTotalDuration.toMillis(); averagePowerW = (totalPowerDuration.toMillis() * averagePowerW + trackPointDuration.toMillis() * trackPoint.power().getW()) / newTotalDuration.toMillis();
totalPowerDuration = newTotalDuration; totalPowerDuration = newTotalDuration;
currentSegment.setAveragePower(Power.of(averagePowerW)); currentSegment.setAveragePower(Power.of(averagePowerW));
@@ -145,11 +145,11 @@ public class TrackStatisticsUpdater {
{ {
// Update total distance // Update total distance
Distance movingDistance = null; Distance movingDistance = null;
if (trackPoint.hasSensorDistance()) { if (trackPoint.sensorDistance() != null) {
movingDistance = trackPoint.getSensorDistance(); movingDistance = trackPoint.sensorDistance();
} else if (lastTrackPoint != null } else if (lastTrackPoint != null
&& lastTrackPoint.hasLocation() && lastTrackPoint.position().hasLocation()
&& trackPoint.hasLocation()) { && trackPoint.position().hasLocation()) {
// GPS-based distance/speed // GPS-based distance/speed
movingDistance = trackPoint.distanceToPrevious(lastTrackPoint); movingDistance = trackPoint.distanceToPrevious(lastTrackPoint);
} }
@@ -173,7 +173,7 @@ public class TrackStatisticsUpdater {
} }
} }
if (trackPoint.hasSpeed()) { if (trackPoint.position().hasSpeed()) {
updateSpeed(trackPoint); updateSpeed(trackPoint);
} }
} }
@@ -205,7 +205,7 @@ public class TrackStatisticsUpdater {
* Updates a speed reading while assuming the user is moving. * Updates a speed reading while assuming the user is moving.
*/ */
private void updateSpeed(@NonNull TrackPoint trackPoint) { private void updateSpeed(@NonNull TrackPoint trackPoint) {
Speed currentSpeed = trackPoint.getSpeed(); Speed currentSpeed = trackPoint.position().speed();
if (currentSpeed.greaterThan(currentSegment.getMaxSpeed())) { if (currentSpeed.greaterThan(currentSegment.getMaxSpeed())) {
currentSegment.setMaxSpeed(currentSpeed); currentSegment.setMaxSpeed(currentSpeed);
} }
@@ -215,8 +215,8 @@ public class TrackStatisticsUpdater {
@Override @Override
public String toString() { public String toString() {
return "TrackStatisticsUpdater{" + return "TrackStatisticsUpdater{" +
"segmentStatisticUpdater=" + statisticsWithoutCurrentSegment + "currentSegment=" + currentSegment +
", currentSegment=" + currentSegment + ", segmentStatisticUpdater=" + statisticsWithoutCurrentSegment +
", lastTrackPoint=" + lastTrackPoint + ", lastTrackPoint=" + lastTrackPoint +
'}'; '}';
} }
@@ -90,20 +90,20 @@ public class CSVTrackExporter implements TrackExporter {
public boolean writeTrack(@NonNull List<Track> tracks, @NonNull OutputStream outputStream) { public boolean writeTrack(@NonNull List<Track> tracks, @NonNull OutputStream outputStream) {
List<Column> columns = List.of( List<Column> columns = List.of(
new Column("time", null), new Column("time", null),
new Column("trackpoint_type", t -> quote(t.getType().name())), new Column("trackpoint_type", t -> quote(t.type().name())),
new Column("latitude", t -> t.hasLocation() ? COORDINATE_FORMAT.format(t.getPosition().latitude()) : ""), new Column("latitude", t -> t.position().hasLocation() ? COORDINATE_FORMAT.format(t.position().latitude()) : ""),
new Column("longitude", t -> t.hasLocation() ? COORDINATE_FORMAT.format(t.getPosition().longitude()) : ""), new Column("longitude", t -> t.position().hasLocation() ? COORDINATE_FORMAT.format(t.position().longitude()) : ""),
new Column("altitude", t -> t.hasAltitude() ? ALTITUDE_FORMAT.format(t.getAltitude().toM()) : ""), new Column("altitude", t -> t.position().hasAltitude() ? ALTITUDE_FORMAT.format(t.position().altitude().toM()) : ""),
new Column("accuracy_horizontal", t -> t.hasHorizontalAccuracy() ? DISTANCE_FORMAT.format(t.getHorizontalAccuracy().toM()) : ""), new Column("accuracy_horizontal", t -> t.position().hasHorizontalAccuracy() ? DISTANCE_FORMAT.format(t.position().horizontalAccuracy().toM()) : ""),
new Column("accuracy_vertical", t -> t.hasVerticalAccuracy() ? DISTANCE_FORMAT.format(t.getVerticalAccuracy().toM()) : ""), new Column("accuracy_vertical", t -> t.position().hasVerticalAccuracy() ? DISTANCE_FORMAT.format(t.position().verticalAccuracy().toM()) : ""),
new Column("speed", t -> t.hasSpeed() ? SPEED_FORMAT.format(t.getSpeed().toKMH()) : ""), new Column("speed", t -> t.position().hasSpeed() ? SPEED_FORMAT.format(t.position().speed().toKMH()) : ""),
new Column("altitude_gain", t -> t.hasAltitudeGainLoss() ? ALTITUDE_FORMAT.format(t.getAltitudeGainLoss().gain_m()) : ""), new Column("altitude_gain", t -> t.altitudeGainLoss() != null ? ALTITUDE_FORMAT.format(t.altitudeGainLoss().gain_m()) : ""),
new Column("altitude_loss", t -> t.hasAltitudeGainLoss() ? ALTITUDE_FORMAT.format(t.getAltitudeGainLoss().loss_m()) : ""), new Column("altitude_loss", t -> t.altitudeGainLoss() != null ? ALTITUDE_FORMAT.format(t.altitudeGainLoss().loss_m()) : ""),
new Column("sensor_distance", t -> t.hasSensorDistance() ? DISTANCE_FORMAT.format(t.getSensorDistance().toM()) : ""), new Column("sensor_distance", t -> t.sensorDistance() != null ? DISTANCE_FORMAT.format(t.sensorDistance().toM()) : ""),
new Column("heartrate", t -> t.hasHeartRate() ? HEARTRATE_FORMAT.format(t.getHeartRate().getBPM()) : ""), new Column("heartrate", t -> t.heartRate() != null ? HEARTRATE_FORMAT.format(t.heartRate().getBPM()) : ""),
new Column("cadence", t -> t.hasCadence() ? CADENCE_FORMAT.format(t.getCadence().getRPM()) : ""), new Column("cadence", t -> t.cadence() != null ? CADENCE_FORMAT.format(t.cadence().getRPM()) : ""),
new Column("power", t -> t.hasPower() ? POWER_FORMAT.format(t.getPower().getW()) : "")); new Column("power", t -> t.power() != null ? POWER_FORMAT.format(t.power().getW()) : ""));
try { try {
prepare(outputStream); prepare(outputStream);
@@ -140,7 +140,7 @@ public class GPXTrackExporter implements TrackExporter {
wroteTrack = true; wroteTrack = true;
} }
switch (trackPoint.getType()) { switch (trackPoint.type()) {
case SEGMENT_START_MANUAL -> case SEGMENT_START_MANUAL ->
Log.i(TAG, "Exporting " + TrackPoint.Type.SEGMENT_START_MANUAL.name() + " is not supported."); Log.i(TAG, "Exporting " + TrackPoint.Type.SEGMENT_START_MANUAL.name() + " is not supported.");
case SEGMENT_END_MANUAL -> { case SEGMENT_END_MANUAL -> {
@@ -152,7 +152,7 @@ public class GPXTrackExporter implements TrackExporter {
if (wroteSegment) writeCloseSegment(); if (wroteSegment) writeCloseSegment();
writeOpenSegment(); writeOpenSegment();
wroteSegment = true; wroteSegment = true;
if (trackPoint.hasLocation()) { if (trackPoint.position().hasLocation()) {
trackDistance = trackDistance.plus(writeTrackPoint(track.zoneOffset(), trackPoint, sensorPoints, trackDistance)); trackDistance = trackDistance.plus(writeTrackPoint(track.zoneOffset(), trackPoint, sensorPoints, trackDistance));
sensorPoints.clear(); sensorPoints.clear();
} else { } else {
@@ -165,7 +165,7 @@ public class GPXTrackExporter implements TrackExporter {
writeOpenSegment(); writeOpenSegment();
wroteSegment = true; wroteSegment = true;
} }
if (trackPoint.hasLocation()) { if (trackPoint.position().hasLocation()) {
trackDistance = trackDistance.plus(writeTrackPoint(track.zoneOffset(), trackPoint, sensorPoints, trackDistance)); trackDistance = trackDistance.plus(writeTrackPoint(track.zoneOffset(), trackPoint, sensorPoints, trackDistance));
sensorPoints.clear(); sensorPoints.clear();
} else { } else {
@@ -176,7 +176,7 @@ public class GPXTrackExporter implements TrackExporter {
// Not supported as IDLE-TrackPoints have no location. // Not supported as IDLE-TrackPoints have no location.
} }
default -> default ->
throw new RuntimeException("Exporting this TrackPoint type is not implemented: " + trackPoint.getType()); throw new RuntimeException("Exporting this TrackPoint type is not implemented: " + trackPoint.type());
} }
} }
@@ -306,10 +306,10 @@ public class GPXTrackExporter implements TrackExporter {
private Distance writeTrackPoint(ZoneOffset zoneOffset, TrackPoint trackPoint, List<TrackPoint> sensorPoints, Distance trackDistance) { private Distance writeTrackPoint(ZoneOffset zoneOffset, TrackPoint trackPoint, List<TrackPoint> sensorPoints, Distance trackDistance) {
Distance cumulativeDistance; Distance cumulativeDistance;
printWriter.println("<trkpt " + formatLocation(trackPoint.getPosition()) + ">"); printWriter.println("<trkpt " + formatLocation(trackPoint.position()) + ">");
if (trackPoint.hasAltitude()) { if (trackPoint.position().hasAltitude()) {
printWriter.println("<ele>" + ALTITUDE_FORMAT.format(trackPoint.getAltitude().toM()) + "</ele>"); printWriter.println("<ele>" + ALTITUDE_FORMAT.format(trackPoint.position().altitude().toM()) + "</ele>");
} }
printWriter.println("<time>" + StringUtils.formatDateTimeIso8601(trackPoint.getTime(), zoneOffset) + "</time>"); printWriter.println("<time>" + StringUtils.formatDateTimeIso8601(trackPoint.getTime(), zoneOffset) + "</time>");
@@ -317,41 +317,41 @@ public class GPXTrackExporter implements TrackExporter {
{ {
String trackPointExtensionV2Content = ""; String trackPointExtensionV2Content = "";
if (trackPoint.hasHeartRate()) { if (trackPoint.heartRate() != null) {
trackPointExtensionV2Content += "<gpxtpx:hr>" + HEARTRATE_FORMAT.format(trackPoint.getHeartRate().getBPM()) + "</gpxtpx:hr>\n"; trackPointExtensionV2Content += "<gpxtpx:hr>" + HEARTRATE_FORMAT.format(trackPoint.heartRate().getBPM()) + "</gpxtpx:hr>\n";
} }
if (trackPoint.hasCadence()) { if (trackPoint.cadence() != null) {
trackPointExtensionV2Content += "<gpxtpx:cad>" + CADENCE_FORMAT.format(trackPoint.getCadence().getRPM()) + "</gpxtpx:cad>\n"; trackPointExtensionV2Content += "<gpxtpx:cad>" + CADENCE_FORMAT.format(trackPoint.cadence().getRPM()) + "</gpxtpx:cad>\n";
} }
if (trackPoint.hasSpeed()) { if (trackPoint.position().hasSpeed()) {
trackPointExtensionV2Content += "<gpxtpx:speed>" + SPEED_FORMAT.format(trackPoint.getSpeed().toMPS()) + "</gpxtpx:speed>\n"; trackPointExtensionV2Content += "<gpxtpx:speed>" + SPEED_FORMAT.format(trackPoint.position().speed().toMPS()) + "</gpxtpx:speed>\n";
} }
String extensionContent = ""; String extensionContent = "";
if (trackPoint.hasPower()) { if (trackPoint.power() != null) {
extensionContent += "<pwr:PowerInWatts>" + POWER_FORMAT.format(trackPoint.getPower().getW()) + "</pwr:PowerInWatts>\n"; extensionContent += "<pwr:PowerInWatts>" + POWER_FORMAT.format(trackPoint.power().getW()) + "</pwr:PowerInWatts>\n";
} }
Double cumulativeGain = cumulateSensorData(trackPoint, sensorPoints, (tp) -> tp.hasAltitudeGainLoss() ? (double) tp.getAltitudeGainLoss().gain_m() : null); Double cumulativeGain = cumulateSensorData(trackPoint, sensorPoints, (tp) -> tp.altitudeGainLoss() != null ? (double) tp.altitudeGainLoss().gain_m() : null);
if (cumulativeGain != null) { if (cumulativeGain != null) {
extensionContent += ("<opentracks:gain>" + ALTITUDE_FORMAT.format(cumulativeGain) + "</opentracks:gain>\n"); extensionContent += ("<opentracks:gain>" + ALTITUDE_FORMAT.format(cumulativeGain) + "</opentracks:gain>\n");
} }
Double cumulativeLoss = cumulateSensorData(trackPoint, sensorPoints, (tp) -> tp.hasAltitudeGainLoss() ? (double) tp.getAltitudeGainLoss().loss_m() : null); Double cumulativeLoss = cumulateSensorData(trackPoint, sensorPoints, (tp) -> tp.altitudeGainLoss() != null ? (double) tp.altitudeGainLoss().loss_m() : null);
if (cumulativeLoss != null) { if (cumulativeLoss != null) {
extensionContent += ("<opentracks:loss>" + ALTITUDE_FORMAT.format(cumulativeLoss) + "</opentracks:loss>\n"); extensionContent += ("<opentracks:loss>" + ALTITUDE_FORMAT.format(cumulativeLoss) + "</opentracks:loss>\n");
} }
if (trackPoint.hasHorizontalAccuracy()) { if (trackPoint.position().hasHorizontalAccuracy()) {
extensionContent += ("<opentracks:accuracy_horizontal>" + DISTANCE_FORMAT.format(trackPoint.getHorizontalAccuracy().toM()) + "</opentracks:accuracy_horizontal>"); extensionContent += ("<opentracks:accuracy_horizontal>" + DISTANCE_FORMAT.format(trackPoint.position().horizontalAccuracy().toM()) + "</opentracks:accuracy_horizontal>");
} }
if (trackPoint.hasVerticalAccuracy()) { if (trackPoint.position().hasVerticalAccuracy()) {
extensionContent += ("<opentracks:accuracy_vertical>" + DISTANCE_FORMAT.format(trackPoint.getVerticalAccuracy().toM()) + "</opentracks:accuracy_vertical>"); extensionContent += ("<opentracks:accuracy_vertical>" + DISTANCE_FORMAT.format(trackPoint.position().verticalAccuracy().toM()) + "</opentracks:accuracy_vertical>");
} }
cumulativeDistance = Distance.ofOrNull(cumulateSensorData(trackPoint, sensorPoints, (tp) -> tp.hasSensorDistance() ? tp.getSensorDistance().toM() : null)); cumulativeDistance = Distance.ofOrNull(cumulateSensorData(trackPoint, sensorPoints, (tp) -> tp.sensorDistance() != null ? tp.sensorDistance().toM() : null));
if (cumulativeDistance != null) { if (cumulativeDistance != null) {
extensionContent += ("<opentracks:distance>" + DISTANCE_FORMAT.format(cumulativeDistance.toM()) + "</opentracks:distance>\n"); extensionContent += ("<opentracks:distance>" + DISTANCE_FORMAT.format(cumulativeDistance.toM()) + "</opentracks:distance>\n");
extensionContent += ("<cluetrust:distance>" + DISTANCE_FORMAT.format(trackDistance.plus(cumulativeDistance).toM()) + "</cluetrust:distance>\n"); extensionContent += ("<cluetrust:distance>" + DISTANCE_FORMAT.format(trackDistance.plus(cumulativeDistance).toM()) + "</cluetrust:distance>\n");
@@ -95,7 +95,7 @@ public class KMLTrackExporter implements TrackExporter {
private final List<Float> powerList = new ArrayList<>(); private final List<Float> powerList = new ArrayList<>();
private final List<Float> cadenceList = new ArrayList<>(); private final List<Float> cadenceList = new ArrayList<>();
private final List<Float> heartRateList = new ArrayList<>(); private final List<Float> heartRateList = new ArrayList<>();
private final List<Float> altitudeGainList = new ArrayList<>(); private final List<Float> altitudeGainList = new ArrayList<>(); //TODO Join with altitudeLossList
private final List<Float> altitudeLossList = new ArrayList<>(); private final List<Float> altitudeLossList = new ArrayList<>();
private final List<Float> accuracyHorizontal = new ArrayList<>(); private final List<Float> accuracyHorizontal = new ArrayList<>();
private final List<Float> accuracyVertical = new ArrayList<>(); private final List<Float> accuracyVertical = new ArrayList<>();
@@ -167,7 +167,7 @@ public class KMLTrackExporter implements TrackExporter {
wroteTrack = true; wroteTrack = true;
} }
switch (trackPoint.getType()) { switch (trackPoint.type()) {
case SEGMENT_START_MANUAL, SEGMENT_START_AUTOMATIC -> { case SEGMENT_START_MANUAL, SEGMENT_START_AUTOMATIC -> {
if (wroteSegment) writeCloseSegment(); if (wroteSegment) writeCloseSegment();
writeOpenSegment(); writeOpenSegment();
@@ -190,7 +190,7 @@ public class KMLTrackExporter implements TrackExporter {
writeTrackPoint(track.zoneOffset(), trackPoint); writeTrackPoint(track.zoneOffset(), trackPoint);
} }
default -> default ->
throw new RuntimeException("Exporting this TrackPoint type is not implemented: " + trackPoint.getType()); throw new RuntimeException("Exporting this TrackPoint type is not implemented: " + trackPoint.type());
} }
} }
@@ -370,28 +370,28 @@ public class KMLTrackExporter implements TrackExporter {
void writeTrackPoint(ZoneOffset zoneOffset, TrackPoint trackPoint) { void writeTrackPoint(ZoneOffset zoneOffset, TrackPoint trackPoint) {
printWriter.println("<when>" + getTime(zoneOffset, trackPoint.getTime()) + "</when>"); printWriter.println("<when>" + getTime(zoneOffset, trackPoint.getTime()) + "</when>");
trackpointTypeList.add(trackPoint.getType()); trackpointTypeList.add(trackPoint.type());
if (trackPoint.hasLocation()) { if (trackPoint.position().hasLocation()) {
printWriter.println("<coord>" + getCoordinates(trackPoint.getPosition(), " ") + "</coord>"); printWriter.println("<coord>" + getCoordinates(trackPoint.position(), " ") + "</coord>");
} else { } else {
printWriter.println("<coord/>"); printWriter.println("<coord/>");
} }
speedList.add(trackPoint.hasSpeed() ? (float) trackPoint.getSpeed().toMPS() : null); speedList.add(trackPoint.position().hasSpeed() ? (float) trackPoint.position().speed().toMPS() : null);
distanceList.add(trackPoint.hasSensorDistance() ? (float) trackPoint.getSensorDistance().toM() : null); distanceList.add(trackPoint.sensorDistance() != null ? (float) trackPoint.sensorDistance().toM() : null);
heartRateList.add(trackPoint.hasHeartRate() ? trackPoint.getHeartRate().getBPM() : null); heartRateList.add(trackPoint.heartRate() != null ? trackPoint.heartRate().getBPM() : null);
cadenceList.add(trackPoint.hasCadence() ? trackPoint.getCadence().getRPM() : null); cadenceList.add(trackPoint.cadence() != null ? trackPoint.cadence().getRPM() : null);
powerList.add(trackPoint.hasPower() ? trackPoint.getPower().getW() : null); powerList.add(trackPoint.power() != null ? trackPoint.power().getW() : null);
if (trackPoint.hasAltitudeGainLoss()) { if (trackPoint.altitudeGainLoss() != null) {
altitudeGainList.add(trackPoint.getAltitudeGainLoss().loss_m()); altitudeGainList.add(trackPoint.altitudeGainLoss().loss_m());
altitudeLossList.add(trackPoint.getAltitudeGainLoss().loss_m()); altitudeLossList.add(trackPoint.altitudeGainLoss().loss_m());
} else { } else {
altitudeGainList.add(null); altitudeGainList.add(null);
altitudeLossList.add(null); altitudeLossList.add(null);
} }
accuracyHorizontal.add(trackPoint.hasHorizontalAccuracy() ? (float) trackPoint.getHorizontalAccuracy().toM() : null); accuracyHorizontal.add(trackPoint.position().hasHorizontalAccuracy() ? (float) trackPoint.position().horizontalAccuracy().toM() : null);
accuracyVertical.add(trackPoint.hasVerticalAccuracy() ? (float) trackPoint.getVerticalAccuracy().toM() : null); accuracyVertical.add(trackPoint.position().hasVerticalAccuracy() ? (float) trackPoint.position().verticalAccuracy().toM() : null);
} }
private void writeSimpleArraySensorData(List<Float> list, String name) { private void writeSimpleArraySensorData(List<Float> list, String name) {
@@ -32,9 +32,13 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import de.dennisguse.opentracks.data.models.Altitude; import de.dennisguse.opentracks.data.models.Altitude;
import de.dennisguse.opentracks.data.models.AltitudeGainLoss;
import de.dennisguse.opentracks.data.models.Cadence;
import de.dennisguse.opentracks.data.models.Distance; import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Marker; import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.models.Position; import de.dennisguse.opentracks.data.models.Position;
import de.dennisguse.opentracks.data.models.Power;
import de.dennisguse.opentracks.data.models.Speed; import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint; import de.dennisguse.opentracks.data.models.TrackPoint;
@@ -265,8 +269,8 @@ public class GPXTrackImporter extends DefaultHandler implements XMLImporter.Trac
return; return;
} }
TrackPoint first = currentSegment.getFirst(); TrackPoint first = currentSegment.removeFirst();
first.setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC); currentSegment.addFirst(first.with(TrackPoint.Type.SEGMENT_START_AUTOMATIC));
trackImporter.addTrackPoints(currentSegment); trackImporter.addTrackPoints(currentSegment);
currentSegment.clear(); currentSegment.clear();
@@ -332,59 +336,70 @@ public class GPXTrackImporter extends DefaultHandler implements XMLImporter.Trac
} }
} }
TrackPoint trackPoint = new TrackPoint( Distance parsedSensorDistance = null;
TrackPoint.Type.TRACKPOINT,
new Position(
parsedTime.toInstant(),
latitudeParsed,
longitudeParsed,
accuracyHorizontalParsed,
altitudeParsed,
accuracyVerticalParsed,
null,
speedParsed
));
if (heartrate != null) {
try {
trackPoint.setHeartRate(Float.parseFloat(heartrate));
} catch (NumberFormatException e) {
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse heart rate: %s", heartrate)), e);
}
}
if (cadence != null) {
try {
trackPoint.setCadence(Float.parseFloat(cadence));
} catch (Exception e) {
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse cadence: %s", cadence)), e);
}
}
if (power != null) {
try {
trackPoint.setPower(Float.parseFloat(power));
} catch (NumberFormatException e) {
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse power: %s", power)), e);
}
}
if (gain != null && loss != null) {
try {
trackPoint.setAltitudeGainLoss(Float.parseFloat(gain), Float.parseFloat(loss));
} catch (NumberFormatException e) {
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse altitude gain: %s or los: %s", gain, loss)), e);
}
}
if (sensorDistance != null) { if (sensorDistance != null) {
try { try {
trackPoint.setSensorDistance(Distance.of(Float.parseFloat(sensorDistance))); parsedSensorDistance = Distance.of(Float.parseFloat(sensorDistance));
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse distance: %s", sensorDistance)), e); throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse distance: %s", sensorDistance)), e);
} }
} }
return trackPoint; HeartRate parsedHeartRate = null;
if (heartrate != null) {
try {
parsedHeartRate = HeartRate.of(Float.parseFloat(heartrate));
} catch (NumberFormatException e) {
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse heart rate: %s", heartrate)), e);
}
}
Cadence parsedCadence = null;
if (cadence != null) {
try {
parsedCadence = Cadence.of(Float.parseFloat(cadence));
} catch (Exception e) {
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse cadence: %s", cadence)), e);
}
}
Power parsedPower = null;
if (power != null) {
try {
parsedPower = Power.of(Float.parseFloat(power));
} catch (NumberFormatException e) {
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse power: %s", power)), e);
}
}
AltitudeGainLoss altitudeGainLoss = null;
if (gain != null && loss != null) {
try {
altitudeGainLoss = new AltitudeGainLoss(Float.parseFloat(gain), Float.parseFloat(loss));
} catch (NumberFormatException e) {
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse altitude gain: %s or los: %s", gain, loss)), e);
}
}
return new TrackPoint(
null,
TrackPoint.Type.TRACKPOINT,
new Position(
parsedTime.toInstant(),
latitudeParsed,
longitudeParsed,
accuracyHorizontalParsed,
altitudeParsed,
accuracyVerticalParsed,
null,
speedParsed
),
parsedSensorDistance,
parsedHeartRate,
parsedCadence,
parsedPower,
altitudeGainLoss
);
} }
private void onTrackPointStart(Attributes attributes) { private void onTrackPointStart(Attributes attributes) {
@@ -425,8 +440,8 @@ public class GPXTrackImporter extends DefaultHandler implements XMLImporter.Trac
TrackPoint trackPoint = createTrackPoint(); TrackPoint trackPoint = createTrackPoint();
if (!trackPoint.hasLocation()) { if (!trackPoint.position().hasLocation()) {
Log.w(TAG, "Marker with invalid coordinates ignored: " + trackPoint.getPosition()); Log.w(TAG, "Marker with invalid coordinates ignored: " + trackPoint.position());
return; return;
} }
markers.add(new Marker( markers.add(new Marker(
@@ -435,7 +450,7 @@ public class GPXTrackImporter extends DefaultHandler implements XMLImporter.Trac
name != null ? name : "", name != null ? name : "",
description != null ? description : "", description != null ? description : "",
markerType != null ? markerType : "", markerType != null ? markerType : "",
trackPoint.getPosition(), trackPoint.position(),
photoUrl photoUrl
)); ));
} }
@@ -33,9 +33,13 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import de.dennisguse.opentracks.data.models.AltitudeGainLoss;
import de.dennisguse.opentracks.data.models.Cadence;
import de.dennisguse.opentracks.data.models.Distance; import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Marker; import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.models.Position; import de.dennisguse.opentracks.data.models.Position;
import de.dennisguse.opentracks.data.models.Power;
import de.dennisguse.opentracks.data.models.Speed; import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint; import de.dennisguse.opentracks.data.models.TrackPoint;
@@ -95,10 +99,11 @@ public class KMLTrackImporter extends DefaultHandler implements XMLImporter.Trac
private ZoneOffset zoneOffset; private ZoneOffset zoneOffset;
private final ArrayList<Instant> whenList = new ArrayList<>(); private final ArrayList<Instant> whenList = new ArrayList<>();
private final ArrayList<Position> positionList = new ArrayList<>(); private final ArrayList<Position> positionList = new ArrayList<>(); //TODO This is only latitude, longitude, and altitude
private String dataType; private String dataType;
//TODO For ArrayList use specific data models; and why Float? Double would be easier.
private final ArrayList<String> trackpointTypeList = new ArrayList<>(); private final ArrayList<String> trackpointTypeList = new ArrayList<>();
private final ArrayList<Float> sensorSpeedList = new ArrayList<>(); private final ArrayList<Float> sensorSpeedList = new ArrayList<>();
private final ArrayList<Float> sensorDistanceList = new ArrayList<>(); private final ArrayList<Float> sensorDistanceList = new ArrayList<>();
@@ -151,8 +156,7 @@ public class KMLTrackImporter extends DefaultHandler implements XMLImporter.Trac
} }
onTrackSegmentStart(); onTrackSegmentStart();
} }
case TAG_EXTENDED_DATA, TAG_SIMPLE_ARRAY_DATA, TAG_KML22_SIMPLE_ARRAY_DATA -> case TAG_EXTENDED_DATA, TAG_SIMPLE_ARRAY_DATA, TAG_KML22_SIMPLE_ARRAY_DATA -> dataType = attributes.getValue(ATTRIBUTE_NAME);
dataType = attributes.getValue(ATTRIBUTE_NAME);
} }
} }
@@ -317,62 +321,77 @@ public class KMLTrackImporter extends DefaultHandler implements XMLImporter.Trac
for (int i = 0; i < positionList.size(); i++) { for (int i = 0; i < positionList.size(); i++) {
Instant time = whenList.get(i); Instant time = whenList.get(i);
Position position = positionList.get(i); Position position = positionList.get(i);
TrackPoint trackPoint;
if (position == null) { if (position == null) {
trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Position.of(time)); position = Position.of(time);
} else { } else {
trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, position.with(time)); position = position.with(time);
} }
TrackPoint.Type type = null;
Speed speed = null;
Distance sensorDistance = null;
HeartRate heartRate = null;
Cadence cadence = null;
Power power = null;
AltitudeGainLoss altitudeGainLoss = null;
Distance horizontalAccuracy = null;
Distance verticalAccuracy = null;
if (i < trackpointTypeList.size() && trackpointTypeList.get(i) != null) { if (i < trackpointTypeList.size() && trackpointTypeList.get(i) != null) {
TrackPoint.Type type = TrackPoint.Type.valueOf(trackpointTypeList.get(i)); type = TrackPoint.Type.valueOf(trackpointTypeList.get(i));
trackPoint.setType(type);
} }
if (i < sensorSpeedList.size() && sensorSpeedList.get(i) != null) { if (i < sensorSpeedList.size() && sensorSpeedList.get(i) != null) {
trackPoint.setSpeed(Speed.of(sensorSpeedList.get(i))); speed = Speed.of(sensorSpeedList.get(i));
} }
if (i < sensorDistanceList.size() && sensorDistanceList.get(i) != null) { if (i < sensorDistanceList.size() && sensorDistanceList.get(i) != null) {
trackPoint.setSensorDistance(Distance.of(sensorDistanceList.get(i))); sensorDistance = Distance.of(sensorDistanceList.get(i));
} }
if (i < sensorHeartRateList.size() && sensorHeartRateList.get(i) != null) { if (i < sensorHeartRateList.size()) {
trackPoint.setHeartRate(sensorHeartRateList.get(i)); heartRate = HeartRate.ofOrNull(sensorHeartRateList.get(i));
} }
if (i < sensorCadenceList.size() && sensorCadenceList.get(i) != null) { if (i < sensorCadenceList.size() && sensorCadenceList.get(i) != null) {
trackPoint.setCadence(sensorCadenceList.get(i)); cadence = Cadence.of(sensorCadenceList.get(i));
} }
if (i < sensorPowerList.size() && sensorPowerList.get(i) != null) { if (i < sensorPowerList.size() && sensorPowerList.get(i) != null) {
trackPoint.setPower(sensorPowerList.get(i)); power = Power.of(sensorPowerList.get(i));
} }
if (i < altitudeGainList.size() && i < altitudeLossList.size()) { if ((i < altitudeGainList.size() && altitudeGainList.get(i) != null)
if (altitudeGainList.get(i) != null && altitudeLossList.get(i) != null) { && (i < altitudeLossList.size() && altitudeLossList.get(i) != null)) {
trackPoint.setAltitudeGainLoss(altitudeGainList.get(i), altitudeLossList.get(i)); altitudeGainLoss = new AltitudeGainLoss(altitudeGainList.get(i), altitudeLossList.get(i));
}
} }
if (i < accuracyHorizontal.size() && accuracyHorizontal.get(i) != null) { if (i < accuracyHorizontal.size() && accuracyHorizontal.get(i) != null) {
trackPoint.setHorizontalAccuracy(Distance.of(accuracyHorizontal.get(i))); horizontalAccuracy = Distance.of(accuracyHorizontal.get(i));
} }
if (i < accuracyVertical.size() && accuracyVertical.get(i) != null) { if (i < accuracyVertical.size() && accuracyVertical.get(i) != null) {
trackPoint.setVerticalAccuracy(Distance.of(accuracyVertical.get(i))); verticalAccuracy = Distance.of(accuracyVertical.get(i));
} }
// Update TrackPoint type for START / STOP. // Update TrackPoint type for START / STOP.
TrackPoint.Type type = trackPoint.getType();
if (i == 0) { if (i == 0) {
//first //first
if (!trackPoint.wasCreatedManually()) { if (wasCreatedAutomatically(position)) {
type = TrackPoint.Type.SEGMENT_START_MANUAL;
} else {
type = TrackPoint.Type.SEGMENT_START_AUTOMATIC; type = TrackPoint.Type.SEGMENT_START_AUTOMATIC;
} else {
type = TrackPoint.Type.SEGMENT_START_MANUAL;
} }
} else if (i == positionList.size() - 1 && !trackPoint.wasCreatedManually()) { } else if (i == positionList.size() - 1 && !wasCreatedAutomatically(position)) {
//last //last
type = TrackPoint.Type.SEGMENT_END_MANUAL; type = TrackPoint.Type.SEGMENT_END_MANUAL;
} }
trackPoint.setType(type);
trackImporter.addTrackPoint(trackPoint); trackImporter.addTrackPoint(new TrackPoint(
null,
type != null ? type : TrackPoint.Type.TRACKPOINT,
position
.with(speed)
.withHorizontalAccuracy(horizontalAccuracy)
.withVerticalAccuracy(verticalAccuracy),
sensorDistance,
heartRate,
cadence,
power,
altitudeGainLoss
));
} }
} }
@@ -392,6 +411,7 @@ public class KMLTrackImporter extends DefaultHandler implements XMLImporter.Trac
} }
private Position createPosition(String latitude, String longitude, String altitude) { private Position createPosition(String latitude, String longitude, String altitude) {
//TODO Do not use Location.
if (longitude != null && latitude != null) { if (longitude != null && latitude != null) {
Location location = new Location("import"); Location location = new Location("import");
try { try {
@@ -439,12 +459,9 @@ public class KMLTrackImporter extends DefaultHandler implements XMLImporter.Trac
case KMLTrackExporter.EXTENDED_DATA_TYPE_CADENCE -> sensorCadenceList.add(value); case KMLTrackExporter.EXTENDED_DATA_TYPE_CADENCE -> sensorCadenceList.add(value);
case KMLTrackExporter.EXTENDED_DATA_TYPE_ALTITUDE_GAIN -> altitudeGainList.add(value); case KMLTrackExporter.EXTENDED_DATA_TYPE_ALTITUDE_GAIN -> altitudeGainList.add(value);
case KMLTrackExporter.EXTENDED_DATA_TYPE_ALTITUDE_LOSS -> altitudeLossList.add(value); case KMLTrackExporter.EXTENDED_DATA_TYPE_ALTITUDE_LOSS -> altitudeLossList.add(value);
case KMLTrackExporter.EXTENDED_DATA_TYPE_ACCURACY_HORIZONTAL -> case KMLTrackExporter.EXTENDED_DATA_TYPE_ACCURACY_HORIZONTAL -> accuracyHorizontal.add(value);
accuracyHorizontal.add(value); case KMLTrackExporter.EXTENDED_DATA_TYPE_ACCURACY_VERTICAL -> accuracyVertical.add(value);
case KMLTrackExporter.EXTENDED_DATA_TYPE_ACCURACY_VERTICAL -> default -> Log.w(TAG, "Data from extended data " + dataType + " is not (yet) supported.");
accuracyVertical.add(value);
default ->
Log.w(TAG, "Data from extended data " + dataType + " is not (yet) supported.");
} }
} }
@@ -457,6 +474,10 @@ public class KMLTrackImporter extends DefaultHandler implements XMLImporter.Trac
trackImporter.finish(); trackImporter.finish();
} }
public boolean wasCreatedAutomatically(Position position) {
return position.hasLocation() || position.hasSpeed();
}
@Override @Override
public DefaultHandler getHandler() { public DefaultHandler getHandler() {
return this; return this;
@@ -176,21 +176,19 @@ public class TrackImporter {
TrackPoint previous = trackPoints.get(i - 1); TrackPoint previous = trackPoints.get(i - 1);
TrackPoint current = trackPoints.get(i); TrackPoint current = trackPoints.get(i);
if (current.hasSensorDistance() || (previous.hasLocation() && current.hasLocation())) { if (current.sensorDistance() != null || (previous.position().hasLocation() && current.position().hasLocation())) {
TrackPoint newCurrent = current;
Distance distanceToPrevious = current.distanceToPrevious(previous); Distance distanceToPrevious = current.distanceToPrevious(previous);
if (!current.hasSpeed()) { if (!current.position().hasSpeed()) {
Duration timeDifference = Duration.between(previous.getTime(), current.getTime()); Duration timeDifference = Duration.between(previous.getTime(), current.getTime());
current.setSpeed(Speed.of(distanceToPrevious, timeDifference)); newCurrent = newCurrent.with(newCurrent.position().with(Speed.of(distanceToPrevious, timeDifference)));
} }
if (!current.hasBearing()) { if (current.type().equals(TrackPoint.Type.TRACKPOINT) && distanceToPrevious.greaterThan(maxRecordingDistance)) {
previous.bearingTo(current) newCurrent = newCurrent.with(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
.ifPresent(current::setBearing);
}
if (current.getType().equals(TrackPoint.Type.TRACKPOINT) && distanceToPrevious.greaterThan(maxRecordingDistance)) {
current.setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
} }
trackPoints.set(i, newCurrent);
} }
} }
} }
@@ -124,20 +124,20 @@ public class GpsManager implements SensorConnector, LocationListenerCompat, GpsS
@Override @Override
public void onLocationChanged(@NonNull Location location) { public void onLocationChanged(@NonNull Location location) {
// Send each update to the status; please note that this TrackPoint is not stored. // Send each update to the status; please note that this TrackPoint is not stored.
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Position.of(location, trackPointCreator.createNow())); Position position = Position.of(location, trackPointCreator.createNow());
gpsStatusManager.onNewTrackPoint(trackPoint); gpsStatusManager.onNewTrackPoint(new TrackPoint(TrackPoint.Type.TRACKPOINT, position));
if (!trackPoint.getPosition().hasValidLocation()) { if (!position.hasValidLocation()) {
Log.w(TAG, "Ignore newTrackPoint. location is invalid."); Log.w(TAG, "Ignore newTrackPoint. Location is invalid.");
return; return;
} }
if (!trackPoint.getPosition().fulfillsAccuracy(thresholdHorizontalAccuracy)) { if (!position.fulfillsAccuracy(thresholdHorizontalAccuracy)) {
Log.d(TAG, "Ignore newTrackPoint. Poor accuracy."); Log.d(TAG, "Ignore newTrackPoint. Poor accuracy.");
return; return;
} }
observer.onChange(new Raw<>(observer.getNow(), Position.of(location))); observer.onChange(new Raw<>(observer.getNow(), position));
} }
@Override @Override
@@ -18,6 +18,7 @@ import de.dennisguse.opentracks.settings.PreferencesUtils;
/** /**
* This class handle GPS status according to received locations and some thresholds. * This class handle GPS status according to received locations and some thresholds.
*/ */
//TODO Use Position instead of TrackPoint.
class GpsStatusManager { class GpsStatusManager {
private static final String TAG = GpsStatusManager.class.getSimpleName(); private static final String TAG = GpsStatusManager.class.getSimpleName();
@@ -100,7 +101,7 @@ class GpsStatusManager {
//TODO use MonotonicClock instead of Instant.now() //TODO use MonotonicClock instead of Instant.now()
@VisibleForTesting @VisibleForTesting
void determineGpsStatusOnTrackpoint(@NonNull TrackPoint lastTrackPoint) { void determineGpsStatusOnTrackpoint(@NonNull TrackPoint lastTrackPoint) {
if (lastTrackPoint.fulfillsAccuracy(horizontalAccuracyThreshold)) { if (lastTrackPoint.position().fulfillsAccuracy(horizontalAccuracyThreshold)) {
if (gpsStatus != GpsStatusValue.GPS_SIGNAL_FIX) { if (gpsStatus != GpsStatusValue.GPS_SIGNAL_FIX) {
setGpsStatus(GpsStatusValue.GPS_SIGNAL_FIX); setGpsStatus(GpsStatusValue.GPS_SIGNAL_FIX);
scheduleTimer(); //TODO scheduleTimer(); //TODO
@@ -11,9 +11,9 @@ import androidx.annotation.VisibleForTesting;
import java.time.Instant; import java.time.Instant;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.sensors.sensorData.Aggregator; import de.dennisguse.opentracks.sensors.sensorData.Aggregator;
import de.dennisguse.opentracks.sensors.sensorData.Raw; import de.dennisguse.opentracks.sensors.sensorData.Raw;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataAggregator;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet; import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
import de.dennisguse.opentracks.services.handlers.TrackPointCreator; import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
import de.dennisguse.opentracks.util.SystemUtils; import de.dennisguse.opentracks.util.SystemUtils;
@@ -24,7 +24,7 @@ public class SensorManager implements SharedPreferences.OnSharedPreferenceChange
//TODO Should be final and not be visible for testing //TODO Should be final and not be visible for testing
@VisibleForTesting @VisibleForTesting
public SensorDataSet sensorDataSet; public SensorDataAggregator sensorDataAggregator;
private PowerManager.WakeLock wakeLock; private PowerManager.WakeLock wakeLock;
@@ -34,23 +34,23 @@ public class SensorManager implements SharedPreferences.OnSharedPreferenceChange
@Override @Override
public void onConnect(Aggregator<?, ?> aggregator) { public void onConnect(Aggregator<?, ?> aggregator) {
sensorDataSet.add(aggregator); sensorDataAggregator.add(aggregator);
} }
@Override @Override
public void onChange(Raw<?> data) { public void onChange(Raw<?> data) {
sensorDataSet.update(data); sensorDataAggregator.update(data);
observer.onChange(new SensorDataSet(sensorDataSet)); observer.onChange();
} }
@Override @Override
public void onDisconnect(Aggregator<?, ?> aggregator) { public void onDisconnect(Aggregator<?, ?> aggregator) {
sensorDataSet.add(aggregator); sensorDataAggregator.add(aggregator);
} }
@Override @Override
public void onRemove(Aggregator<?, ?> aggregator) { public void onRemove(Aggregator<?, ?> aggregator) {
sensorDataSet.remove(aggregator); sensorDataAggregator.remove(aggregator);
} }
@Override @Override
@@ -67,7 +67,7 @@ public class SensorManager implements SharedPreferences.OnSharedPreferenceChange
public SensorManager(TrackPointCreator observer) { public SensorManager(TrackPointCreator observer) {
this.observer = observer; this.observer = observer;
this.sensorDataSet = new SensorDataSet(observer); this.sensorDataAggregator = new SensorDataAggregator();
} }
public void start(Context context, Handler handler) { public void start(Context context, Handler handler) {
@@ -100,12 +100,11 @@ public class SensorManager implements SharedPreferences.OnSharedPreferenceChange
gpsManager.stop(context); gpsManager.stop(context);
gpsManager = null; gpsManager = null;
sensorDataSet.clear(); sensorDataAggregator.clear();
} }
public SensorDataSet fill(TrackPoint trackPoint) { public SensorDataSet getSensorDataSet(Instant now) {
sensorDataSet.fillTrackPoint(trackPoint); return sensorDataAggregator.getSensorData(now);
return new SensorDataSet(sensorDataSet);
} }
public void reset() { public void reset() {
@@ -113,7 +112,7 @@ public class SensorManager implements SharedPreferences.OnSharedPreferenceChange
Log.d(TAG, "No recording running and no reset necessary."); Log.d(TAG, "No recording running and no reset necessary.");
return; return;
} }
sensorDataSet.reset(); sensorDataAggregator.reset();
} }
@VisibleForTesting @VisibleForTesting
@@ -1,7 +1,5 @@
package de.dennisguse.opentracks.sensors.sensorData; package de.dennisguse.opentracks.sensors.sensorData;
import android.util.Pair;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import java.time.Instant; import java.time.Instant;
@@ -61,8 +59,8 @@ public abstract class Aggregator<Input, Output> {
} }
@NonNull @NonNull
public Pair<Output, String> getAggregatedValueWithSensorName(Instant now) { public SensorData<Output> getAggregatedValueWithSensorName(Instant now) {
return new Pair<>(getAggregatedValue(now), getSensorNameOrAddress()); return new SensorData<>(getAggregatedValue(now), getSensorNameOrAddress());
} }
/** /**
@@ -2,6 +2,8 @@ package de.dennisguse.opentracks.sensors.sensorData;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import java.time.Instant;
import de.dennisguse.opentracks.data.models.Position; import de.dennisguse.opentracks.data.models.Position;
public class AggregatorGPS extends Aggregator<Position, Position> { public class AggregatorGPS extends Aggregator<Position, Position> {
@@ -36,4 +38,11 @@ public class AggregatorGPS extends Aggregator<Position, Position> {
protected Position getNoneValue() { protected Position getNoneValue() {
return Position.empty(); return Position.empty();
} }
@NonNull
@Override
public Position getAggregatedValue(Instant now) {
return super.getAggregatedValue(now)
.with(now);
}
} }
@@ -0,0 +1,9 @@
package de.dennisguse.opentracks.sensors.sensorData;
public record SensorData<T>(
T data,
String sensorNameOrAddress
) {
}
@@ -0,0 +1,212 @@
package de.dennisguse.opentracks.sensors.sensorData;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import java.time.Instant;
import de.dennisguse.opentracks.data.models.AtmosphericPressure;
import de.dennisguse.opentracks.data.models.Cadence;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Position;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingCadence;
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingDistanceSpeed;
import de.dennisguse.opentracks.sensors.BluetoothHandlerManagerCyclingPower;
import de.dennisguse.opentracks.sensors.BluetoothHandlerRunningSpeedAndCadence;
import de.dennisguse.opentracks.settings.PreferencesUtils;
public class SensorDataAggregator {
private static final String TAG = SensorDataAggregator.class.getSimpleName();
@VisibleForTesting
public AggregatorHeartRate heartRate;
@VisibleForTesting
public AggregatorCyclingCadence cyclingCadence;
@VisibleForTesting
public AggregatorCyclingDistanceSpeed cyclingDistanceSpeed;
@VisibleForTesting
public AggregatorCyclingPower cyclingPower;
@VisibleForTesting
public AggregatorRunning runningDistanceSpeedCadence;
@VisibleForTesting
public AggregatorBarometer barometer;
public AggregatorGPS gps;
@VisibleForTesting
public SensorData<HeartRate> getHeartRate(Instant now) {
if (heartRate != null) {
SensorData<HeartRate> value = heartRate.getAggregatedValueWithSensorName(now);
if (value.data().isValid()) return value;
}
return null;
}
private SensorData<Cadence> getCadence(Instant now) {
if (cyclingCadence != null) {
return cyclingCadence.getAggregatedValueWithSensorName(now);
}
if (runningDistanceSpeedCadence != null && runningDistanceSpeedCadence.hasReceivedData() && runningDistanceSpeedCadence.getAggregatedValue(now).cadence() != null) {
return new SensorData<>(runningDistanceSpeedCadence.aggregatedValue.cadence(), runningDistanceSpeedCadence.getSensorNameOrAddress());
}
return null;
}
private SensorData<Distance> getDistance(Instant now) {
if (cyclingDistanceSpeed != null && cyclingDistanceSpeed.hasReceivedData()) {
return new SensorData<>(cyclingDistanceSpeed.getAggregatedValue(now).distanceOverall(), cyclingDistanceSpeed.getSensorNameOrAddress());
}
if (runningDistanceSpeedCadence != null && runningDistanceSpeedCadence.hasReceivedData()) {
return new SensorData<>(runningDistanceSpeedCadence.getAggregatedValue(now).distance(), runningDistanceSpeedCadence.getSensorNameOrAddress());
}
return null;
}
//TOOD simplify?
private SensorData<Speed> getSpeed(Instant now) {
if (cyclingDistanceSpeed != null && cyclingDistanceSpeed.hasReceivedData() && cyclingDistanceSpeed.getAggregatedValue(now).speed() != null) {
return new SensorData<>(cyclingDistanceSpeed.getAggregatedValue(now).speed(), cyclingDistanceSpeed.getSensorNameOrAddress());
}
if (runningDistanceSpeedCadence != null && runningDistanceSpeedCadence.hasReceivedData() && runningDistanceSpeedCadence.getAggregatedValue(now).speed() != null) {
return new SensorData<>(runningDistanceSpeedCadence.aggregatedValue.speed(), runningDistanceSpeedCadence.getSensorNameOrAddress());
}
return null;
}
public void add(@NonNull Aggregator<?, ?> data) {
set(data, data);
}
public void update(@NonNull Raw<?> data) {
Object value = data.value();
if (value instanceof HeartRate) {
this.heartRate.add((Raw<HeartRate>) data);
return;
}
if (value instanceof BluetoothHandlerCyclingCadence.CrankData) {
this.cyclingCadence.add((Raw<BluetoothHandlerCyclingCadence.CrankData>) data);
return;
}
if (value instanceof BluetoothHandlerCyclingDistanceSpeed.WheelData) {
this.cyclingDistanceSpeed.setWheelCircumference(PreferencesUtils.getWheelCircumference()); //TODO Fetch once and then listen for changes.
this.cyclingDistanceSpeed.add((Raw<BluetoothHandlerCyclingDistanceSpeed.WheelData>) data);
return;
}
if (value instanceof BluetoothHandlerRunningSpeedAndCadence.Data) {
this.runningDistanceSpeedCadence.add((Raw<BluetoothHandlerRunningSpeedAndCadence.Data>) data);
return;
}
if (value instanceof BluetoothHandlerManagerCyclingPower.Data) {
this.cyclingPower.add((Raw<BluetoothHandlerManagerCyclingPower.Data>) data);
return;
}
if (value instanceof AtmosphericPressure) {
this.barometer.add((Raw<AtmosphericPressure>) data);
return;
}
if (value instanceof Position) {
this.gps.add((Raw<Position>) data);
return;
}
throw new UnsupportedOperationException(data.getClass().getCanonicalName() + " " + data.value().getClass().getCanonicalName());
}
public void remove(@NonNull Aggregator<?, ?> type) {
set(type, null);
}
public void clear() {
Log.i(TAG, "Removing all aggregators");
this.heartRate = null;
this.cyclingCadence = null;
this.cyclingDistanceSpeed = null;
this.cyclingPower = null;
this.runningDistanceSpeedCadence = null;
this.barometer = null;
this.gps = null;
}
public SensorDataSet getSensorData(Instant now) {
return new SensorDataSet(
gps != null && gps.hasReceivedData() ? gps.getAggregatedValueWithSensorName(now) : new SensorData<>(Position.of(now), ""),
getSpeed(now),
getDistance(now),
getHeartRate(now),
getCadence(now),
cyclingPower != null && cyclingPower.hasReceivedData() ? cyclingPower.getAggregatedValueWithSensorName(now) : null,
barometer != null && barometer.hasReceivedData() ? barometer.getAggregatedValueWithSensorName(now) : null
);
}
public void reset() {
Log.i(TAG, "Resetting data");
if (heartRate != null) heartRate.resetAggregated();
if (cyclingCadence != null) cyclingCadence.resetAggregated();
if (cyclingDistanceSpeed != null) cyclingDistanceSpeed.resetAggregated();
if (cyclingPower != null) cyclingPower.resetAggregated();
if (runningDistanceSpeedCadence != null) runningDistanceSpeedCadence.resetAggregated();
if (barometer != null) barometer.resetAggregated();
if (gps != null) gps.resetAggregated();
}
//TODO Refactor generic "fun" here: go explicit.
private void set(@NonNull Aggregator<?, ?> type, @Nullable Aggregator<?, ?> sensorData) {
Log.i(TAG, "Setting aggregator " + type.getClass().getCanonicalName() + " to " + sensorData);
if (type instanceof AggregatorHeartRate) {
heartRate = (AggregatorHeartRate) sensorData;
return;
}
if (type instanceof AggregatorCyclingCadence) {
cyclingCadence = (AggregatorCyclingCadence) sensorData;
return;
}
if (type instanceof AggregatorCyclingDistanceSpeed) {
cyclingDistanceSpeed = (AggregatorCyclingDistanceSpeed) sensorData;
return;
}
if (type instanceof AggregatorCyclingPower) {
cyclingPower = (AggregatorCyclingPower) sensorData;
return;
}
if (type instanceof AggregatorRunning) {
runningDistanceSpeedCadence = (AggregatorRunning) sensorData;
return;
}
if (type instanceof AggregatorBarometer) {
barometer = (AggregatorBarometer) sensorData;
return;
}
if (type instanceof AggregatorGPS) {
gps = (AggregatorGPS) sensorData;
return;
}
throw new UnsupportedOperationException(type.getClass().getCanonicalName());
}
}
@@ -1,240 +1,30 @@
package de.dennisguse.opentracks.sensors.sensorData; package de.dennisguse.opentracks.sensors.sensorData;
import android.util.Log;
import android.util.Pair;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.data.models.AtmosphericPressure; import de.dennisguse.opentracks.data.models.AltitudeGainLoss;
import de.dennisguse.opentracks.data.models.Cadence; import de.dennisguse.opentracks.data.models.Cadence;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate; import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Position; import de.dennisguse.opentracks.data.models.Position;
import de.dennisguse.opentracks.data.models.Power;
import de.dennisguse.opentracks.data.models.Speed; import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingCadence;
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingDistanceSpeed;
import de.dennisguse.opentracks.sensors.BluetoothHandlerManagerCyclingPower;
import de.dennisguse.opentracks.sensors.BluetoothHandlerRunningSpeedAndCadence;
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
import de.dennisguse.opentracks.settings.PreferencesUtils;
public class SensorDataSet { public record SensorDataSet(
@NonNull
private static final String TAG = SensorDataSet.class.getSimpleName(); SensorData<Position> position,
@Nullable
@VisibleForTesting SensorData<Speed> speed,
public AggregatorHeartRate heartRate; @Nullable
SensorData<Distance> distance,
@VisibleForTesting @Nullable
public AggregatorCyclingCadence cyclingCadence; SensorData<HeartRate> heartRate,
@Nullable
@VisibleForTesting SensorData<Cadence> cadence,
public AggregatorCyclingDistanceSpeed cyclingDistanceSpeed; @Nullable
SensorData<Power> power,
@VisibleForTesting @Nullable
public AggregatorCyclingPower cyclingPower; SensorData<AltitudeGainLoss> altitudeGainLoss
) {
@VisibleForTesting
public AggregatorRunning runningDistanceSpeedCadence;
@VisibleForTesting
public AggregatorBarometer barometer;
public AggregatorGPS gps;
private final TrackPointCreator trackPointCreator;
public SensorDataSet(TrackPointCreator trackPointCreator) {
this.trackPointCreator = trackPointCreator;
}
@Deprecated //TODO This is not a copy constructor anymore, but it should be - aggregators are no value objects; best guess: can be removed
public SensorDataSet(SensorDataSet toCopy) {
this.trackPointCreator = toCopy.trackPointCreator;
this.heartRate = toCopy.heartRate;
this.cyclingCadence = toCopy.cyclingCadence;
this.cyclingDistanceSpeed = toCopy.cyclingDistanceSpeed;
this.cyclingPower = toCopy.cyclingPower;
this.runningDistanceSpeedCadence = toCopy.runningDistanceSpeedCadence;
this.barometer = toCopy.barometer;
this.gps = toCopy.gps;
}
public Pair<HeartRate, String> getHeartRate() {
if (heartRate != null) {
Pair<HeartRate, String> value = heartRate.getAggregatedValueWithSensorName(trackPointCreator.createNow());
if (value.first.isValid()) return value;
}
return null;
}
public Pair<Cadence, String> getCadence() {
if (cyclingCadence != null) {
return cyclingCadence.getAggregatedValueWithSensorName(trackPointCreator.createNow());
}
if (runningDistanceSpeedCadence != null && runningDistanceSpeedCadence.hasReceivedData() && runningDistanceSpeedCadence.aggregatedValue.cadence() != null) {
return new Pair<>(runningDistanceSpeedCadence.aggregatedValue.cadence(), runningDistanceSpeedCadence.getSensorNameOrAddress());
}
return null;
}
public Pair<Speed, String> getSpeed() {
if (cyclingDistanceSpeed != null && cyclingDistanceSpeed.hasReceivedData() && cyclingDistanceSpeed.getAggregatedValue(trackPointCreator.createNow()).speed() != null) {
return new Pair<>(cyclingDistanceSpeed.getAggregatedValue(trackPointCreator.createNow()).speed(), cyclingDistanceSpeed.getSensorNameOrAddress());
}
if (runningDistanceSpeedCadence != null && runningDistanceSpeedCadence.hasReceivedData() && runningDistanceSpeedCadence.getAggregatedValue(trackPointCreator.createNow()).speed() != null) {
return new Pair<>(runningDistanceSpeedCadence.aggregatedValue.speed(), runningDistanceSpeedCadence.getSensorNameOrAddress());
}
return null;
}
public AggregatorCyclingPower getCyclingPower() {
return cyclingPower;
}
public void add(@NonNull Aggregator<?, ?> data) {
set(data, data);
}
public void update(@NonNull Raw<?> data) {
Object value = data.value();
if (value instanceof HeartRate) {
this.heartRate.add((Raw<HeartRate>) data);
return;
}
if (value instanceof BluetoothHandlerCyclingCadence.CrankData) {
this.cyclingCadence.add((Raw<BluetoothHandlerCyclingCadence.CrankData>) data);
return;
}
if (value instanceof BluetoothHandlerCyclingDistanceSpeed.WheelData ) {
this.cyclingDistanceSpeed.setWheelCircumference(PreferencesUtils.getWheelCircumference()); //TODO Fetch once and then listen for changes.
this.cyclingDistanceSpeed.add((Raw<BluetoothHandlerCyclingDistanceSpeed.WheelData>) data);
return;
}
if (value instanceof BluetoothHandlerRunningSpeedAndCadence.Data) {
this.runningDistanceSpeedCadence.add((Raw<BluetoothHandlerRunningSpeedAndCadence.Data>) data);
return;
}
if (value instanceof BluetoothHandlerManagerCyclingPower.Data) {
this.cyclingPower.add((Raw<BluetoothHandlerManagerCyclingPower.Data>) data);
return;
}
if (value instanceof AtmosphericPressure) {
this.barometer.add((Raw<AtmosphericPressure>) data);
return;
}
if (value instanceof Position) {
this.gps.add((Raw<Position>) data);
return;
}
throw new UnsupportedOperationException(data.getClass().getCanonicalName() + " " + data.value().getClass().getCanonicalName());
}
public void remove(@NonNull Aggregator<?, ?> type) {
set(type, null);
}
public void clear() {
Log.i(TAG, "Removing all aggregators");
this.heartRate = null;
this.cyclingCadence = null;
this.cyclingDistanceSpeed = null;
this.cyclingPower = null;
this.runningDistanceSpeedCadence = null;
this.barometer = null;
this.gps = null;
}
public void fillTrackPoint(TrackPoint trackPoint) {
if (gps != null && gps.hasReceivedData()) {
trackPoint.setPosition(gps.getAggregatedValue(trackPointCreator.createNow()));
}
Pair<HeartRate, String> heartRate = getHeartRate();
if (heartRate != null) {
trackPoint.setHeartRate(heartRate.first);
}
if (getCadence() != null) {
trackPoint.setCadence(getCadence().first);
}
if (getSpeed() != null) {
trackPoint.setSpeed(getSpeed().first);
}
if (cyclingDistanceSpeed != null && cyclingDistanceSpeed.hasReceivedData()) {
trackPoint.setSensorDistance(cyclingDistanceSpeed.getAggregatedValue(trackPointCreator.createNow()).distanceOverall());
}
if (cyclingPower != null && cyclingPower.hasReceivedData()) {
trackPoint.setPower(cyclingPower.getAggregatedValue(trackPointCreator.createNow()));
}
if (runningDistanceSpeedCadence != null && runningDistanceSpeedCadence.hasReceivedData()) {
trackPoint.setSensorDistance(runningDistanceSpeedCadence.getAggregatedValue(trackPointCreator.createNow()).distance());
}
if (barometer != null && barometer.hasReceivedData()) {
trackPoint.setAltitudeGainLoss(barometer.getAggregatedValue(trackPointCreator.createNow()).gain_m(), barometer.getAggregatedValue(trackPointCreator.createNow()).loss_m());
}
}
public void reset() {
Log.i(TAG, "Resetting data");
if (heartRate != null) heartRate.resetAggregated();
if (cyclingCadence != null) cyclingCadence.resetAggregated();
if (cyclingDistanceSpeed != null) cyclingDistanceSpeed.resetAggregated();
if (cyclingPower != null) cyclingPower.resetAggregated();
if (runningDistanceSpeedCadence != null) runningDistanceSpeedCadence.resetAggregated();
if (barometer != null) barometer.resetAggregated();
if (gps != null) gps.resetAggregated();
}
private void set(@NonNull Aggregator<?, ?> type, @Nullable Aggregator<?, ?> sensorData) {
Log.i(TAG, "Setting aggregator " + type.getClass().getCanonicalName() + " to " + sensorData);
if (type instanceof AggregatorHeartRate) {
heartRate = (AggregatorHeartRate) sensorData;
return;
}
if (type instanceof AggregatorCyclingCadence) {
cyclingCadence = (AggregatorCyclingCadence) sensorData;
return;
}
if (type instanceof AggregatorCyclingDistanceSpeed) {
cyclingDistanceSpeed = (AggregatorCyclingDistanceSpeed) sensorData;
return;
}
if (type instanceof AggregatorCyclingPower) {
cyclingPower = (AggregatorCyclingPower) sensorData;
return;
}
if (type instanceof AggregatorRunning) {
runningDistanceSpeedCadence = (AggregatorRunning) sensorData;
return;
}
if (type instanceof AggregatorBarometer) {
barometer = (AggregatorBarometer) sensorData;
return;
}
if (type instanceof AggregatorGPS) {
gps = (AggregatorGPS) sensorData;
return;
}
throw new UnsupportedOperationException(type.getClass().getCanonicalName());
}
} }
@@ -22,11 +22,11 @@ import de.dennisguse.opentracks.data.models.Statistics;
import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackBuilder; import de.dennisguse.opentracks.data.models.TrackBuilder;
import de.dennisguse.opentracks.data.models.TrackPoint; import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.data.statistics.TrackStatisticsUpdater;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet; import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
import de.dennisguse.opentracks.services.handlers.AltitudeCorrectionManager; import de.dennisguse.opentracks.services.handlers.AltitudeCorrectionManager;
import de.dennisguse.opentracks.services.handlers.TrackPointCreator; import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
import de.dennisguse.opentracks.settings.PreferencesUtils; import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.data.statistics.TrackStatisticsUpdater;
import de.dennisguse.opentracks.util.TrackNameUtils; import de.dennisguse.opentracks.util.TrackNameUtils;
public class TrackRecordingManager implements SharedPreferences.OnSharedPreferenceChangeListener { public class TrackRecordingManager implements SharedPreferences.OnSharedPreferenceChangeListener {
@@ -160,14 +160,14 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
* @return TrackPoint was stored? * @return TrackPoint was stored?
*/ */
synchronized boolean onNewTrackPoint(@NonNull TrackPoint trackPoint) { synchronized boolean onNewTrackPoint(@NonNull TrackPoint trackPoint) {
if (trackPoint.hasSpeed()) { if (trackPoint.position().hasSpeed()) {
lastTrackPointUIWithSpeed = trackPoint; lastTrackPointUIWithSpeed = trackPoint;
} }
if (trackPoint.hasAltitude()) { if (trackPoint.position().hasAltitude()) {
lastTrackPointUIWithAltitude = trackPoint; lastTrackPointUIWithAltitude = trackPoint;
} }
if (trackPoint.getType() == TrackPoint.Type.IDLE) { if (trackPoint.type() == TrackPoint.Type.IDLE) {
insertTrackPoint(trackPoint, true); insertTrackPoint(trackPoint, true);
handler.removeCallbacks(ON_IDLE); handler.removeCallbacks(ON_IDLE);
return true; return true;
@@ -180,14 +180,14 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
return true; return true;
} }
if (trackPoint.hasLocation() && lastStoredTrackPointWithLocation == null) { if (trackPoint.position().hasLocation() && lastStoredTrackPointWithLocation == null) {
insertTrackPoint(trackPoint, true); insertTrackPoint(trackPoint, true);
scheduleNewIdleTimeout(); scheduleNewIdleTimeout();
return true; return true;
} }
if (!trackPoint.hasLocation() && !trackPoint.hasSensorDistance()) { if (!trackPoint.position().hasLocation() && trackPoint.sensorDistance() == null) {
Duration minStorageInterval = Duration.ofSeconds(10); // TODO Should be configurable. Duration minStorageInterval = Duration.ofSeconds(10); // TODO Should be configurable.
boolean shouldStore = lastStoredTrackPoint.getTime().plus(minStorageInterval) boolean shouldStore = lastStoredTrackPoint.getTime().plus(minStorageInterval)
.isBefore(trackPoint.getTime()); .isBefore(trackPoint.getTime());
@@ -201,14 +201,14 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
} }
Distance distanceToLastStoredTrackPoint; Distance distanceToLastStoredTrackPoint;
if (trackPoint.hasLocation() && !lastStoredTrackPoint.hasLocation()) { if (trackPoint.position().hasLocation() && !lastStoredTrackPoint.position().hasLocation()) {
distanceToLastStoredTrackPoint = trackPoint.distanceToPreviousFromLocation(lastStoredTrackPointWithLocation); distanceToLastStoredTrackPoint = trackPoint.distanceToPreviousFromLocation(lastStoredTrackPointWithLocation);
} else { } else {
distanceToLastStoredTrackPoint = trackPoint.distanceToPrevious(lastStoredTrackPoint); distanceToLastStoredTrackPoint = trackPoint.distanceToPrevious(lastStoredTrackPoint);
} }
if (distanceToLastStoredTrackPoint.greaterThan(maxRecordingDistance)) { if (distanceToLastStoredTrackPoint.greaterThan(maxRecordingDistance)) {
trackPoint.setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC); trackPoint = trackPoint.with(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
insertTrackPoint(trackPoint, true); insertTrackPoint(trackPoint, true);
scheduleNewIdleTimeout(); scheduleNewIdleTimeout();
@@ -248,8 +248,8 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
Log.w(TAG, "Ignore insertTrackPoint. trackPoint time same as last valid trackId point time."); Log.w(TAG, "Ignore insertTrackPoint. trackPoint time same as last valid trackId point time.");
} else { } else {
insertTrackPointHelper(lastTrackPoint); insertTrackPointHelper(lastTrackPoint);
// Remove the sensorDistance from trackPoint that is already going be stored with lastTrackPoint. // Remove the sensorDistance from trackPoint that is already going be stored with lastTrackPoint.
trackPoint.minusCumulativeSensorData(lastTrackPoint); trackPoint = trackPoint.minusCumulativeSensorData(lastTrackPoint);
} }
} }
lastTrackPoint = null; lastTrackPoint = null;
@@ -264,7 +264,7 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
contentProviderUtils.updateTrackStatistics(trackId, trackStatisticsUpdater.getTrackStatistics()); contentProviderUtils.updateTrackStatistics(trackId, trackStatisticsUpdater.getTrackStatistics());
lastStoredTrackPoint = trackPoint; lastStoredTrackPoint = trackPoint;
if (trackPoint.hasLocation()) { if (trackPoint.position().hasLocation()) {
lastStoredTrackPointWithLocation = lastStoredTrackPoint; lastStoredTrackPointWithLocation = lastStoredTrackPoint;
} }
} catch (SQLiteException e) { } catch (SQLiteException e) {
@@ -255,7 +255,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
"", "",
"", "",
"", "",
trackPoint.getPosition(), trackPoint.position(),
null null
); );
return new ContentProviderUtils(this).insertMarker(marker); return new ContentProviderUtils(this).insertMarker(marker);
@@ -109,17 +109,17 @@ class TrackRecordingServiceNotificationManager implements SharedPreferences.OnSh
.build(context); .build(context);
//TODO The decision if a trackpoint is not accurate enough should happen in the TrackRecordingManager (via RecordingData). //TODO The decision if a trackpoint is not accurate enough should happen in the TrackRecordingManager (via RecordingData).
if (recordingData.latestTrackPoint().hasHorizontalAccuracy()) { if (recordingData.latestTrackPoint().position().hasHorizontalAccuracy()) {
formattedAccuracy = formatter.formatDistance(recordingData.latestTrackPoint().getHorizontalAccuracy()); formattedAccuracy = formatter.formatDistance(recordingData.latestTrackPoint().position().horizontalAccuracy());
boolean currentLocationWasAccurate = recordingData.latestTrackPoint().getHorizontalAccuracy().lessThan(thresholdHorizontalAccuracy); boolean currentLocationWasAccurate = recordingData.latestTrackPoint().position().horizontalAccuracy().lessThan(thresholdHorizontalAccuracy);
boolean shouldAlert = !currentLocationWasAccurate && previousLocationWasAccurate; boolean shouldAlert = !currentLocationWasAccurate && previousLocationWasAccurate;
notificationBuilder.setOnlyAlertOnce(!shouldAlert); notificationBuilder.setOnlyAlertOnce(!shouldAlert);
previousLocationWasAccurate = currentLocationWasAccurate; previousLocationWasAccurate = currentLocationWasAccurate;
} }
notificationBuilder.setContentTitle(context.getString(R.string.track_distance_notification, formatter.formatDistance(recordingData.trackStatistics().totalDistance()))); notificationBuilder.setContentTitle(context.getString(R.string.track_distance_notification, formatter.formatDistance(recordingData.trackStatistics().totalDistance())));
String formattedSpeed = SpeedFormatter.Builder().setUnit(unitSystem).setReportSpeedOrPace(true).build(context).formatSpeed(recordingData.latestTrackPoint().getSpeed()); String formattedSpeed = SpeedFormatter.Builder().setUnit(unitSystem).setReportSpeedOrPace(true).build(context).formatSpeed(recordingData.latestTrackPoint().position().speed());
notificationBuilder.setContentText(context.getString(R.string.track_speed_notification, formattedSpeed)); notificationBuilder.setContentText(context.getString(R.string.track_speed_notification, formattedSpeed));
notificationBuilder.setSubText(context.getString(R.string.track_recording_notification_accuracy, formattedAccuracy)); notificationBuilder.setSubText(context.getString(R.string.track_recording_notification_accuracy, formattedAccuracy));
updateNotification(); updateNotification();
@@ -18,7 +18,6 @@ import android.text.Spannable;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.style.TtsSpan; import android.text.style.TtsSpan;
import android.util.Pair;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -35,9 +34,10 @@ import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Speed; import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Statistics; import de.dennisguse.opentracks.data.models.Statistics;
import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.statistics.SensorStatistics;
import de.dennisguse.opentracks.sensors.sensorData.SensorData;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet; import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
import de.dennisguse.opentracks.settings.UnitSystem; import de.dennisguse.opentracks.settings.UnitSystem;
import de.dennisguse.opentracks.data.statistics.SensorStatistics;
import de.dennisguse.opentracks.util.StringUtils; import de.dennisguse.opentracks.util.StringUtils;
class VoiceAnnouncementUtils { class VoiceAnnouncementUtils {
@@ -160,9 +160,9 @@ class VoiceAnnouncementUtils {
} }
} }
Pair<HeartRate, String> heartrate = sensorDataSet.getHeartRate(); SensorData<HeartRate> heartrate = sensorDataSet.heartRate();
if (shouldVoiceAnnounceHeartRateCurrent() && heartrate != null && heartrate.first != null && heartrate.first.isValid()) { //TODO Check has an announcable value? if (shouldVoiceAnnounceHeartRateCurrent() && heartrate != null && heartrate.data().isValid()) { //TODO Check has an announceable value?
int averageHeartRate = Math.round(heartrate.first.getBPM()); int averageHeartRate = Math.round(heartrate.data().getBPM());
builder.append(" ") builder.append(" ")
.append(context.getString(R.string.current_heart_rate)); .append(context.getString(R.string.current_heart_rate));
@@ -19,22 +19,22 @@ public class AltitudeCorrectionManager {
private static final String TAG = AltitudeCorrectionManager.class.getSimpleName(); private static final String TAG = AltitudeCorrectionManager.class.getSimpleName();
public void correctAltitude(Context context, TrackPoint trackPoint) { public TrackPoint correctAltitude(Context context, TrackPoint trackPoint) {
if (!trackPoint.hasLocation() || !trackPoint.hasAltitude()) { if (!trackPoint.position().hasLocation() || !trackPoint.position().hasAltitude()) {
return; return trackPoint;
} }
// TODO The following is doing IO and should not be done in main thread. final TrackPoint[] newTrackPoint = {null}; //TODO this is kind of ugly
// AltitudeConverterCompat uses internally a RoomDatabase that cannot be access from main thread and thus fails on version <= 34. // AltitudeConverterCompat uses internally a RoomDatabase that cannot be access from main thread and thus fails on version <= 34.
Thread t = new Thread(() -> { Thread t = new Thread(() -> {
try { try {
Location loc = trackPoint.getPosition().toLocation(); Location loc = trackPoint.position().toLocation();
AltitudeConverterCompat.addMslAltitudeToLocation(context, loc); AltitudeConverterCompat.addMslAltitudeToLocation(context, loc);
trackPoint.setAltitude(Altitude.EGM2008.of(LocationCompat.getMslAltitudeMeters(loc))); newTrackPoint[0] = TrackPoint.with(trackPoint, Altitude.EGM2008.of(LocationCompat.getMslAltitudeMeters(loc)));
} catch (IOException e) { } catch (IOException e) {
Log.w(TAG, "Android's AltitudeConverterCompat failed with " + e.getMessage()); Log.w(TAG, "Android's AltitudeConverterCompat failed with " + e.getMessage());
} }
}); });
t.start(); t.start();
try { try {
@@ -42,5 +42,7 @@ public class AltitudeCorrectionManager {
} catch (InterruptedException e) { } catch (InterruptedException e) {
Log.w(TAG, "Android's AltitudeConverterCompat failed with " + e.getMessage()); Log.w(TAG, "Android's AltitudeConverterCompat failed with " + e.getMessage());
} }
return newTrackPoint[0];
} }
} }
@@ -3,7 +3,6 @@ package de.dennisguse.opentracks.services.handlers;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Handler; import android.os.Handler;
import android.util.Log;
import android.util.Pair; import android.util.Pair;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@@ -15,6 +14,7 @@ import java.time.Instant;
import java.time.ZoneId; import java.time.ZoneId;
import de.dennisguse.opentracks.data.models.Distance; import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Position;
import de.dennisguse.opentracks.data.models.TrackPoint; import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.sensors.GpsStatusValue; import de.dennisguse.opentracks.sensors.GpsStatusValue;
import de.dennisguse.opentracks.sensors.SensorManager; import de.dennisguse.opentracks.sensors.SensorManager;
@@ -55,15 +55,6 @@ public class TrackPointCreator implements SharedPreferences.OnSharedPreferenceCh
sensorManager.reset(); sensorManager.reset();
} }
private SensorDataSet addSensorData(TrackPoint trackPoint) {
if (!isStarted()) {
Log.w(TAG, "Not started, should not be called.");
return null;
}
return sensorManager.fill(trackPoint);
}
public void stop() { public void stop() {
sensorManager.stop(context); sensorManager.stop(context);
this.context = null; this.context = null;
@@ -72,13 +63,8 @@ public class TrackPointCreator implements SharedPreferences.OnSharedPreferenceCh
/** /**
* Got a new TrackPoint from Bluetooth only; contains no GPS location. * Got a new TrackPoint from Bluetooth only; contains no GPS location.
*/ */
public synchronized void onChange(@NonNull SensorDataSet unused) { public synchronized void onChange() {
onNewTrackPoint(new TrackPoint(TrackPoint.Type.TRACKPOINT, createNow())); TrackPoint trackPoint = createTrackPoint(TrackPoint.Type.TRACKPOINT);
}
@VisibleForTesting
public void onNewTrackPoint(@NonNull TrackPoint trackPoint) {
addSensorData(trackPoint);
boolean stored = service.newTrackPoint(trackPoint, PreferencesUtils.getThresholdHorizontalAccuracy()); //TODO Cache preference for performance boolean stored = service.newTrackPoint(trackPoint, PreferencesUtils.getThresholdHorizontalAccuracy()); //TODO Cache preference for performance
if (stored) { if (stored) {
@@ -91,40 +77,73 @@ public class TrackPointCreator implements SharedPreferences.OnSharedPreferenceCh
} }
public synchronized TrackPoint createSegmentEnd() { public synchronized TrackPoint createSegmentEnd() {
TrackPoint segmentEnd = TrackPoint.createSegmentEndWithTime(createNow()); TrackPoint segmentEnd = createTrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL);
addSensorData(segmentEnd);
reset(); reset();
return segmentEnd; return segmentEnd;
} }
public synchronized TrackPoint createIdle() { public synchronized TrackPoint createIdle() {
TrackPoint idle = new TrackPoint(TrackPoint.Type.IDLE, createNow()); TrackPoint idle = createTrackPoint(TrackPoint.Type.IDLE);
addSensorData(idle);
reset(); reset();
return idle; return idle;
} }
public Pair<TrackPoint, SensorDataSet> createCurrentTrackPoint(@Nullable TrackPoint lastTrackPointUISpeed, @Nullable TrackPoint lastTrackPointUIAltitude, @Nullable TrackPoint lastStoredTrackPointWithLocation) { public Pair<TrackPoint, SensorDataSet> createCurrentTrackPoint(@Nullable TrackPoint lastTrackPointUISpeed, @Nullable TrackPoint lastTrackPointUIAltitude, @Nullable TrackPoint lastStoredTrackPointWithLocation) {
TrackPoint currentTrackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, createNow()); Instant now = createNow();
SensorDataSet sensorDataSet = sensorManager.getSensorDataSet(now);
SensorDataSet sensorDataSet = addSensorData(currentTrackPoint); Position position = sensorDataSet.position().data();
if (!currentTrackPoint.hasLocation() && lastStoredTrackPointWithLocation != null && lastStoredTrackPointWithLocation.hasLocation()) { if (!position.hasLocation()
&& lastStoredTrackPointWithLocation != null
&& lastStoredTrackPointWithLocation.position().hasLocation()) {
//We are taking the coordinates from the last stored TrackPoint, so the distance is monotonously increasing. //We are taking the coordinates from the last stored TrackPoint, so the distance is monotonously increasing.
currentTrackPoint.setPosition(lastStoredTrackPointWithLocation.getPosition()); position = position.withCoordinates(lastStoredTrackPointWithLocation.position());
} }
if (lastTrackPointUISpeed != null) { if (lastTrackPointUISpeed != null)
currentTrackPoint.setSpeed(lastTrackPointUISpeed.getSpeed()); position.with(lastTrackPointUISpeed.position().speed());
}
if (lastTrackPointUIAltitude != null) { if (lastTrackPointUIAltitude != null)
currentTrackPoint.setAltitude(lastTrackPointUIAltitude.getAltitude()); position.with(lastTrackPointUIAltitude.position().altitude());
}
TrackPoint currentTrackPoint = new TrackPoint(
null,
TrackPoint.Type.TRACKPOINT,
position,
sensorDataSet.distance() != null ? sensorDataSet.distance().data() : null,
sensorDataSet.heartRate() != null ? sensorDataSet.heartRate().data() : null,
sensorDataSet.cadence() != null ? sensorDataSet.cadence().data() : null,
sensorDataSet.power() != null ? sensorDataSet.power().data() : null,
sensorDataSet.altitudeGainLoss() != null ? sensorDataSet.altitudeGainLoss().data() : null
);
return new Pair<>(currentTrackPoint, sensorDataSet); return new Pair<>(currentTrackPoint, sensorDataSet);
} }
private TrackPoint createTrackPoint(TrackPoint.Type type) {
Instant now = createNow();
SensorDataSet sensorDataSet = sensorManager.getSensorDataSet(now);
Position position = sensorDataSet.position().data();
if (sensorDataSet.speed() != null) {
position = position.with(sensorDataSet.speed().data());
}
return
new TrackPoint(
null,
type,
position,
sensorDataSet.distance() != null ? sensorDataSet.distance().data() : null,
sensorDataSet.heartRate() != null ? sensorDataSet.heartRate().data() : null,
sensorDataSet.cadence() != null ? sensorDataSet.cadence().data() : null,
sensorDataSet.power() != null ? sensorDataSet.power().data() : null,
sensorDataSet.altitudeGainLoss() != null ? sensorDataSet.altitudeGainLoss().data() : null
);
}
public Instant createNow() { public Instant createNow() {
return Instant.now(clock); return Instant.now(clock);
} }
@@ -76,7 +76,7 @@ public class IntervalStatisticsUpdater {
} }
} }
return trackPoint != null ? trackPoint.getId() : null; return trackPoint != null ? trackPoint.id() : null;
} }
public List<Statistics> getIntervalList() { public List<Statistics> getIntervalList() {
@@ -152,9 +152,9 @@ public class IntervalStatisticsUpdater {
if (lastTrackPoint == null) { if (lastTrackPoint == null) {
return; return;
} }
if (gain_m != null && lastTrackPoint.hasAltitudeGainLoss()) { if (gain_m != null && lastTrackPoint.altitudeGainLoss() != null) {
gain_m = gain_m - lastTrackPoint.getAltitudeGainLoss().gain_m(); gain_m = gain_m - lastTrackPoint.altitudeGainLoss().gain_m();
loss_m = loss_m - lastTrackPoint.getAltitudeGainLoss().loss_m(); loss_m = loss_m - lastTrackPoint.altitudeGainLoss().loss_m();
} }
} }
@@ -150,7 +150,7 @@ public class MarkerEditViewModel extends AndroidViewModel {
name, name,
"", "",
"", "",
trackPoint.getPosition(), trackPoint.position(),
null); null);
if (markerData == null) { if (markerData == null) {
@@ -84,11 +84,11 @@ public abstract class GenericStatisticsViewHolder extends StatisticViewHolder<St
SensorDataSet sensorDataSet = data.sensorDataSet(); SensorDataSet sensorDataSet = data.sensorDataSet();
final TrackPoint latestTrackPoint = data.latestTrackPoint(); final TrackPoint latestTrackPoint = data.latestTrackPoint();
if (sensorDataSet != null && sensorDataSet.getSpeed() != null) { if (sensorDataSet != null && sensorDataSet.speed() != null) {
valueAndUnit = localSpeedFormatter.getSpeedParts(sensorDataSet.getSpeed().first); valueAndUnit = localSpeedFormatter.getSpeedParts(sensorDataSet.speed().data());
getBinding().statsDescriptionMain.setText(sensorDataSet.getSpeed().second); getBinding().statsDescriptionMain.setText(sensorDataSet.speed().sensorNameOrAddress());
} else { } else {
Speed speed = latestTrackPoint != null && latestTrackPoint.hasSpeed() ? latestTrackPoint.getSpeed() : null; Speed speed = latestTrackPoint != null && latestTrackPoint.position().hasSpeed() ? latestTrackPoint.position().speed() : null;
valueAndUnit = localSpeedFormatter.getSpeedParts(speed); valueAndUnit = localSpeedFormatter.getSpeedParts(speed);
String title = reportSpeed ? getContext().getString(R.string.stats_speed) : getContext().getString(R.string.stats_pace); String title = reportSpeed ? getContext().getString(R.string.stats_speed) : getContext().getString(R.string.stats_pace);
@@ -249,8 +249,8 @@ public abstract class GenericStatisticsViewHolder extends StatisticViewHolder<St
@Override @Override
public void onChanged(UnitSystem unitSystem, RecordingData data) { public void onChanged(UnitSystem unitSystem, RecordingData data) {
TrackPoint latestTrackPoint = data.latestTrackPoint(); TrackPoint latestTrackPoint = data.latestTrackPoint();
Float altitude = latestTrackPoint != null && latestTrackPoint.hasAltitude() ? (float) latestTrackPoint.getAltitude().toM() : null; Float altitude = latestTrackPoint != null && latestTrackPoint.position().hasAltitude() ? (float) latestTrackPoint.position().altitude().toM() : null;
String altitudeReference = latestTrackPoint != null && latestTrackPoint.hasAltitude() ? getContext().getString(latestTrackPoint.getAltitude().getLabelId()) : null; String altitudeReference = latestTrackPoint != null && latestTrackPoint.position().hasAltitude() ? getContext().getString(latestTrackPoint.position().altitude().getLabelId()) : null;
Pair<String, String> valueAndUnit = StringUtils.getAltitudeParts(getContext(), altitude, unitSystem); Pair<String, String> valueAndUnit = StringUtils.getAltitudeParts(getContext(), altitude, unitSystem);
getBinding().statsValue.setText(valueAndUnit.first); getBinding().statsValue.setText(valueAndUnit.first);
@@ -294,8 +294,8 @@ public abstract class GenericStatisticsViewHolder extends StatisticViewHolder<St
public void onChanged(UnitSystem unitSystem, RecordingData data) { public void onChanged(UnitSystem unitSystem, RecordingData data) {
TrackPoint latestTrackPoint = data.latestTrackPoint(); TrackPoint latestTrackPoint = data.latestTrackPoint();
String value; String value;
if (latestTrackPoint != null && latestTrackPoint.hasLocation()) { if (latestTrackPoint != null && latestTrackPoint.position().hasLocation()) {
value = StringUtils.formatCoordinate(getContext(), latestTrackPoint.getPosition()); value = StringUtils.formatCoordinate(getContext(), latestTrackPoint.position());
} else { } else {
value = getContext().getString(R.string.value_unknown); value = getContext().getString(R.string.value_unknown);
} }
@@ -3,10 +3,7 @@ package de.dennisguse.opentracks.viewmodels;
import android.util.Pair; import android.util.Pair;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import java.time.Instant;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.HeartRateZones; import de.dennisguse.opentracks.data.models.HeartRateZones;
import de.dennisguse.opentracks.databinding.StatsSensorItemBinding; import de.dennisguse.opentracks.databinding.StatsSensorItemBinding;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet; import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
@@ -37,10 +34,9 @@ public abstract class SensorStatisticsViewHolder extends StatisticViewHolder<Sta
String sensorName = getContext().getString(R.string.value_unknown); String sensorName = getContext().getString(R.string.value_unknown);
Pair<String, String> valueAndUnit; Pair<String, String> valueAndUnit;
Pair<HeartRate, String> heartRate; if (sensorDataSet != null && sensorDataSet.heartRate() != null) {
if (sensorDataSet != null && (heartRate = sensorDataSet.getHeartRate()) != null) { valueAndUnit = StringUtils.getHeartRateParts(getContext(), sensorDataSet.heartRate().data());
valueAndUnit = StringUtils.getHeartRateParts(getContext(), heartRate.first); sensorName = sensorDataSet.heartRate().sensorNameOrAddress();
sensorName = heartRate.second;
} else { } else {
valueAndUnit = StringUtils.getHeartRateParts(getContext(), null); valueAndUnit = StringUtils.getHeartRateParts(getContext(), null);
} }
@@ -48,8 +44,8 @@ public abstract class SensorStatisticsViewHolder extends StatisticViewHolder<Sta
//TODO Loads preference every time //TODO Loads preference every time
HeartRateZones zones = PreferencesUtils.getHeartRateZones(); HeartRateZones zones = PreferencesUtils.getHeartRateZones();
int textColor; int textColor;
if (sensorDataSet != null && (heartRate = sensorDataSet.getHeartRate()) != null) { if (sensorDataSet != null && sensorDataSet.heartRate() != null) {
textColor = zones.getTextColorForZone(getContext(), heartRate.first); textColor = zones.getTextColorForZone(getContext(), sensorDataSet.heartRate().data());
} else { } else {
textColor = zones.getTextColorForZone(getContext(), null); textColor = zones.getTextColorForZone(getContext(), null);
} }
@@ -72,9 +68,9 @@ public abstract class SensorStatisticsViewHolder extends StatisticViewHolder<Sta
String sensorName = getContext().getString(R.string.value_unknown); String sensorName = getContext().getString(R.string.value_unknown);
Pair<String, String> valueAndUnit; Pair<String, String> valueAndUnit;
if (sensorDataSet != null && sensorDataSet.getCadence() != null) { if (sensorDataSet != null && sensorDataSet.cadence() != null) {
valueAndUnit = StringUtils.getCadenceParts(getContext(), sensorDataSet.getCadence().first); valueAndUnit = StringUtils.getCadenceParts(getContext(), sensorDataSet.cadence().data());
sensorName = sensorDataSet.getCadence().second; sensorName = sensorDataSet.cadence().sensorNameOrAddress();
} else { } else {
valueAndUnit = StringUtils.getCadenceParts(getContext(), null); valueAndUnit = StringUtils.getCadenceParts(getContext(), null);
} }
@@ -95,9 +91,9 @@ public abstract class SensorStatisticsViewHolder extends StatisticViewHolder<Sta
String sensorName = getContext().getString(R.string.value_unknown); String sensorName = getContext().getString(R.string.value_unknown);
Pair<String, String> valueAndUnit; Pair<String, String> valueAndUnit;
if (sensorDataSet != null && sensorDataSet.getCyclingPower() != null) { if (sensorDataSet != null && sensorDataSet.power() != null) {
valueAndUnit = StringUtils.getPowerParts(getContext(), sensorDataSet.getCyclingPower().getAggregatedValue(Instant.now())); //TODO Use MonotonicClock valueAndUnit = StringUtils.getPowerParts(getContext(), sensorDataSet.power().data());
sensorName = sensorDataSet.getCyclingPower().getSensorNameOrAddress(); sensorName = sensorDataSet.power().sensorNameOrAddress();
} else { } else {
valueAndUnit = StringUtils.getCadenceParts(getContext(), null); valueAndUnit = StringUtils.getCadenceParts(getContext(), null);
} }