SENSORPOINT and TRACKPOINT require a distance to be stored.

TrackRecording and TrackStatisticsUpdater.

Part of #500
Fixes #1074.
This commit is contained in:
Dennis Guse
2022-01-02 14:50:57 +01:00
parent c4b8082efc
commit 1163f74b98
10 changed files with 200 additions and 149 deletions
@@ -38,7 +38,6 @@ import java.util.ArrayList;
import java.util.List; 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 java.util.stream.Collectors;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Distance; import de.dennisguse.opentracks.content.data.Distance;
@@ -129,21 +128,21 @@ public class ExportImportTest {
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET"))); trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
trackId = service.startNewTrack(); trackId = service.startNewTrack();
service.stopUpdateRecordingData();
Distance sensorDistance = Distance.of(10); // recording distance interval Distance sensorDistance = Distance.of(10); // recording distance interval
mockBLESensorData(trackPointCreator, 15f, sensorDistance, 66f, 3f, 50f);
sendLocation(trackPointCreator, Instant.parse("2020-02-02T02:02:03Z"), 3, 14, 10, 15, 10, 1); sendLocation(trackPointCreator, Instant.parse("2020-02-02T02:02:03Z"), 3, 14, 10, 15, 10, 1);
service.insertMarker("Marker 1", "Marker 1 category", "Marker 1 desc", null); service.insertMarker("Marker 1", "Marker 1 category", "Marker 1 desc", null);
// A sensor-only TrackPoint // A sensor-only TrackPoint
mockBLESensorData(trackPointCreator, 15f, sensorDistance, 66f, 3f, 50f); trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:04Z"), ZoneId.of("CET")));
mockAltitudeChange(trackPointCreator, 1); mockAltitudeChange(trackPointCreator, 1);
sendSensor(trackPointCreator, Instant.parse("2020-02-02T02:02:04Z"));
mockBLESensorData(trackPointCreator, 15f, sensorDistance, 66f, 3f, 50f); mockBLESensorData(trackPointCreator, 15f, sensorDistance, 66f, 3f, 50f);
sendLocation(trackPointCreator, Instant.parse("2020-02-02T02:02:05Z"), 3, 14.002, 10, 15, 10, 0);
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:05Z"), ZoneId.of("CET")));
mockBLESensorData(trackPointCreator, 5f, Distance.of(2), 66f, 3f, 50f); // Distance will be added to next TrackPoint
sendLocation(trackPointCreator, Instant.parse("2020-02-02T02:02:05Z"), 3, 14.001, 10, 15, 10, 0);
service.insertMarker("Marker 2", "Marker 2 category", "Marker 2 desc", null); service.insertMarker("Marker 2", "Marker 2 category", "Marker 2 desc", null);
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:06Z"), ZoneId.of("CET"))); trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:06Z"), ZoneId.of("CET")));
@@ -152,16 +151,11 @@ public class ExportImportTest {
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:20Z"), ZoneId.of("CET"))); trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:20Z"), ZoneId.of("CET")));
service.resumeCurrentTrack(); service.resumeCurrentTrack();
service.stopUpdateRecordingData();
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:21Z"), ZoneId.of("CET"))); sendLocation(trackPointCreator, Instant.parse("2020-02-02T02:02:21Z"), 3, 14.002, 10, 15, 10, 0);
mockBLESensorData(trackPointCreator, 15f, sensorDistance, 66f, 3f, 50f);
sendLocation(trackPointCreator, Instant.parse("2020-02-02T02:02:21Z"), 3, 14.003, 10, 15, 10, 0);
mockBLESensorData(trackPointCreator, 15f, sensorDistance, 66f, 3f, 50f);
sendLocation(trackPointCreator, Instant.parse("2020-02-02T02:02:22Z"), 3, 16, 10, 15, 10, 0); sendLocation(trackPointCreator, Instant.parse("2020-02-02T02:02:22Z"), 3, 16, 10, 15, 10, 0);
mockBLESensorData(trackPointCreator, 15f, sensorDistance, 66f, 3f, 50f);
sendLocation(trackPointCreator, Instant.parse("2020-02-02T02:02:23Z"), 3, 16.001, 10, 15, 10, 0); sendLocation(trackPointCreator, Instant.parse("2020-02-02T02:02:23Z"), 3, 16.001, 10, 15, 10, 0);
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:24Z"), ZoneId.of("CET"))); trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:24Z"), ZoneId.of("CET")));
@@ -213,7 +207,8 @@ public class ExportImportTest {
// 2. trackpoints // 2. trackpoints
TrackPointAssert a = new TrackPointAssert() TrackPointAssert a = new TrackPointAssert()
.noAccuracy(); .noAccuracy();
a.assertEquals(trackPoints, TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId)); List<TrackPoint> actual = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
a.assertEquals(trackPoints, actual);
// 3. trackstatistics // 3. trackstatistics
TrackStatistics importedTrackStatistics = importedTrack.getTrackStatistics(); TrackStatistics importedTrackStatistics = importedTrack.getTrackStatistics();
@@ -226,24 +221,24 @@ public class ExportImportTest {
TrackStatistics originalTrackStatistics = track.getTrackStatistics(); TrackStatistics originalTrackStatistics = track.getTrackStatistics();
assertEquals(originalTrackStatistics.getTotalTime(), importedTrackStatistics.getTotalTime()); assertEquals(originalTrackStatistics.getTotalTime(), importedTrackStatistics.getTotalTime());
assertEquals(Duration.ofSeconds(8), importedTrackStatistics.getTotalTime()); assertEquals(Duration.ofSeconds(7), importedTrackStatistics.getTotalTime());
assertEquals(originalTrackStatistics.getMovingTime(), importedTrackStatistics.getMovingTime()); assertEquals(originalTrackStatistics.getMovingTime(), importedTrackStatistics.getMovingTime());
assertEquals(Duration.ofSeconds(4), importedTrackStatistics.getMovingTime()); assertEquals(Duration.ofSeconds(3), importedTrackStatistics.getMovingTime());
// Distance // Distance
assertEquals(originalTrackStatistics.getTotalDistance(), importedTrackStatistics.getTotalDistance()); assertEquals(originalTrackStatistics.getTotalDistance(), importedTrackStatistics.getTotalDistance());
assertEquals(Distance.of(60), importedTrackStatistics.getTotalDistance()); assertEquals(123.16, importedTrackStatistics.getTotalDistance().toM(), 0.01);
// Speed // Speed
assertEquals(originalTrackStatistics.getMaxSpeed(), importedTrackStatistics.getMaxSpeed()); assertEquals(originalTrackStatistics.getMaxSpeed(), importedTrackStatistics.getMaxSpeed());
assertEquals(Speed.of(15), importedTrackStatistics.getMaxSpeed()); assertEquals(41.05, importedTrackStatistics.getMaxSpeed().toMPS(), 0.01);
assertEquals(originalTrackStatistics.getAverageSpeed(), importedTrackStatistics.getAverageSpeed()); assertEquals(originalTrackStatistics.getAverageSpeed(), importedTrackStatistics.getAverageSpeed());
assertEquals(Speed.of(7.5), importedTrackStatistics.getAverageSpeed()); assertEquals(17.59, importedTrackStatistics.getAverageSpeed().toMPS(), 0.01);
assertEquals(originalTrackStatistics.getAverageMovingSpeed(), importedTrackStatistics.getAverageMovingSpeed()); assertEquals(originalTrackStatistics.getAverageMovingSpeed(), importedTrackStatistics.getAverageMovingSpeed());
assertEquals(Speed.of(15), importedTrackStatistics.getAverageMovingSpeed()); assertEquals(41.05, importedTrackStatistics.getMaxSpeed().toMPS(), 0.01);
// Altitude // Altitude
assertEquals(originalTrackStatistics.getMinAltitude(), importedTrackStatistics.getMinAltitude(), 0.01); assertEquals(originalTrackStatistics.getMinAltitude(), importedTrackStatistics.getMinAltitude(), 0.01);
@@ -322,17 +317,52 @@ public class ExportImportTest {
// 2. trackpoints // 2. trackpoints
// The GPX exporter does not support exporting TrackPoints without lat/lng. // The GPX exporter does not support exporting TrackPoints without lat/lng.
// Therefore, the track segmentation is changes. // Therefore, the track segmentation is changes.
List<TrackPoint> trackPointsWithCoordinates = trackPoints.stream().filter(it -> TrackPoint.Type.SEGMENT_START_AUTOMATIC.equals(it.getType()) || TrackPoint.Type.TRACKPOINT.equals(it.getType())).collect(Collectors.toList());
trackPointsWithCoordinates.get(0).setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
trackPointsWithCoordinates.get(1).setSensorDistance(Distance.of(20));
trackPointsWithCoordinates.get(1).setAltitudeGain(1f);
trackPointsWithCoordinates.get(1).setAltitudeLoss(1f);
trackPointsWithCoordinates.get(2).setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
TrackPointAssert a = new TrackPointAssert() TrackPointAssert a = new TrackPointAssert()
.setDelta(0.05) .setDelta(0.05)
.noAccuracy(); // speed is not fully .noAccuracy(); // speed is not fully
a.assertEquals(trackPointsWithCoordinates, TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId)); List<TrackPoint> actual = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
a.assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, Instant.parse("2020-02-02T02:02:03Z"))
.setLatitude(3)
.setLongitude(14)
.setAltitude(10)
.setSpeed(Speed.of(15))
.setAltitudeLoss(1f)
.setAltitudeGain(1f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-02-02T02:02:05Z"))
.setLatitude(3)
.setLongitude(14.001)
.setAltitude(10)
.setSpeed(Speed.of(5))
.setAltitudeLoss(1f)
.setAltitudeGain(1f)
.setSensorDistance(Distance.of(12))
.setHeartRate_bpm(66f)
.setPower(50f)
.setCadence_rpm(3f),
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, Instant.parse("2020-02-02T02:02:21Z"))
.setLatitude(3)
.setLongitude(14.002)
.setAltitude(10)
.setAltitudeLoss(0f)
.setAltitudeGain(0f)
.setSpeed(Speed.of(15)),
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, Instant.parse("2020-02-02T02:02:22Z"))
.setLatitude(3)
.setLongitude(16)
.setAltitude(10)
.setAltitudeLoss(0f)
.setAltitudeGain(0f)
.setSpeed(Speed.of(15)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-02-02T02:02:23Z"))
.setLatitude(3)
.setLongitude(16.001)
.setAltitude(10)
.setAltitudeLoss(0f)
.setAltitudeGain(0f)
.setSpeed(Speed.of(15))
), actual);
// 3. trackstatistics // 3. trackstatistics
TrackStatistics trackStatistics = track.getTrackStatistics(); TrackStatistics trackStatistics = track.getTrackStatistics();
@@ -343,16 +373,16 @@ public class ExportImportTest {
assertEquals(Instant.parse("2020-02-02T02:02:03Z"), importedTrackStatistics.getStartTime()); assertEquals(Instant.parse("2020-02-02T02:02:03Z"), importedTrackStatistics.getStartTime());
assertEquals(Instant.parse("2020-02-02T02:02:23Z"), importedTrackStatistics.getStopTime()); assertEquals(Instant.parse("2020-02-02T02:02:23Z"), importedTrackStatistics.getStopTime());
assertEquals(Duration.ofSeconds(4), importedTrackStatistics.getTotalTime()); assertEquals(Duration.ofSeconds(3), importedTrackStatistics.getTotalTime());
assertEquals(Duration.ofSeconds(4), importedTrackStatistics.getMovingTime()); assertEquals(Duration.ofSeconds(3), importedTrackStatistics.getMovingTime());
// Distance // Distance
assertEquals(Distance.of(60), importedTrackStatistics.getTotalDistance()); assertEquals(123.16, importedTrackStatistics.getTotalDistance().toM(), 0.01);
// Speed // Speed
assertEquals(Speed.of(15), importedTrackStatistics.getMaxSpeed()); assertEquals(41.05, importedTrackStatistics.getMaxSpeed().toMPS(), 0.01);
assertEquals(Speed.of(15), importedTrackStatistics.getAverageSpeed()); assertEquals(41.05, importedTrackStatistics.getAverageSpeed().toMPS(), 0.01);
assertEquals(Speed.of(15), importedTrackStatistics.getAverageMovingSpeed()); assertEquals(41.05, importedTrackStatistics.getAverageMovingSpeed().toMPS(), 0.01);
// Altitude // Altitude
assertEquals(10, importedTrackStatistics.getMinAltitude(), 0.01); assertEquals(10, importedTrackStatistics.getMinAltitude(), 0.01);
@@ -424,19 +454,21 @@ public class ExportImportTest {
Mockito.when(cadence.getValue()).thenReturn(cyclingCadence); Mockito.when(cadence.getValue()).thenReturn(cyclingCadence);
sensorDataSet.set(cadence); sensorDataSet.set(cadence);
SensorDataCycling.DistanceSpeed.Data distanceSpeedData = Mockito.mock(SensorDataCycling.DistanceSpeed.Data.class); if (distance != null && speed != null) {
Mockito.when(distanceSpeedData.getDistanceOverall()).thenReturn(distance); SensorDataCycling.DistanceSpeed.Data distanceSpeedData = Mockito.mock(SensorDataCycling.DistanceSpeed.Data.class);
Mockito.when(distanceSpeedData.getSpeed()).thenReturn(Speed.of(speed)); Mockito.when(distanceSpeedData.getDistanceOverall()).thenReturn(distance);
SensorDataCycling.DistanceSpeed distanceSpeed = Mockito.mock(SensorDataCycling.DistanceSpeed.class); Mockito.when(distanceSpeedData.getSpeed()).thenReturn(Speed.of(speed));
Mockito.when(distanceSpeed.hasValue()).thenReturn(true); SensorDataCycling.DistanceSpeed distanceSpeed = Mockito.mock(SensorDataCycling.DistanceSpeed.class);
Mockito.when(distanceSpeed.getValue()).thenReturn(distanceSpeedData); Mockito.when(distanceSpeed.hasValue()).thenReturn(true);
Mockito.when(distanceSpeed.getValue()).thenReturn(distanceSpeedData);
sensorDataSet.set(distanceSpeed); sensorDataSet.set(distanceSpeed);
}
sensorDataSet.fillTrackPoint(trackPoint); sensorDataSet.fillTrackPoint(trackPoint);
return sensorDataSet; return sensorDataSet;
} }
}); });
trackPointCreator.onChange(null);
} }
private void mockAltitudeChange(TrackPointCreator trackPointCreator, float altitudeGain) { private void mockAltitudeChange(TrackPointCreator trackPointCreator, float altitudeGain) {
@@ -445,11 +477,6 @@ public class ExportImportTest {
altitudeSumManager.setAltitudeLoss_m(altitudeGain); altitudeSumManager.setAltitudeLoss_m(altitudeGain);
} }
private void sendSensor(TrackPointCreator trackPointCreator, Instant time) {
trackPointCreator.setClock(Clock.fixed(time, ZoneId.of("CET")));
trackPointCreator.onNewTrackPointWithoutGPS();
}
private void sendLocation(TrackPointCreator trackPointCreator, Instant time, double latitude, double longitude, float accuracy, float speed, float altitude, float altitudeGain) { private void sendLocation(TrackPointCreator trackPointCreator, Instant time, double latitude, double longitude, float accuracy, float speed, float altitude, float altitudeGain) {
Location location = new Location("mock"); Location location = new Location("mock");
location.setLatitude(latitude); location.setLatitude(latitude);
@@ -62,6 +62,7 @@ import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.io.file.importer.TrackPointAssert; import de.dennisguse.opentracks.io.file.importer.TrackPointAssert;
import de.dennisguse.opentracks.services.handlers.TrackPointCreator; import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
import de.dennisguse.opentracks.services.sensors.AltitudeSumManager; import de.dennisguse.opentracks.services.sensors.AltitudeSumManager;
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
import de.dennisguse.opentracks.settings.PreferencesUtils; import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.stats.TrackStatistics; import de.dennisguse.opentracks.stats.TrackStatistics;
@@ -465,8 +466,10 @@ public class TrackRecordingServiceTest {
// Reset service (if some previous test failed) // Reset service (if some previous test failed)
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class))) TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)))
.getService(); .getService();
service.endCurrentTrack();
service.getTrackPointCreator().setRemoteSensorManager(new BluetoothRemoteSensorManager(context, service.getTrackPointCreator()));
service.getTrackPointCreator().setClock(Clock.systemUTC()); service.getTrackPointCreator().setClock(Clock.systemUTC());
service.endCurrentTrack();
service.sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null); service.sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
} }
} }
@@ -382,7 +382,6 @@ public class TrackRecordingServiceTestLocation {
), trackPoints); ), trackPoints);
} }
@Deprecated // Will be superseded when fixing #500
@MediumTest @MediumTest
@Test @Test
public void testOnLocationChangedAsync_idle_withSensorDistance() { public void testOnLocationChangedAsync_idle_withSensorDistance() {
@@ -407,23 +406,25 @@ public class TrackRecordingServiceTestLocation {
// when // when
altitudeSumManager.addAltitudeGain_m(6f); altitudeSumManager.addAltitudeGain_m(6f);
altitudeSumManager.addAltitudeLoss_m(6f); altitudeSumManager.addAltitudeLoss_m(6f);
remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(0))); remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(0))); //Should be ignored
remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(2))); remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(2))); //TODO Should be ignored; distance will be added to TrackPoint
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 15); TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 15);
remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(12))); remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(12)));
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 2, 15);
remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(13))); remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(13))); //Should be ignored
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 3, 15);
altitudeSumManager.addAltitudeGain_m(6f); altitudeSumManager.addAltitudeGain_m(6f);
altitudeSumManager.addAltitudeLoss_m(6f); altitudeSumManager.addAltitudeLoss_m(6f);
remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(14))); remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(14))); //Should be ignored
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 4, 15); TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 4, 15); //Should be ignored
altitudeSumManager.addAltitudeGain_m(6f); altitudeSumManager.addAltitudeGain_m(6f);
altitudeSumManager.addAltitudeLoss_m(6f); altitudeSumManager.addAltitudeLoss_m(6f);
remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(16))); remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(16)));
altitudeSumManager.addAltitudeGain_m(7f);
altitudeSumManager.addAltitudeLoss_m(7f);
service.endCurrentTrack(); service.endCurrentTrack();
// then // then
@@ -434,34 +435,31 @@ public class TrackRecordingServiceTestLocation {
.ignoreTime(); .ignoreTime();
a.assertEquals(List.of( a.assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, null), new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, null),
new TrackPoint(TrackPoint.Type.SENSORPOINT, null) // TODO Should be ignored; is stored as it assumed to be first in current segment.
.setAltitudeGain(6f)
.setAltitudeLoss(6f)
.setSpeed(Speed.of(5))
.setSensorDistance(Distance.of(2)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null) new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45) .setLatitude(45)
.setLongitude(35) .setLongitude(35)
.setHorizontalAccuracy(Distance.of(1)) .setHorizontalAccuracy(Distance.of(1))
.setSpeed(Speed.of(5)) .setSpeed(Speed.of(5))
.setAltitudeGain(6f) .setSensorDistance(Distance.of(0)),
.setAltitudeLoss(6f) new TrackPoint(TrackPoint.Type.SENSORPOINT, null)
.setSensorDistance(Distance.of(2)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(2))
.setSpeed(Speed.of(5)) .setSpeed(Speed.of(5))
.setSensorDistance(Distance.of(10)), .setSensorDistance(Distance.of(10)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null) new TrackPoint(TrackPoint.Type.SENSORPOINT, null)
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(4))
.setSpeed(Speed.of(5)) .setSpeed(Speed.of(5))
.setAltitudeGain(6f) .setAltitudeGain(12f)
.setAltitudeLoss(6f) .setAltitudeLoss(12f)
.setSensorDistance(Distance.of(2)), .setSensorDistance(Distance.of(4)),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, null) new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, null)
.setSensorDistance(Distance.of(11)) .setSensorDistance(Distance.of(11))
.setSpeed(Speed.of(5)) .setSpeed(Speed.of(5))
.setAltitudeGain(6f) .setAltitudeGain(7f)
.setAltitudeLoss(6f) .setAltitudeLoss(7f)
.setSensorDistance(Distance.of(2)) .setSensorDistance(Distance.of(0))
), trackPoints); ), trackPoints);
} }
@@ -59,5 +59,12 @@ public abstract class Altitude {
return new EGM2008(altitude_m); return new EGM2008(altitude_m);
} }
} }
@Override
public String toString() {
return "Altitude{" +
"altitude_m=" + altitude_m + this.getClass().getSimpleName() +
'}';
}
} }
@@ -64,7 +64,7 @@ public class TrackPoint {
SEGMENT_START_AUTOMATIC(-1), //Start of a segment due to too much distance from previous TrackPoint SEGMENT_START_AUTOMATIC(-1), //Start of a segment due to too much distance from previous TrackPoint
TRACKPOINT(0), //Just GPS data and may contain BLE sensor data TRACKPOINT(0), //Just GPS data and may contain BLE sensor data
SENSORPOINT(2), //Just BLE sensor data SENSORPOINT(2), //Just BLE sensor data; required to have speed and sensorDistance
SEGMENT_END_MANUAL(1); //End of a segment SEGMENT_END_MANUAL(1); //End of a segment
@@ -109,13 +109,7 @@ public class TrackPoint {
public TrackPoint(@NonNull Type type, @NonNull Location location, @NonNull Instant time) { public TrackPoint(@NonNull Type type, @NonNull Location location, @NonNull Instant time) {
this(type, time); this(type, time);
this.latitude = location.getLatitude(); setLocation(location);
this.longitude = location.getLongitude();
this.altitude = location.hasAltitude() ? Altitude.WGS84.of(location.getAltitude()) : null;
this.speed = location.hasSpeed() ? Speed.of(location.getSpeed()) : null;
this.horizontalAccuracy = location.hasAccuracy() ? Distance.of(location.getAccuracy()) : null;
//TODO Should we copy the bearing?
} }
@VisibleForTesting @VisibleForTesting
@@ -152,6 +146,10 @@ public class TrackPoint {
return type == Type.SEGMENT_END_MANUAL; return type == Type.SEGMENT_END_MANUAL;
} }
public boolean wasCreatedManually() {
return hasLocation() || hasSpeed();
}
/** /**
* May be null if the track was not loaded from the database. * May be null if the track was not loaded from the database.
*/ */
@@ -211,6 +209,17 @@ public class TrackPoint {
return location; return location;
} }
public TrackPoint setLocation(@NonNull Location location) {
this.latitude = location.getLatitude();
this.longitude = location.getLongitude();
this.altitude = location.hasAltitude() ? Altitude.WGS84.of(location.getAltitude()) : null;
this.speed = location.hasSpeed() ? Speed.of(location.getSpeed()) : null;
this.horizontalAccuracy = location.hasAccuracy() ? Distance.of(location.getAccuracy()) : null;
//TODO Should we copy the bearing?
return this;
}
public boolean hasAltitudeGain() { public boolean hasAltitudeGain() {
return altitudeGain_m != null; return altitudeGain_m != null;
} }
@@ -310,13 +319,13 @@ public class TrackPoint {
return this; return this;
} }
@Nullable @NonNull
public Distance distanceToPrevious(@Nullable TrackPoint previous) { public Distance distanceToPrevious(@NonNull TrackPoint previous) {
if (hasSensorDistance()) { if (hasSensorDistance()) {
return getSensorDistance(); return getSensorDistance();
} }
if (previous == null || !(hasLocation() && previous.hasLocation())) { if (!hasLocation() || hasLocation() != previous.hasLocation()) {
return null; throw new RuntimeException("Cannot compute distance.");
} }
return Distance.of(getLocation().distanceTo(previous.getLocation())); return Distance.of(getLocation().distanceTo(previous.getLocation()));
@@ -411,7 +420,7 @@ public class TrackPoint {
public String toString() { public String toString() {
String result = "time=" + getTime() + " (type=" + getType() + ")"; String result = "time=" + getTime() + " (type=" + getType() + ")";
if (hasLocation()) { if (hasLocation()) {
result += ": lat=" + getLatitude() + " lng=" + getLongitude(); result += ": lat=" + getLatitude() + " lng=" + getLongitude() + " alt=" + getAltitude();
} }
if (hasHorizontalAccuracy()) { if (hasHorizontalAccuracy()) {
result += " acc=" + getHorizontalAccuracy(); result += " acc=" + getHorizontalAccuracy();
@@ -419,6 +428,12 @@ public class TrackPoint {
if (hasSensorDistance()) { if (hasSensorDistance()) {
result += " distance=" + getSensorDistance(); result += " distance=" + getSensorDistance();
} }
if (hasAltitudeGain()) {
result += " altitudeGain= " + getAltitudeGain();
}
if (hasAltitudeLoss()) {
result += " altitudeLoss= " + getAltitudeLoss();
}
return result; return result;
} }
@@ -306,23 +306,10 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac
Instant time = whenList.get(i); Instant time = whenList.get(i);
Location location = locationList.get(i); Location location = locationList.get(i);
TrackPoint trackPoint; TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.SENSORPOINT, time);
if (i == 0) { if (location != null) {
//first trackPoint.setType(TrackPoint.Type.TRACKPOINT);
if (location == null) { trackPoint.setLocation(location);
trackPoint = TrackPoint.createSegmentStartManualWithTime(time);
} else {
trackPoint = new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, location, time);
}
} else if (i == locationList.size() - 1 && location == null) {
//last
trackPoint = TrackPoint.createSegmentEndWithTime(time);
} else {
if (location == null) {
trackPoint = new TrackPoint(TrackPoint.Type.SENSORPOINT, time);
} else {
trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, location, time);
}
} }
if (i < sensorSpeedList.size() && sensorSpeedList.get(i) != null) { if (i < sensorSpeedList.size() && sensorSpeedList.get(i) != null) {
@@ -347,6 +334,21 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac
trackPoint.setAltitudeLoss(altitudeLossList.get(i)); trackPoint.setAltitudeLoss(altitudeLossList.get(i));
} }
// Update TrackPoint type for START / STOP.
TrackPoint.Type type = trackPoint.getType();
if (i == 0) {
//first
if (!trackPoint.wasCreatedManually()) {
type = TrackPoint.Type.SEGMENT_START_MANUAL;
} else {
type = TrackPoint.Type.SEGMENT_START_AUTOMATIC;
}
} else if (i == locationList.size() - 1 && !trackPoint.wasCreatedManually()) {
//last
type = TrackPoint.Type.SEGMENT_END_MANUAL;
}
trackPoint.setType(type);
trackImporter.addTrackPoint(trackPoint); trackImporter.addTrackPoint(trackPoint);
} }
} }
@@ -198,8 +198,8 @@ 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);
Distance distanceToPrevious = current.distanceToPrevious(previous); if (current.hasSensorDistance() || (previous.hasLocation() && current.hasLocation())) {
if (distanceToPrevious != null) { Distance distanceToPrevious = current.distanceToPrevious(previous);
if (!current.hasSpeed()) { if (!current.hasSpeed()) {
Duration timeDifference = Duration.between(previous.getTime(), current.getTime()); Duration timeDifference = Duration.between(previous.getTime(), current.getTime());
current.setSpeed(Speed.of(distanceToPrevious, timeDifference)); current.setSpeed(Speed.of(distanceToPrevious, timeDifference));
@@ -39,8 +39,6 @@ class TrackRecordingManager {
private Track.Id trackId; private Track.Id trackId;
private TrackStatisticsUpdater trackStatisticsUpdater; private TrackStatisticsUpdater trackStatisticsUpdater;
//TDOO use lastStoredTrackPoint?
private boolean currentSegmentHasTrackPoint;
private TrackPoint lastTrackPoint; private TrackPoint lastTrackPoint;
private TrackPoint lastStoredTrackPoint; private TrackPoint lastStoredTrackPoint;
private TrackPoint lastStoredTrackPointWithLocation; private TrackPoint lastStoredTrackPointWithLocation;
@@ -68,8 +66,6 @@ class TrackRecordingManager {
track.setName(TrackNameUtils.getTrackName(context, trackId, track.getStartTime())); track.setName(TrackNameUtils.getTrackName(context, trackId, track.getStartTime()));
contentProviderUtils.updateTrack(track); contentProviderUtils.updateTrack(track);
currentSegmentHasTrackPoint = false;
return trackId; return trackId;
} }
@@ -85,7 +81,6 @@ class TrackRecordingManager {
trackStatisticsUpdater = new TrackStatisticsUpdater(track.getTrackStatistics()); trackStatisticsUpdater = new TrackStatisticsUpdater(track.getTrackStatistics());
insertTrackPoint(trackId, segmentStartTrackPoint); insertTrackPoint(trackId, segmentStartTrackPoint);
currentSegmentHasTrackPoint = false;
lastTrackPoint = null; lastTrackPoint = null;
lastStoredTrackPoint = null; lastStoredTrackPoint = null;
lastStoredTrackPointWithLocation = null; lastStoredTrackPointWithLocation = null;
@@ -94,7 +89,6 @@ class TrackRecordingManager {
void pause(TrackPointCreator trackPointCreator) { void pause(TrackPointCreator trackPointCreator) {
insertTrackPoint(trackId, trackPointCreator.createSegmentEnd()); insertTrackPoint(trackId, trackPointCreator.createSegmentEnd());
currentSegmentHasTrackPoint = false;
lastTrackPoint = null; lastTrackPoint = null;
lastStoredTrackPoint = null; lastStoredTrackPoint = null;
lastStoredTrackPointWithLocation = null; lastStoredTrackPointWithLocation = null;
@@ -107,7 +101,6 @@ class TrackRecordingManager {
trackId = null; trackId = null;
trackStatisticsUpdater = null; trackStatisticsUpdater = null;
currentSegmentHasTrackPoint = false;
lastTrackPoint = null; lastTrackPoint = null;
lastStoredTrackPoint = null; lastStoredTrackPoint = null;
lastStoredTrackPointWithLocation = null; lastStoredTrackPointWithLocation = null;
@@ -161,44 +154,43 @@ class TrackRecordingManager {
/** /**
* @return TrackPoint was stored? * @return TrackPoint was stored?
*/ */
boolean onNewTrackPoint(TrackPoint trackPoint) { boolean onNewTrackPoint(@NonNull TrackPoint trackPoint) {
//Storing trackPoint //Storing trackPoint
// Always insert the first segment location // Always insert the first segment location
if (!currentSegmentHasTrackPoint) { if (lastStoredTrackPoint == null) {
insertTrackPoint(trackId, trackPoint); insertTrackPoint(trackId, trackPoint);
currentSegmentHasTrackPoint = true;
return true; return true;
} }
Distance distanceToLastStoredTrackPoint = trackPoint.distanceToPrevious(lastStoredTrackPoint); if (trackPoint.hasLocation() && lastStoredTrackPointWithLocation == null) {
if (distanceToLastStoredTrackPoint != null) { insertTrackPoint(trackId, trackPoint);
if (distanceToLastStoredTrackPoint.greaterThan(maxRecordingDistance)) { return true;
trackPoint.setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
insertTrackPoint(trackId, trackPoint);
return true;
}
if (distanceToLastStoredTrackPoint.greaterOrEqualThan(recordingDistanceInterval) && trackPoint.isMoving()) {
insertTrackPoint(trackId, trackPoint);
return true;
}
if (trackPoint.hasLocation()) {
if (lastStoredTrackPointWithLocation == null) {
insertTrackPoint(trackId, trackPoint);
return true;
}
Distance distanceToLastStoredTrackPointWithLocation = trackPoint.distanceToPrevious(lastStoredTrackPointWithLocation);
if (distanceToLastStoredTrackPointWithLocation != null && distanceToLastStoredTrackPointWithLocation.greaterOrEqualThan(recordingDistanceInterval)) {
insertTrackPoint(trackId, trackPoint);
return true;
}
}
} }
if (lastStoredTrackPoint != null && trackPoint.isMoving() != lastStoredTrackPoint.isMoving()) { if (!trackPoint.hasLocation() && !trackPoint.hasSensorDistance()) {
Log.d(TAG, "Ignoring TrackPoint as it has no distance.");
return false;
}
TrackPoint distanceTo = lastStoredTrackPoint;
if (trackPoint.hasLocation() && !lastStoredTrackPoint.hasLocation()) {
distanceTo = lastStoredTrackPointWithLocation;
}
Distance distanceToLastStoredTrackPoint = trackPoint.distanceToPrevious(distanceTo);
if (distanceToLastStoredTrackPoint.greaterThan(maxRecordingDistance)) {
trackPoint.setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
insertTrackPoint(trackId, trackPoint);
return true;
}
if (distanceToLastStoredTrackPoint.greaterOrEqualThan(recordingDistanceInterval)
&& trackPoint.isMoving()) {
insertTrackPoint(trackId, trackPoint);
return true;
}
if (trackPoint.isMoving() != lastStoredTrackPoint.isMoving()) {
// Moving from non-moving to moving or vice versa; required to compute moving time correctly. // Moving from non-moving to moving or vice versa; required to compute moving time correctly.
insertTrackPoint(trackId, trackPoint); insertTrackPoint(trackId, trackPoint);
return true; return true;
@@ -206,6 +198,7 @@ class TrackRecordingManager {
Log.d(TAG, "Not recording TrackPoint"); Log.d(TAG, "Not recording TrackPoint");
lastTrackPoint = trackPoint; lastTrackPoint = trackPoint;
return false; return false;
} }
@@ -213,6 +206,7 @@ class TrackRecordingManager {
return trackStatisticsUpdater.getTrackStatistics(); return trackStatisticsUpdater.getTrackStatistics();
} }
//TODO Should be only be called from onNewTrackPoint().
private void insertTrackPoint(@NonNull Track.Id trackId, @NonNull TrackPoint trackPoint) { private void insertTrackPoint(@NonNull Track.Id trackId, @NonNull TrackPoint trackPoint) {
if (lastTrackPoint != null) { if (lastTrackPoint != null) {
if (lastStoredTrackPoint != null && lastTrackPoint.getTime().equals(lastStoredTrackPoint.getTime())) { if (lastStoredTrackPoint != null && lastTrackPoint.getTime().equals(lastStoredTrackPoint.getTime())) {
@@ -26,6 +26,7 @@ import java.time.Instant;
import de.dennisguse.opentracks.content.data.Altitude; import de.dennisguse.opentracks.content.data.Altitude;
import de.dennisguse.opentracks.content.data.Distance; import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Speed; import de.dennisguse.opentracks.content.data.Speed;
import de.dennisguse.opentracks.content.data.TrackPoint;
/** /**
* Statistical data about a {@link de.dennisguse.opentracks.content.data.Track}. * Statistical data about a {@link de.dennisguse.opentracks.content.data.Track}.
@@ -161,6 +162,7 @@ public class TrackStatistics {
public void setStopTime(Instant stopTime) { public void setStopTime(Instant stopTime) {
if (stopTime.isBefore(startTime)) { if (stopTime.isBefore(startTime)) {
// Time must be monotonically increasing, but we might have events at the same point in time (BLE and GPS)
throw new RuntimeException("stopTime cannot be less than startTime: " + startTime + " " + stopTime); throw new RuntimeException("stopTime cannot be less than startTime: " + startTime + " " + stopTime);
} }
this.stopTime = stopTime; this.stopTime = stopTime;
@@ -199,8 +201,15 @@ public class TrackStatistics {
this.movingTime = movingTime; this.movingTime = movingTime;
} }
public void addMovingTime(TrackPoint trackPoint, TrackPoint lastTrackPoint) {
addMovingTime(Duration.between(lastTrackPoint.getTime(), trackPoint.getTime()));
}
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public void addMovingTime(Duration time) { public void addMovingTime(Duration time) {
if (time.isNegative()) {
throw new RuntimeException("Moving time cannot be negative");
}
movingTime = movingTime.plus(time); movingTime = movingTime.plus(time);
} }
@@ -148,23 +148,19 @@ public class TrackStatisticsUpdater {
if (trackPoint.hasSensorDistance()) { if (trackPoint.hasSensorDistance()) {
// Sensor-based distance/speed // Sensor-based distance/speed
currentSegment.addTotalDistance(trackPoint.getSensorDistance()); currentSegment.addTotalDistance(trackPoint.getSensorDistance());
} else if (lastTrackPoint != null && trackPoint.isMoving()) { } else if (lastTrackPoint != null
&& lastTrackPoint.hasLocation()
&& trackPoint.hasLocation() && trackPoint.isMoving()) {
// GPS-based distance/speed // GPS-based distance/speed
// Assumption: we ignore TrackPoints that are not moving as those are likely imprecise GPS measurements // Assumption: we ignore TrackPoints that are not moving as those are likely imprecise GPS measurements
Distance movingDistance = trackPoint.distanceToPrevious(lastTrackPoint); Distance movingDistance = trackPoint.distanceToPrevious(lastTrackPoint);
if (movingDistance != null) { currentSegment.addTotalDistance(movingDistance);
currentSegment.addTotalDistance(movingDistance);
}
} }
// Update moving time // Update moving time
if (trackPoint.isMoving() && lastTrackPoint != null && lastTrackPoint.isMoving()) { if (trackPoint.isMoving() && lastTrackPoint != null && lastTrackPoint.isMoving()) {
Duration movingTime = Duration.between(lastTrackPoint.getTime(), trackPoint.getTime()); currentSegment.addMovingTime(trackPoint, lastTrackPoint);
if (movingTime.isNegative()) {
throw new RuntimeException("Moving time cannot be negative");
}
currentSegment.addMovingTime(movingTime);
// Update max speed // Update max speed
updateSpeed(trackPoint, lastTrackPoint); updateSpeed(trackPoint, lastTrackPoint);