Segments: use TrackPoint.type.

So, far specific latitude for segment start/end (i.e., PAUSE/RESUME) was used.
#316.
This commit is contained in:
Dennis Guse
2021-01-15 07:57:05 +01:00
parent 163af2dab2
commit fcc17413e3
25 changed files with 300 additions and 235 deletions
@@ -63,7 +63,7 @@ public class DescriptionGeneratorTest {
stats.setMaxSpeed(100);
stats.setMaxElevation(550);
stats.setMinElevation(-500);
stats.setTotalElevationGain(6000);
stats.setTotalElevationGain(6000f);
stats.setTotalElevationLoss(6000);
stats.setStartTime_ms(START_TIME);
track.setTrackStatistics(stats);
@@ -101,7 +101,7 @@ public class DescriptionGeneratorTest {
stats.setMaxSpeed(100);
stats.setMaxElevation(Double.POSITIVE_INFINITY);
stats.setMinElevation(Double.NEGATIVE_INFINITY);
stats.setTotalElevationGain(6000);
stats.setTotalElevationGain(6000f);
stats.setTotalElevationLoss(6000);
stats.setStartTime_ms(START_TIME);
track.setTrackStatistics(stats);
@@ -63,7 +63,6 @@ public class ChartFragmentTest {
boolean chartByDistance = false;
chartFragment = (ChartFragment) ChartFragment.newInstance(chartByDistance);
chartFragment.setChartView(new ChartView(ApplicationProvider.getApplicationContext(), chartByDistance));
chartFragment.setTrackStatisticsUpdater(TrackStubUtils.INITIAL_TIME);
}
/**
@@ -65,7 +65,7 @@ public class LegacyKmlImportTest {
// 3. trackpoints
List<TrackPoint> importedTrackPoints = contentProviderUtils.getTrackPoints(importTrackId);
assertEquals(8, importedTrackPoints.size()); // 6 trackpoints + pause and resume
assertEquals(8, importedTrackPoints.size()); // 6 trackpoints + segmentEnd and segmentStart
// first 3 trackpoints
assertTrackpoint(importedTrackPoints.get(0), "2020-11-28T17:06:22.401Z", 1.234156, 12.340097, 469.286376953125);
@@ -73,9 +73,12 @@ public class LegacyKmlImportTest {
assertTrackpoint(importedTrackPoints.get(2), "2020-11-28T17:06:47.888Z", 1.23405, 12.340057, 421.8070983886719);
// created pause trackpoint with time of previous valid trackpoint
assertTrackpoint(importedTrackPoints.get(3), "2020-11-28T17:06:47.888Z", 0.0, 100, 0.0);
assertTrackpoint(importedTrackPoints.get(3), "2020-11-28T17:06:47.888Z", 0.0, 0.0, 0.0);
assertEquals(1, importedTrackPoints.get(3).getType().type_db);
// created resume trackpoint with time of next valid trackpoint
assertTrackpoint(importedTrackPoints.get(4), "2020-11-28T17:06:55.861Z", 0.0, 200, 0.0);
assertTrackpoint(importedTrackPoints.get(4), "2020-11-28T17:06:55.861Z", 0.0, 0.0, 0.0);
assertEquals(-2, importedTrackPoints.get(4).getType().type_db);
// last 3 trackpoints
assertTrackpoint(importedTrackPoints.get(5), "2020-11-28T17:06:55.861Z", 1.23405, 12.340057, 419.93902587890625);
@@ -47,7 +47,6 @@ import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
import de.dennisguse.opentracks.stats.TrackStatistics;
@@ -158,7 +157,7 @@ public class TrackRecordingServiceTest {
@Test
public void testRecording_oldTracks() throws TimeoutException {
// given
createDummyTrack(trackId, -1L, false);
createDummyTrack(trackId, false);
// when
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
@@ -172,7 +171,7 @@ public class TrackRecordingServiceTest {
@Test
public void testRecording_serviceRestart_whileRecording() throws TimeoutException {
// given
createDummyTrack(trackId, -1L, true);
createDummyTrack(trackId, true);
//when
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
@@ -185,9 +184,8 @@ public class TrackRecordingServiceTest {
@Test
public void testRecording_pauseAndResume() throws TimeoutException, InterruptedException {
// given
createDummyTrack(trackId, -1L, true);
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
newTrackPoint(service);
Track.Id trackId = service.startNewTrack();
// when
service.pauseCurrentTrack();
@@ -197,29 +195,28 @@ public class TrackRecordingServiceTest {
//when
service.resumeTrack(trackId);
newTrackPoint(service);
// then
assertTrue(service.isRecording());
assertEquals(trackId, service.getRecordingTrackId());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
assertEquals(5, trackPoints.size());
assertEquals(TrackPointsColumns.PAUSE_LATITUDE, trackPoints.get(1).getLatitude(), 0.01);
assertEquals(TrackPointsColumns.PAUSE_LATITUDE, trackPoints.get(2).getLatitude(), 0.01);
assertEquals(TrackPointsColumns.RESUME_LATITUDE, trackPoints.get(3).getLatitude(), 0.01);
assertEquals(3, trackPoints.size());
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
assertEquals(TrackPoint.Type.SEGMENT_END_MANUAL, trackPoints.get(1).getType());
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(2).getType());
}
@MediumTest
@Test
public void testRecording_resumeStoppedTrack() throws TimeoutException, InterruptedException {
// given
createDummyTrack(trackId, -1L, true);
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
newTrackPoint(service);
Track.Id trackId = service.startNewTrack();
assertTrue(service.isRecording());
service.endCurrentTrack();
assertEquals(1, contentProviderUtils.getTrackPoints(trackId).size());
assertEquals(2, contentProviderUtils.getTrackPoints(trackId).size());
// when
service.resumeTrack(trackId);
@@ -231,8 +228,10 @@ public class TrackRecordingServiceTest {
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
assertEquals(4, trackPoints.size());
assertEquals(TrackPointsColumns.PAUSE_LATITUDE, trackPoints.get(1).getLatitude(), 0.01);
assertEquals(TrackPointsColumns.RESUME_LATITUDE, trackPoints.get(2).getLatitude(), 0.01);
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
assertEquals(TrackPoint.Type.SEGMENT_END_MANUAL, trackPoints.get(1).getType());
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(2).getType());
assertEquals(TrackPoint.Type.TRACKPOINT, trackPoints.get(3).getType());
}
@Ignore("Sometimes fails on CI.")
@@ -336,12 +335,11 @@ public class TrackRecordingServiceTest {
}
// NOTE: Do not use to create a track that is currently recording.
private void createDummyTrack(Track.Id id, long stopTime, boolean isRecording) {
private void createDummyTrack(Track.Id id, boolean isRecording) {
Track dummyTrack = new Track();
dummyTrack.setId(id);
dummyTrack.setName("Dummy Track");
TrackStatistics trackStatistics = new TrackStatistics();
trackStatistics.setStopTime_ms(stopTime);
dummyTrack.setTrackStatistics(trackStatistics);
addTrack(dummyTrack, isRecording);
}
@@ -368,7 +366,6 @@ public class TrackRecordingServiceTest {
TrackPoint trackPoint = new TrackPoint(location);
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(ApplicationProvider.getApplicationContext());
trackRecordingService.newTrackPoint(trackPoint, prefAccuracy);
//TODO Needed?
Thread.sleep(200);
}
@@ -24,7 +24,6 @@ import java.util.concurrent.TimeoutException;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
import de.dennisguse.opentracks.content.sensor.SensorDataHeartRate;
@@ -34,6 +33,7 @@ import de.dennisguse.opentracks.util.PreferencesUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
/**
* Tests insert location.
@@ -69,6 +69,7 @@ public class TrackRecordingServiceTestLocation {
customContentProvider.attachInfo(context, null);
contentProviderUtils = new ContentProviderUtils(context);
tearDown();
// Let's use default values.
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
@@ -79,11 +80,6 @@ public class TrackRecordingServiceTestLocation {
@After
public void tearDown() {
// Reset service (if some previous test failed)
if (service.isRecording() || service.isPaused()) {
service.endCurrentTrack();
}
// Ensure that the database is empty after every test
contentProviderUtils.deleteAllTracks(context);
}
@@ -108,8 +104,10 @@ public class TrackRecordingServiceTestLocation {
assertFalse(service.isRecording());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
assertEquals(6, trackPoints.size());
assertEquals(45.0005, trackPoints.get(5).getLatitude(), 0.01);
assertEquals(8, trackPoints.size());
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
assertEquals(45.0005, trackPoints.get(6).getLatitude(), 0.01);
assertEquals(TrackPoint.Type.SEGMENT_END_MANUAL, trackPoints.get(7).getType());
}
@MediumTest
@@ -117,6 +115,7 @@ public class TrackRecordingServiceTestLocation {
public void testOnLocationChangedAsync_slowMovingAccurate() throws Exception {
// given
Track.Id trackId = service.startNewTrack();
assertNotNull(trackId);
// when
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 5, 15);
@@ -132,8 +131,10 @@ public class TrackRecordingServiceTestLocation {
assertFalse(service.isRecording());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
assertEquals(2, trackPoints.size());
assertEquals(45.000005, trackPoints.get(1).getLatitude(), 0.01);
assertEquals(4, trackPoints.size());
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
assertEquals(45.000005, trackPoints.get(2).getLatitude(), 0.01);
assertEquals(TrackPoint.Type.SEGMENT_END_MANUAL, trackPoints.get(3).getType());
}
// @MediumTest
@@ -174,10 +175,12 @@ public class TrackRecordingServiceTestLocation {
assertFalse(service.isRecording());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
assertEquals(3, trackPoints.size());
assertEquals(1, trackPoints.get(0).getAccuracy(), 0.01);
assertEquals(2, trackPoints.get(1).getAccuracy(), 0.01);
assertEquals(6, trackPoints.get(2).getAccuracy(), 0.01);
assertEquals(5, trackPoints.size());
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
assertEquals(1, trackPoints.get(1).getAccuracy(), 0.01);
assertEquals(2, trackPoints.get(2).getAccuracy(), 0.01);
assertEquals(6, trackPoints.get(3).getAccuracy(), 0.01);
assertEquals(TrackPoint.Type.SEGMENT_END_MANUAL, trackPoints.get(4).getType());
}
@MediumTest
@@ -200,14 +203,15 @@ public class TrackRecordingServiceTestLocation {
assertFalse(service.isRecording());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
assertEquals(4, trackPoints.size());
assertEquals(1, trackPoints.get(0).getAccuracy(), 0.01);
assertEquals(2, trackPoints.get(1).getAccuracy(), 0.01);
assertEquals(5, trackPoints.get(2).getAccuracy(), 0.01); //TODO Check why this trackPoint is inserted.
assertEquals(6, trackPoints.get(3).getAccuracy(), 0.01);
assertEquals(6, trackPoints.size());
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
assertEquals(1, trackPoints.get(1).getAccuracy(), 0.01);
assertEquals(2, trackPoints.get(2).getAccuracy(), 0.01);
assertEquals(5, trackPoints.get(3).getAccuracy(), 0.01); //TODO Check why this trackPoint is inserted.
assertEquals(6, trackPoints.get(4).getAccuracy(), 0.01);
assertEquals(TrackPoint.Type.SEGMENT_END_MANUAL, trackPoints.get(5).getType());
}
@MediumTest
@Test
public void testOnLocationChangedAsync_idle_withSensorData() throws Exception {
@@ -243,13 +247,15 @@ public class TrackRecordingServiceTestLocation {
assertFalse(service.isRecording());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
assertEquals(6, trackPoints.size());
assertEquals(0, trackPoints.get(0).getAccuracy(), 0.01);
assertEquals(1, trackPoints.get(1).getAccuracy(), 0.01);
assertEquals(2, trackPoints.get(2).getAccuracy(), 0.01);
assertEquals(3, trackPoints.get(3).getAccuracy(), 0.01);
assertEquals(4, trackPoints.get(4).getAccuracy(), 0.01);
assertEquals(5, trackPoints.get(5).getAccuracy(), 0.01);
assertEquals(8, trackPoints.size());
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
assertEquals(0, trackPoints.get(1).getAccuracy(), 0.01);
assertEquals(1, trackPoints.get(2).getAccuracy(), 0.01);
assertEquals(2, trackPoints.get(3).getAccuracy(), 0.01);
assertEquals(3, trackPoints.get(4).getAccuracy(), 0.01);
assertEquals(4, trackPoints.get(5).getAccuracy(), 0.01);
assertEquals(5, trackPoints.get(6).getAccuracy(), 0.01);
assertEquals(TrackPoint.Type.SEGMENT_END_MANUAL, trackPoints.get(7).getType());
}
@MediumTest
@@ -271,13 +277,15 @@ public class TrackRecordingServiceTestLocation {
assertFalse(service.isRecording());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
assertEquals(7, trackPoints.size());
assertEquals(1, trackPoints.get(0).getAccuracy(), 0.01);
assertEquals(TrackPointsColumns.PAUSE_LATITUDE, trackPoints.get(1).getLatitude(), 0.01);
assertEquals(2, trackPoints.get(2).getAccuracy(), 0.01);
assertEquals(3, trackPoints.get(3).getAccuracy(), 0.01);
assertEquals(TrackPointsColumns.PAUSE_LATITUDE, trackPoints.get(4).getLatitude(), 0.01);
assertEquals(4, trackPoints.get(5).getAccuracy(), 0.01);
assertEquals(5, trackPoints.get(6).getAccuracy(), 0.01);
assertEquals(9, trackPoints.size());
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
assertEquals(1, trackPoints.get(1).getAccuracy(), 0.01);
assertEquals(TrackPoint.Type.SEGMENT_END_MANUAL, trackPoints.get(2).getType());
assertEquals(2, trackPoints.get(3).getAccuracy(), 0.01);
assertEquals(3, trackPoints.get(4).getAccuracy(), 0.01);
assertEquals(TrackPoint.Type.SEGMENT_END_MANUAL, trackPoints.get(5).getType());
assertEquals(4, trackPoints.get(6).getAccuracy(), 0.01);
assertEquals(5, trackPoints.get(7).getAccuracy(), 0.01);
assertEquals(TrackPoint.Type.SEGMENT_END_MANUAL, trackPoints.get(8).getType());
}
}
@@ -31,7 +31,7 @@ public class AnnouncementUtilsTest {
stats.setTotalTime(600000);
stats.setMovingTime(300000);
stats.setMaxSpeed(100);
stats.setTotalElevationGain(6000);
stats.setTotalElevationGain(6000f);
// when
String announcement = AnnouncementUtils.getAnnouncement(context, stats, "airplane", null);
@@ -47,7 +47,7 @@ public class AnnouncementUtilsTest {
stats.setTotalTime(600000);
stats.setMovingTime(300000);
stats.setMaxSpeed(100);
stats.setTotalElevationGain(6000);
stats.setTotalElevationGain(6000f);
List<TrackPoint> trackPoints = TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), 10).second;
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, 1000);
@@ -25,12 +25,12 @@ public class IntervalStatisticsTest {
}
private TrackStatistics buildTrackStatistics(List<TrackPoint> trackPoints) {
TrackStatisticsUpdater trackStatisticsUpdater = new TrackStatisticsUpdater(trackPoints.get(0).getTime());
for (TrackPoint tp : trackPoints) {
trackStatisticsUpdater.addTrackPoint(tp, 0);
}
return trackStatisticsUpdater.getTrackStatistics();
}
TrackStatisticsUpdater trackStatisticsUpdater = new TrackStatisticsUpdater();
for (TrackPoint tp : trackPoints) {
trackStatisticsUpdater.addTrackPoint(tp, 0);
}
return trackStatisticsUpdater.getTrackStatistics();
}
/**
* Tests that build method compute the distance correctly comparing the result with TrackStatisticsUpdater result.
@@ -40,13 +40,12 @@ import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
import de.dennisguse.opentracks.util.LocationUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
/**
* Track data hub.
* Receives data from {@link de.dennisguse.opentracks.content.provider.CustomContentProvider} and distributes it to {@link TrackDataListener} after some processing.
*
* <p>
* {@link TrackPoint}s are filtered/downsampled with a dynamic sampling frequency.
*
* @author Rodrigo Damazio
@@ -314,9 +313,6 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
if (cursor != null && cursor.moveToFirst()) {
do {
Marker marker = contentProviderUtils.createMarker(cursor);
if (!LocationUtils.isValidLocation(marker.getLocation())) {
continue;
}
for (TrackDataListener trackDataListener : trackDataListeners) {
trackDataListener.onNewMarker(marker);
}
@@ -362,7 +358,6 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
TrackPoint.Id lastTrackPointId = contentProviderUtils.getLastTrackPointId(selectedTrackId);
int samplingFrequency = -1;
boolean includeNextPoint = false;
TrackPoint.Id next;
@@ -390,19 +385,15 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
samplingFrequency = 1 + (int) (numTotalPoints / targetNumPoints);
}
if (!LocationUtils.isValidLocation(trackPoint.getLocation())) { //This can be split markers (not anymore supported feature)
includeNextPoint = true;
// Also include the last point if the selected track is not recording.
if ((localNumLoadedTrackPoints % samplingFrequency == 0) || (trackPointId == lastTrackPointId && !isSelectedTrackRecording())) {
for (TrackDataListener trackDataListener : sampledInListeners) {
trackDataListener.onSampledInTrackPoint(trackPoint);
}
} else {
// Also include the last point if the selected track is not recording.
if (includeNextPoint || (localNumLoadedTrackPoints % samplingFrequency == 0) || (trackPointId == lastTrackPointId && !isSelectedTrackRecording())) {
includeNextPoint = false;
for (TrackDataListener trackDataListener : sampledInListeners) {
trackDataListener.onSampledInTrackPoint(trackPoint);
}
} else {
for (TrackDataListener trackDataListener : sampledOutListeners) {
trackDataListener.onSampledOutTrackPoint(trackPoint);
}
for (TrackDataListener trackDataListener : sampledOutListeners) {
trackDataListener.onSampledOutTrackPoint(trackPoint);
}
}
@@ -28,6 +28,8 @@ import androidx.annotation.VisibleForTesting;
import java.util.Objects;
/**
* NOTE: A marker is indirectly (via it's location) assigned to one {@link TrackPoint} with trackPoint.hasLocation() == true.
*
* @author Leif Hendrik Wilden
* @author Rodrigo Damazio
*/
@@ -26,6 +26,16 @@ import java.util.Objects;
/**
* This class extends the standard Android location with extra information.
* <p>
* NOTE: default location will be latitude=0.0 and longitude=0.0 (this is not meaningful).
* <p>
* NOTE: For Locations provided by the GPS.
* We are replacing the GPS-provided time using the system time.
* Then we have the same timestamps for the user-driven events (aka start, pause, resume) and restore {@link de.dennisguse.opentracks.stats.TrackStatistics}.
* Drawbacks:
* * GPS-provided timestamp might be more precise (but also have GPS week rollover)
* * System clock might be changed (and thus non-monotonic)
* TODO: if these might be problems, we need to store both timestamps.
*
* @author Sandor Dornbush
*/
@@ -36,6 +46,41 @@ public class TrackPoint {
private final Location location;
//TODO Private
public enum Type {
SEGMENT_START_MANUAL(-2), //Start of a segment due to user interaction (start, resume); no useful coordinates
SEGMENT_START_AUTOMATIC(-1), //Start of a segment due to too much distance from previous TrackPoint
TRACKPOINT(0), //Normal trackpoint got from GPS
SEGMENT_END_MANUAL(1); //End of a segment; no useful coordinates
public final int type_db;
Type(int type_db) {
this.type_db = type_db;
}
@Override
public String toString() {
return "" + type_db;
}
public static Type getById(int id) {
for (Type e : values()) {
if (e.type_db == id) return e;
}
throw new RuntimeException("unknown id: " + id);
}
public boolean hasLocation() {
return this == SEGMENT_START_AUTOMATIC || this == TRACKPOINT;
}
}
private final Type type;
private Float heartRate_bpm = null;
private Float cyclingCadence_rpm = null;
private Float power = null;
@@ -43,14 +88,26 @@ public class TrackPoint {
private Float elevationLoss = null;
public TrackPoint() {
this(Type.TRACKPOINT, new Location(""));
}
public TrackPoint(Type type) {
this.type = type;
this.location = new Location("");
}
public TrackPoint(@NonNull Location location) {
this.type = Type.TRACKPOINT;
this.location = location;
}
public TrackPoint(@NonNull Type type, @NonNull Location location) {
this.type = type;
this.location = location;
}
public TrackPoint(@NonNull TrackPoint trackPoint) {
this.type = trackPoint.getType();
this.location = trackPoint.getLocation();
this.heartRate_bpm = trackPoint.getHeartRate_bpm();
@@ -62,6 +119,7 @@ public class TrackPoint {
}
public TrackPoint(double latitude, double longitude, Double altitude, long time) {
this.type = Type.TRACKPOINT;
location = new Location(LocationManager.GPS_PROVIDER);
location.setLatitude(latitude);
location.setLongitude(longitude);
@@ -72,31 +130,52 @@ public class TrackPoint {
}
@Deprecated //See #316
public static TrackPoint createPause() {
return createPauseWithTime(System.currentTimeMillis());
public static TrackPoint createSegmentStartManual() {
return createSegmentStartManualWithTime(System.currentTimeMillis());
}
@Deprecated //See #316
public static TrackPoint createPauseWithTime(long time) {
Location pause = new Location(LocationManager.GPS_PROVIDER);
pause.setLongitude(0);
pause.setLatitude(TrackPointsColumns.PAUSE_LATITUDE);
pause.setTime(time);
return new TrackPoint(pause);
}
@Deprecated //See #316
public static TrackPoint createResume() {
return createResumeWithTime(System.currentTimeMillis());
}
@Deprecated //See #316
public static TrackPoint createResumeWithTime(long time) {
public static TrackPoint createSegmentStartManualWithTime(long time) {
Location resume = new Location(LocationManager.GPS_PROVIDER);
resume.setLongitude(0);
resume.setLatitude(TrackPointsColumns.RESUME_LATITUDE);
resume.setTime(time);
return new TrackPoint(resume);
return new TrackPoint(Type.SEGMENT_START_MANUAL, resume);
}
@Deprecated //See #316
public static TrackPoint createSegmentStartAutomatic() {
return createSegmentStartAutomaticWithTime(System.currentTimeMillis());
}
public static TrackPoint createSegmentStartAutomaticWithTime(long time) {
Location resume = new Location(LocationManager.GPS_PROVIDER);
resume.setTime(time);
return new TrackPoint(Type.SEGMENT_START_AUTOMATIC, resume);
}
public static TrackPoint createSegmentEnd() {
return createSegmentEndWithTime(System.currentTimeMillis());
}
public static TrackPoint createSegmentEndWithTime(@NonNull TrackPoint trackPoint) {
return createSegmentEndWithTime(trackPoint.getTime());
}
public static TrackPoint createSegmentEndWithTime(long time) {
Location pause = new Location(LocationManager.GPS_PROVIDER);
pause.setTime(time);
return new TrackPoint(Type.SEGMENT_END_MANUAL, pause);
}
@NonNull
public Type getType() {
return type;
}
public boolean isSegmentStart() {
return type == Type.SEGMENT_START_AUTOMATIC || type == Type.SEGMENT_START_MANUAL;
}
public boolean isSegmentEnd() {
return type == Type.SEGMENT_END_MANUAL;
}
/**
@@ -268,7 +347,7 @@ public class TrackPoint {
@NonNull
@Override
public String toString() {
return "time=" + getTime() + ": lat=" + getLatitude() + " lng=" + getLongitude() + " acc=" + getAccuracy();
return "time=" + getTime() + " (type=" + getType() + "): lat=" + getLatitude() + " lng=" + getLongitude() + " acc=" + getAccuracy();
}
public static class Id {
@@ -37,13 +37,11 @@ public interface TrackPointsColumns extends BaseColumns {
// Columns
String TRACKID = "trackid";
// See {@link TrackPoint.Type}
String TYPE = "type";
String LONGITUDE = "longitude";
String LATITUDE = "latitude";
@Deprecated
double PAUSE_LATITUDE = 100.0;
@Deprecated
double RESUME_LATITUDE = 200.0;
String TIME = "time";
String ALTITUDE = "elevation";
@@ -71,6 +69,7 @@ public interface TrackPointsColumns extends BaseColumns {
+ SENSOR_POWER + " FLOAT, "
+ ELEVATION_GAIN + " FLOAT, "
+ ELEVATION_LOSS + " FLOAT, "
+ TYPE + " TEXT CHECK(type IN (-2, -1, 0, 1)), "
+ "FOREIGN KEY (" + TRACKID + ") REFERENCES " + TracksColumns.TABLE_NAME + "(" + TracksColumns._ID + ") ON UPDATE CASCADE ON DELETE CASCADE"
+ ")";
@@ -9,6 +9,7 @@ import de.dennisguse.opentracks.content.data.TrackPointsColumns;
*/
class CachedTrackPointsIndexes {
final int idIndex;
final int typeIndex;
final int longitudeIndex;
final int latitudeIndex;
final int timeIndex;
@@ -24,6 +25,7 @@ class CachedTrackPointsIndexes {
CachedTrackPointsIndexes(Cursor cursor) {
idIndex = cursor.getColumnIndex(TrackPointsColumns._ID);
typeIndex = cursor.getColumnIndex(TrackPointsColumns.TYPE);
longitudeIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.LONGITUDE);
latitudeIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.LATITUDE);
timeIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.TIME);
@@ -63,8 +63,6 @@ public class ContentProviderUtils {
// The base URI for the app's content provider.
public static final String CONTENT_BASE_URI = "content://" + AUTHORITY_PACKAGE;
// Maximum number of markers that will be loaded at one time.
public static final int MAX_LOADED_MARKERS = 10000;
private static final String ID_SEPARATOR = ",";
private final ContentResolver contentResolver;
@@ -544,7 +542,7 @@ public class ContentProviderUtils {
* @param indexes the cached trackPoints indexes
*/
static TrackPoint fillTrackPoint(Cursor cursor, CachedTrackPointsIndexes indexes) {
TrackPoint trackPoint = new TrackPoint();
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.getById(cursor.getInt(indexes.typeIndex)));
if (!cursor.isNull(indexes.longitudeIndex)) {
trackPoint.setId(new TrackPoint.Id(cursor.getInt(indexes.idIndex)));
}
@@ -696,7 +694,7 @@ public class ContentProviderUtils {
*/
@Deprecated
public TrackPoint getLastValidTrackPoint(Track.Id trackId) {
String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns.LATITUDE + "<=" + MAX_LATITUDE + ")";
String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns.TYPE + " IN (" + TrackPoint.Type.SEGMENT_START_AUTOMATIC + "," + TrackPoint.Type.TRACKPOINT + "))";
String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
return findTrackPointBy(selection, selectionArgs);
}
@@ -715,12 +713,15 @@ public class ContentProviderUtils {
/**
* Creates the {@link ContentValues} for a {@link TrackPoint}.
*
* @param trackPoint the trackPointstats_pace_km#87
* @param trackPoint the trackPoint
* @param trackId the track id
*/
private ContentValues createContentValues(TrackPoint trackPoint, Track.Id trackId) {
ContentValues values = new ContentValues();
values.put(TrackPointsColumns.TRACKID, trackId.getId());
if (trackPoint.getType() != null) {
values.put(TrackPointsColumns.TYPE, trackPoint.getType().type_db);
}
values.put(TrackPointsColumns.LONGITUDE, (int) (trackPoint.getLongitude() * 1E6));
values.put(TrackPointsColumns.LATITUDE, (int) (trackPoint.getLatitude() * 1E6));
@@ -329,10 +329,8 @@ public class CustomContentProvider extends ContentProvider {
}
private Uri insertTrackPoint(Uri url, ContentValues values) {
boolean hasLatitude = values.containsKey(TrackPointsColumns.LATITUDE);
boolean hasLongitude = values.containsKey(TrackPointsColumns.LONGITUDE);
boolean hasTime = values.containsKey(TrackPointsColumns.TIME);
if (!hasLatitude || !hasLongitude || !hasTime) {
if (!hasTime) {
throw new IllegalArgumentException("Latitude, longitude, and time values are required.");
}
long rowId = db.insert(TrackPointsColumns.TABLE_NAME, TrackPointsColumns._ID, values);
@@ -25,7 +25,7 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
private static final String TAG = CustomSQLiteOpenHelper.class.getSimpleName();
private static final int DATABASE_VERSION = 29;
private static final int DATABASE_VERSION = 30;
@VisibleForTesting
public static final String DATABASE_NAME = "database.db";
@@ -43,7 +43,6 @@ import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.databinding.ChartBinding;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
import de.dennisguse.opentracks.util.LocationUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
/**
@@ -71,8 +70,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
private TrackDataHub trackDataHub;
// Stats gathered from the received data
private TrackStatisticsUpdater trackStatisticsUpdater;
private long startTime;
private TrackStatisticsUpdater trackStatisticsUpdater = new TrackStatisticsUpdater();
private ChartBinding viewBinding;
@@ -198,7 +196,6 @@ public class ChartFragment extends Fragment implements TrackDataListener {
public void onTrackUpdated(Track track) {
if (isResumed()) {
if (track == null || track.getTrackStatistics() == null) {
startTime = -1L;
category = "";
return;
}
@@ -209,14 +206,13 @@ public class ChartFragment extends Fragment implements TrackDataListener {
chartView.setReportSpeed(reportSpeed);
chartView.applyReportSpeed();
}
startTime = track.getTrackStatistics().getStartTime_ms();
}
}
@Override
public void clearTrackPoints() {
if (isResumed()) {
trackStatisticsUpdater = startTime != -1L ? new TrackStatisticsUpdater(startTime) : null;
trackStatisticsUpdater = new TrackStatisticsUpdater();
pendingPoints.clear();
chartView.reset();
runOnUiThread(() -> {
@@ -261,7 +257,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
@Override
public void onNewMarker(Marker marker) {
if (isResumed() && marker != null && LocationUtils.isValidLocation(marker.getLocation())) {
if (isResumed() && marker != null) {
chartView.addMarker(marker);
}
}
@@ -327,11 +323,6 @@ public class ChartFragment extends Fragment implements TrackDataListener {
return new ChartPoint(trackStatisticsUpdater, trackPoint, chartByDistance, chartView.getMetricUnits());
}
@VisibleForTesting
void setTrackStatisticsUpdater(long time) {
trackStatisticsUpdater = new TrackStatisticsUpdater(time);
}
@VisibleForTesting
void setChartView(ChartView view) {
chartView = view;
@@ -191,8 +191,8 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
}
TrackPoint trackPoint = lastTrackPoint; //NOTE: There seems to be a race condition; just fix the symptom for now.
if (trackPoint != null) {
boolean hasFix = !LocationUtils.isLocationOld(trackPoint.getLocation());
if (trackPoint != null && trackPoint.getType().hasLocation()) {
boolean hasFix = !LocationUtils.isTrackPointOld(trackPoint);
if (!hasFix) {
lastTrackPoint = null;
@@ -28,7 +28,6 @@ import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
import de.dennisguse.opentracks.util.LocationUtils;
/**
* Track Writer for writing tracks to an {@link OutputStream}.
@@ -95,7 +94,7 @@ public class FileTrackExporter implements TrackExporter {
* I am leaving the number of markers very high which should not be a problem, because we don't try to load them into objects all at the same time.
*/
boolean hasMarkers = false;
try (Cursor cursor = contentProviderUtils.getMarkerCursor(track.getId(), null, ContentProviderUtils.MAX_LOADED_MARKERS)) {
try (Cursor cursor = contentProviderUtils.getMarkerCursor(track.getId(), null, -1)) {
if (cursor != null && cursor.moveToFirst()) {
for (int i = 0; i < cursor.getCount(); i++) {
if (Thread.interrupted()) {
@@ -136,7 +135,7 @@ public class FileTrackExporter implements TrackExporter {
setLocationTime(trackPoint, offset);
boolean isLocationValid = LocationUtils.isValidLocation(trackPoint.getLocation());
boolean isLocationValid = trackPoint.getType().hasLocation();
boolean isSegmentValid = isLocationValid && isLastLocationValid;
if (!wroteTrack && isSegmentValid) {
// Found the first two consecutive locations that are valid
@@ -156,9 +156,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
int markerPosition = -1;
Marker marker = null;
TrackPoint trackPoint = null;
TrackStatisticsUpdater trackStatisticsUpdater = new TrackStatisticsUpdater(track.getTrackStatistics().getStartTime_ms());
// TODO Should not be necessary anymore?
TrackStatisticsUpdater markerTrackStatisticsUpdater = new TrackStatisticsUpdater(track.getTrackStatistics().getStartTime_ms());
TrackStatisticsUpdater trackStatisticsUpdater = new TrackStatisticsUpdater();
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), null)) {
@@ -183,7 +181,6 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
}
trackPoint = trackPointIterator.next();
trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
markerTrackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
}
if (marker.getLocation().getTime() > trackPoint.getTime()) {
@@ -193,7 +190,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
marker = null;
} else {
// The marker trackPoint time matches the track point time
if (!LocationUtils.isValidLocation(trackPoint.getLocation())) {
if (!trackPoint.getType().hasLocation()) {
// Invalid trackPoint, load the next trackPoint
trackPoint = null;
continue;
@@ -253,8 +250,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
trackData.track.setIcon(icon);
}
if (trackData.trackStatisticsUpdater == null) {
trackData.trackStatisticsUpdater = new TrackStatisticsUpdater(trackData.importTime);
trackData.trackStatisticsUpdater.updateTime(trackData.importTime);
trackData.trackStatisticsUpdater = new TrackStatisticsUpdater();
}
trackData.track.setTrackStatistics(trackData.trackStatisticsUpdater.getTrackStatistics());
@@ -289,7 +285,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
//If not the first segment, add a pause separator if there is at least one location in the last segment.
if (trackData.numberOfSegments > 1 && trackData.lastLocationInCurrentSegment != null) {
insertLocation(TrackPoint.createPauseWithTime(trackData.lastLocationInCurrentSegment.getTime()));
insertLocation(TrackPoint.createSegmentEndWithTime(trackData.lastLocationInCurrentSegment.getTime()));
}
trackData.lastLocationInCurrentSegment = null;
}
@@ -302,7 +298,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
TrackPoint trackPoint = createTrackPoint();
if (!LocationUtils.isValidLocation(trackPoint.getLocation())) {
if (!trackPoint.getType().hasLocation()) {
Log.w(TAG, "Marker with invalid coordinates ignored: " + trackPoint.getLocation());
return;
}
@@ -361,7 +357,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
if (trackData.numberOfSegments > 1 && trackData.lastLocationInCurrentSegment == null) {
// If not the first segment, add a resume separator before adding the first location.
insertLocation(TrackPoint.createResumeWithTime(trackPoint.getTime()));
insertLocation(TrackPoint.createSegmentStartManualWithTime(trackPoint.getTime()));
}
trackData.lastLocationInCurrentSegment = trackPoint;
return trackPoint;
@@ -428,14 +424,10 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse latitude longitude: %s %s", latitude, longitude)), e);
}
if (time == null) {
trackPoint.setTime(trackData.importTime);
} else {
try {
trackPoint.setTime(StringUtils.parseTime(time));
} catch (Exception e) {
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse time: %s", time)), e);
}
try {
trackPoint.setTime(StringUtils.parseTime(time));
} catch (Exception e) {
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse time: %s", time)), e);
}
if (altitude != null) {
@@ -494,8 +486,9 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
*/
private void insertLocation(TrackPoint trackPoint) {
if (trackData.trackStatisticsUpdater == null) {
trackData.trackStatisticsUpdater = new TrackStatisticsUpdater(trackPoint.getTime() != -1L ? trackPoint.getTime() : trackData.importTime);
trackData.trackStatisticsUpdater = new TrackStatisticsUpdater();
}
//TODO I guess, we should not filter by recordingDistanceInterval on import; the data is already recorded, so we should not change it.
trackData.trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
trackData.bufferedTrackPoints.add(trackPoint);
@@ -545,9 +538,6 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
// The TrackStatisticsUpdater for the current track
TrackStatisticsUpdater trackStatisticsUpdater;
// The import time of the track.
final long importTime = System.currentTimeMillis();
// The buffered locations
final List<TrackPoint> bufferedTrackPoints = new ArrayList<>();
@@ -60,7 +60,6 @@ import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
import de.dennisguse.opentracks.util.ExportUtils;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.LocationUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.SystemUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
@@ -283,8 +282,6 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
Log.d(TAG, "Ignore startNewTrack. Already recording.");
return null;
}
long now = System.currentTimeMillis();
trackStatisticsUpdater = new TrackStatisticsUpdater(now);
// Insert a track
Track track = new Track();
@@ -296,7 +293,13 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
// Update database
track.setId(trackId);
track.setName(TrackNameUtils.getTrackName(this, trackId, now));
TrackPoint segmentStartTrackPoint = TrackPoint.createSegmentStartManual();
trackStatisticsUpdater = new TrackStatisticsUpdater();
insertTrackPoint(track, segmentStartTrackPoint);
//TODO Pass TrackPoint
track.setName(TrackNameUtils.getTrackName(this, trackId, segmentStartTrackPoint.getTime()));
String category = PreferencesUtils.getDefaultActivity(this);
track.setCategory(category);
@@ -325,8 +328,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
track.getTrackStatistics().setStopTime_ms(System.currentTimeMillis());
trackStatisticsUpdater = new TrackStatisticsUpdater(track.getTrackStatistics());
insertTrackPoint(track, TrackPoint.createPause());
insertTrackPoint(track, TrackPoint.createResume());
insertTrackPoint(track, TrackPoint.createSegmentStartManual());
// Update shared preferences.
updateRecordingState(trackId, false);
@@ -350,7 +352,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
Log.d(TAG, "Restarting track: " + track.getId());
trackStatisticsUpdater = new TrackStatisticsUpdater(track.getTrackStatistics().getStartTime_ms());
trackStatisticsUpdater = new TrackStatisticsUpdater();
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), null)) {
trackStatisticsUpdater.addTrackPoint(trackPointIterator, recordingDistanceInterval);
@@ -371,7 +373,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
// Update database
Track track = contentProviderUtils.getTrack(recordingTrackId);
if (track != null) {
insertTrackPoint(track, TrackPoint.createResume());
insertTrackPoint(track, TrackPoint.createSegmentStartManual());
}
startRecording();
@@ -424,15 +426,14 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
// Update database
Track track = contentProviderUtils.getTrack(trackId);
if (track != null) {
// If not wasPaused, add the last location
if (!wasPaused) {
if (lastTrackPoint != null) {
insertTrackPointIfNewer(track, lastTrackPoint);
}
// Update the recording track time
updateTrackTotalTime(track);
if (lastTrackPoint != null) {
insertTrackPointIfNewer(track, lastTrackPoint);
}
insertTrackPoint(track, TrackPoint.createSegmentEnd());
// Update the recording track time
updateTrackTotalTime(track);
}
ExportUtils.postWorkoutExport(this, track, new ExportServiceResultReceiver(new Handler(), this));
@@ -454,7 +455,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
if (lastTrackPoint != null) {
insertTrackPointIfNewer(track, lastTrackPoint);
}
insertTrackPoint(track, TrackPoint.createPause());
insertTrackPoint(track, TrackPoint.createSegmentEnd());
}
endRecording(false);
@@ -567,18 +568,10 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
return;
}
if (lastValidTrackPoint == null || !LocationUtils.isValidLocation(lastValidTrackPoint.getLocation())) {
// For some reason the previous first trackPoint was not stored, but currentSegmentHasLocation set true.
// Should not happen. The current segment should have a location. Just insert the current location.
insertTrackPoint(track, trackPoint);
lastTrackPoint = trackPoint;
return;
}
double distanceToLastTrackLocation = trackPoint.distanceTo(lastValidTrackPoint);
if (distanceToLastTrackLocation > maxRecordingDistance) {
insertTrackPointIfNewer(track, lastTrackPoint);
insertTrackPoint(track, TrackPoint.createPause());
insertTrackPoint(track, TrackPoint.createSegmentEndWithTime(trackPoint));
insertTrackPoint(track, trackPoint);
@@ -95,6 +95,7 @@ public interface TrackRecordingServiceInterface {
* @param trackPoint the track point object to be inserted.
* @param recordingGpsAccuracy recording GPS accuracy.
*/
@Deprecated //REMOVE
@VisibleForTesting
void newTrackPoint(TrackPoint trackPoint, int recordingGpsAccuracy);
@@ -26,15 +26,16 @@ import androidx.annotation.Nullable;
* @author Rodrigo Damazio
*/
//TODO Use null instead of Double.isInfinite
//TODO Check that data ranges are valid (not less than zero etc.)
public class TrackStatistics {
// The min and max elevation (meters) seen on this track.
private final ExtremityMonitor elevationExtremities = new ExtremityMonitor();
// The track start time. This is the system time, might not match the GPs time.
private long startTime_ms = -1L;
// The track stop time. This is the system time, might not match the GPS time.
private long stopTime_ms = -1L;
// The track start time.
private long startTime_ms;
// The track stop time.
private long stopTime_ms;
private double totalDistance_m;
// Updated when new points are received, may be stale.
@@ -44,11 +45,13 @@ public class TrackStatistics {
// The maximum speed (meters/second) that we believe is valid.
private double maxSpeed_mps;
// The total elevation gained (meters).
private Float totalElevationGain_m = null;
@Nullable
private Float totalElevationGain_m;
// The total elevation lost (meters).
private Float totalElevationLoss_m = null;
public TrackStatistics() {
reset();
}
/**
@@ -105,6 +108,21 @@ public class TrackStatistics {
}
}
public void reset() {
setStartTime_ms(0);
setTotalDistance(0);
setTotalTime(0);
setMovingTime(0);
setMaxSpeed(0);
setTotalElevationGain(null);
}
public void reset(long startTime_ms) {
reset();
setStartTime_ms(startTime_ms);
}
/**
* Gets the track start time. The number of milliseconds since epoch.
*/
@@ -112,8 +130,12 @@ public class TrackStatistics {
return startTime_ms;
}
/**
* Should only be called on start.
*/
public void setStartTime_ms(long startTime_ms) {
this.startTime_ms = startTime_ms;
setStopTime_ms(startTime_ms);
}
public long getStopTime_ms() {
@@ -121,6 +143,9 @@ public class TrackStatistics {
}
public void setStopTime_ms(long stopTime_ms) {
if (stopTime_ms < startTime_ms) {
throw new RuntimeException("stopTime cannot be less than startTime: " + startTime_ms + " " + stopTime_ms);
}
this.stopTime_ms = stopTime_ms;
}
@@ -258,7 +283,7 @@ public class TrackStatistics {
return totalElevationGain_m;
}
public void setTotalElevationGain(float totalElevationGain_m) {
public void setTotalElevationGain(Float totalElevationGain_m) {
this.totalElevationGain_m = totalElevationGain_m;
}
@@ -22,9 +22,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
import de.dennisguse.opentracks.util.LocationUtils;
import de.dennisguse.opentracks.util.TrackPointUtils;
/**
@@ -58,6 +56,9 @@ public class TrackStatisticsUpdater {
*/
private static final double MAX_ACCELERATION = 0.02;
private boolean trackInitialized = false;
private boolean segmentInitialized = false;
// The track's statistics
private final TrackStatistics trackStatistics;
@@ -67,20 +68,14 @@ public class TrackStatisticsUpdater {
private final DoubleRingBuffer speedBuffer_ms = new DoubleRingBuffer(SPEED_SMOOTHING_FACTOR);
// The current segment's statistics
private TrackStatistics currentSegment;
private final TrackStatistics currentSegment = new TrackStatistics();
// Current segment's last trackPoint
private TrackPoint lastTrackPoint;
// Current segment's last moving trackPoint
private TrackPoint lastMovingTrackPoint;
/**
* Creates a new {@link TrackStatisticsUpdater}.
*
* @param startTime_ms the start time in milliseconds
*/
public TrackStatisticsUpdater(long startTime_ms) {
trackStatistics = init(startTime_ms);
currentSegment = init(startTime_ms);
public TrackStatisticsUpdater() {
trackStatistics = new TrackStatistics();
}
/**
@@ -90,17 +85,14 @@ public class TrackStatisticsUpdater {
*/
public TrackStatisticsUpdater(TrackStatistics trackStatistics) {
this.trackStatistics = trackStatistics;
currentSegment = init(System.currentTimeMillis());
trackInitialized = true;
}
public void updateTime(long time) {
currentSegment.setStopTime_ms(time);
currentSegment.setTotalTime(time - currentSegment.getStartTime_ms());
public void updateTime(long time_ms) {
currentSegment.setStopTime_ms(time_ms);
currentSegment.setTotalTime(time_ms - currentSegment.getStartTime_ms());
}
/**
* Gets the track's statistics.
*/
public TrackStatistics getTrackStatistics() {
// Take a snapshot - we don't want anyone messing with our trackStatistics
TrackStatistics stats = new TrackStatistics(trackStatistics);
@@ -110,23 +102,30 @@ public class TrackStatisticsUpdater {
/**
* Adds a trackPoint.
* TODO: This assume trackPoint has a valid time.
*
* @param trackPoint the trackPoint
* @param minRecordingDistance the min recording distance
*/
public void addTrackPoint(TrackPoint trackPoint, int minRecordingDistance) {
if (!trackInitialized) {
trackStatistics.setStartTime_ms(trackPoint.getTime());
trackInitialized = true;
}
if (!segmentInitialized) {
currentSegment.setStartTime_ms(trackPoint.getTime());
segmentInitialized = true;
}
// Always update time
updateTime(trackPoint.getTime());
if (!LocationUtils.isValidLocation(trackPoint.getLocation())) {
// Either pause or resume marker
if (trackPoint.getLatitude() == TrackPointsColumns.PAUSE_LATITUDE) {
if (trackPoint.isSegmentStart() || trackPoint.isSegmentEnd()) {
if (trackPoint.isSegmentEnd()) {
if (lastTrackPoint != null && lastMovingTrackPoint != null && lastTrackPoint != lastMovingTrackPoint) {
currentSegment.addTotalDistance(lastMovingTrackPoint.distanceTo(lastTrackPoint));
}
trackStatistics.merge(currentSegment);
}
currentSegment = init(trackPoint.getTime());
currentSegment.reset(trackPoint.getTime());
lastTrackPoint = null;
lastMovingTrackPoint = null;
elevationBuffer_m.reset();
@@ -238,13 +237,6 @@ public class TrackStatisticsUpdater {
return newAverage - oldAverage;
}
private TrackStatistics init(long time) {
TrackStatistics stats = new TrackStatistics();
stats.setStartTime_ms(time);
stats.setStopTime_ms(time);
return stats;
}
/**
* Returns true if the speed is valid.
*/
@@ -17,6 +17,8 @@ package de.dennisguse.opentracks.util;
import android.location.Location;
import de.dennisguse.opentracks.content.data.TrackPoint;
/**
* Utility class for decimating tracks at a given level of precision.
*
@@ -34,26 +36,18 @@ public class LocationUtils {
/**
* Checks if a given location is a valid (i.e. physically possible) location on Earth.
* Note: The special separator locations (which have latitude = 100) will not qualify as valid.
* Neither will locations with lat=0 and lng=0 as these are most likely "bad" measurements which often cause trouble.
*
* @param location the location to test
* @return true if the location is a valid location.
*/
//TODO Move to TrackPoint
@Deprecated //See #316
public static boolean isValidLocation(Location location) {
return location != null
&& Math.abs(location.getLatitude()) <= 90
&& Math.abs(location.getLongitude()) <= 180;
}
/**
* Returns true if a location is old.
*
* @param location the location
*/
public static boolean isLocationOld(Location location) {
return !LocationUtils.isValidLocation(location) || (System.currentTimeMillis() - location.getTime() > MAX_LOCATION_AGE_MS);
public static boolean isTrackPointOld(TrackPoint trackPoint) {
return System.currentTimeMillis() - trackPoint.getTime() > MAX_LOCATION_AGE_MS;
}
}
@@ -1,10 +1,11 @@
package de.dennisguse.opentracks.viewmodels;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.util.LocationUtils;
import de.dennisguse.opentracks.util.UnitConversions;
public class IntervalStatistics {
@@ -15,11 +16,11 @@ public class IntervalStatistics {
* @param trackPoints the list of TrackPoint.
* @param distanceInterval_m the meters of every interval.
*/
public IntervalStatistics(List<TrackPoint> trackPoints, float distanceInterval_m) {
public IntervalStatistics(@NonNull List<TrackPoint> trackPoints, float distanceInterval_m) {
intervalList.clear();
this.distanceInterval_m = distanceInterval_m;
if (trackPoints == null || trackPoints.size() == 0) {
if (trackPoints.size() == 0) {
return;
}
@@ -30,7 +31,7 @@ public class IntervalStatistics {
TrackPoint prevTrackPoint = trackPoints.get(i - 1);
TrackPoint trackPoint = trackPoints.get(i);
if (LocationUtils.isValidLocation(trackPoint.getLocation()) && LocationUtils.isValidLocation(prevTrackPoint.getLocation())) {
if (trackPoint.getType().hasLocation() && prevTrackPoint.getType().hasLocation()) {
interval.distance_m += prevTrackPoint.distanceTo(trackPoint);
interval.time_ms += trackPoint.getTime() - prevTrackPoint.getTime();
interval.gain_m += trackPoint.hasElevationGain() ? trackPoint.getElevationGain() : 0;