From fcc17413e313360378fec287d7e4d1d8bbb32366 Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Fri, 15 Jan 2021 07:57:05 +0100 Subject: [PATCH] Segments: use TrackPoint.type. So, far specific latitude for segment start/end (i.e., PAUSE/RESUME) was used. #316. --- .../content/DescriptionGeneratorTest.java | 4 +- .../fragments/ChartFragmentTest.java | 1 - .../io/file/importer/LegacyKmlImportTest.java | 9 +- .../services/TrackRecordingServiceTest.java | 33 +++-- .../TrackRecordingServiceTestLocation.java | 78 ++++++----- .../util/AnnouncementUtilsTest.java | 4 +- .../viewmodels/IntervalStatisticsTest.java | 12 +- .../opentracks/content/TrackDataHub.java | 27 ++-- .../opentracks/content/data/Marker.java | 2 + .../opentracks/content/data/TrackPoint.java | 123 ++++++++++++++---- .../content/data/TrackPointsColumns.java | 7 +- .../provider/CachedTrackPointsIndexes.java | 2 + .../provider/ContentProviderUtils.java | 11 +- .../provider/CustomContentProvider.java | 4 +- .../provider/CustomSQLiteOpenHelper.java | 2 +- .../opentracks/fragments/ChartFragment.java | 15 +-- .../StatisticsRecordingFragment.java | 4 +- .../io/file/exporter/FileTrackExporter.java | 5 +- .../importer/AbstractFileTrackImporter.java | 34 ++--- .../services/TrackRecordingService.java | 45 +++---- .../TrackRecordingServiceInterface.java | 1 + .../opentracks/stats/TrackStatistics.java | 37 +++++- .../stats/TrackStatisticsUpdater.java | 52 ++++---- .../opentracks/util/LocationUtils.java | 14 +- .../viewmodels/IntervalStatistics.java | 9 +- 25 files changed, 300 insertions(+), 235 deletions(-) diff --git a/src/androidTest/java/de/dennisguse/opentracks/content/DescriptionGeneratorTest.java b/src/androidTest/java/de/dennisguse/opentracks/content/DescriptionGeneratorTest.java index eb16d2d4d..38011251c 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/content/DescriptionGeneratorTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/content/DescriptionGeneratorTest.java @@ -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); diff --git a/src/androidTest/java/de/dennisguse/opentracks/fragments/ChartFragmentTest.java b/src/androidTest/java/de/dennisguse/opentracks/fragments/ChartFragmentTest.java index a9d083c12..b8d20ec73 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/fragments/ChartFragmentTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/fragments/ChartFragmentTest.java @@ -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); } /** diff --git a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/LegacyKmlImportTest.java b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/LegacyKmlImportTest.java index 43a2035bf..f969fb6af 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/LegacyKmlImportTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/LegacyKmlImportTest.java @@ -65,7 +65,7 @@ public class LegacyKmlImportTest { // 3. trackpoints List 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); diff --git a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTest.java b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTest.java index ed5800dd8..b9d7b6cef 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTest.java @@ -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 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 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); } diff --git a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLocation.java b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLocation.java index 393876d47..4170829c4 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLocation.java +++ b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLocation.java @@ -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 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 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 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 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 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 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()); } } diff --git a/src/androidTest/java/de/dennisguse/opentracks/util/AnnouncementUtilsTest.java b/src/androidTest/java/de/dennisguse/opentracks/util/AnnouncementUtilsTest.java index e7ba77a55..b124100bc 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/util/AnnouncementUtilsTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/util/AnnouncementUtilsTest.java @@ -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 trackPoints = TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), 10).second; IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, 1000); diff --git a/src/androidTest/java/de/dennisguse/opentracks/viewmodels/IntervalStatisticsTest.java b/src/androidTest/java/de/dennisguse/opentracks/viewmodels/IntervalStatisticsTest.java index 04d3b4d86..976aad1db 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/viewmodels/IntervalStatisticsTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/viewmodels/IntervalStatisticsTest.java @@ -25,12 +25,12 @@ public class IntervalStatisticsTest { } private TrackStatistics buildTrackStatistics(List 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. diff --git a/src/main/java/de/dennisguse/opentracks/content/TrackDataHub.java b/src/main/java/de/dennisguse/opentracks/content/TrackDataHub.java index 789faec5b..9a0da7fa4 100644 --- a/src/main/java/de/dennisguse/opentracks/content/TrackDataHub.java +++ b/src/main/java/de/dennisguse/opentracks/content/TrackDataHub.java @@ -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. - * + *

* {@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); } } diff --git a/src/main/java/de/dennisguse/opentracks/content/data/Marker.java b/src/main/java/de/dennisguse/opentracks/content/data/Marker.java index 198e80dff..ee7ced8bb 100644 --- a/src/main/java/de/dennisguse/opentracks/content/data/Marker.java +++ b/src/main/java/de/dennisguse/opentracks/content/data/Marker.java @@ -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 */ diff --git a/src/main/java/de/dennisguse/opentracks/content/data/TrackPoint.java b/src/main/java/de/dennisguse/opentracks/content/data/TrackPoint.java index ab5fbfc4c..a29d98d97 100644 --- a/src/main/java/de/dennisguse/opentracks/content/data/TrackPoint.java +++ b/src/main/java/de/dennisguse/opentracks/content/data/TrackPoint.java @@ -26,6 +26,16 @@ import java.util.Objects; /** * This class extends the standard Android location with extra information. + *

+ * NOTE: default location will be latitude=0.0 and longitude=0.0 (this is not meaningful). + *

+ * 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 { diff --git a/src/main/java/de/dennisguse/opentracks/content/data/TrackPointsColumns.java b/src/main/java/de/dennisguse/opentracks/content/data/TrackPointsColumns.java index b1bbd3668..7c511a04e 100644 --- a/src/main/java/de/dennisguse/opentracks/content/data/TrackPointsColumns.java +++ b/src/main/java/de/dennisguse/opentracks/content/data/TrackPointsColumns.java @@ -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" + ")"; diff --git a/src/main/java/de/dennisguse/opentracks/content/provider/CachedTrackPointsIndexes.java b/src/main/java/de/dennisguse/opentracks/content/provider/CachedTrackPointsIndexes.java index 08bba72d9..f391d7916 100644 --- a/src/main/java/de/dennisguse/opentracks/content/provider/CachedTrackPointsIndexes.java +++ b/src/main/java/de/dennisguse/opentracks/content/provider/CachedTrackPointsIndexes.java @@ -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); diff --git a/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java b/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java index 05bf64904..4acb38a55 100644 --- a/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java +++ b/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java @@ -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)); diff --git a/src/main/java/de/dennisguse/opentracks/content/provider/CustomContentProvider.java b/src/main/java/de/dennisguse/opentracks/content/provider/CustomContentProvider.java index cad2a15d9..0c26eb9bf 100644 --- a/src/main/java/de/dennisguse/opentracks/content/provider/CustomContentProvider.java +++ b/src/main/java/de/dennisguse/opentracks/content/provider/CustomContentProvider.java @@ -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); diff --git a/src/main/java/de/dennisguse/opentracks/content/provider/CustomSQLiteOpenHelper.java b/src/main/java/de/dennisguse/opentracks/content/provider/CustomSQLiteOpenHelper.java index a2fa4f2ad..84117e7c6 100644 --- a/src/main/java/de/dennisguse/opentracks/content/provider/CustomSQLiteOpenHelper.java +++ b/src/main/java/de/dennisguse/opentracks/content/provider/CustomSQLiteOpenHelper.java @@ -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"; diff --git a/src/main/java/de/dennisguse/opentracks/fragments/ChartFragment.java b/src/main/java/de/dennisguse/opentracks/fragments/ChartFragment.java index 4b6794987..455a68d25 100644 --- a/src/main/java/de/dennisguse/opentracks/fragments/ChartFragment.java +++ b/src/main/java/de/dennisguse/opentracks/fragments/ChartFragment.java @@ -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; diff --git a/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordingFragment.java b/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordingFragment.java index e707ba93f..ebd34b7a5 100644 --- a/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordingFragment.java +++ b/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordingFragment.java @@ -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; diff --git a/src/main/java/de/dennisguse/opentracks/io/file/exporter/FileTrackExporter.java b/src/main/java/de/dennisguse/opentracks/io/file/exporter/FileTrackExporter.java index 693931bbd..6f40c7049 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/exporter/FileTrackExporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/exporter/FileTrackExporter.java @@ -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 diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/AbstractFileTrackImporter.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/AbstractFileTrackImporter.java index de571e285..75ec84846 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/AbstractFileTrackImporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/AbstractFileTrackImporter.java @@ -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 bufferedTrackPoints = new ArrayList<>(); diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java index c2372ff70..a62687d9c 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java @@ -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); diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceInterface.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceInterface.java index 107fef395..d580c555b 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceInterface.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceInterface.java @@ -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); diff --git a/src/main/java/de/dennisguse/opentracks/stats/TrackStatistics.java b/src/main/java/de/dennisguse/opentracks/stats/TrackStatistics.java index f3420b846..49fd74fc5 100644 --- a/src/main/java/de/dennisguse/opentracks/stats/TrackStatistics.java +++ b/src/main/java/de/dennisguse/opentracks/stats/TrackStatistics.java @@ -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; } diff --git a/src/main/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdater.java b/src/main/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdater.java index 96f7f0254..713205aa2 100644 --- a/src/main/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdater.java +++ b/src/main/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdater.java @@ -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. */ diff --git a/src/main/java/de/dennisguse/opentracks/util/LocationUtils.java b/src/main/java/de/dennisguse/opentracks/util/LocationUtils.java index 227a678a5..05c5dcc98 100644 --- a/src/main/java/de/dennisguse/opentracks/util/LocationUtils.java +++ b/src/main/java/de/dennisguse/opentracks/util/LocationUtils.java @@ -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; } } diff --git a/src/main/java/de/dennisguse/opentracks/viewmodels/IntervalStatistics.java b/src/main/java/de/dennisguse/opentracks/viewmodels/IntervalStatistics.java index 7ef57756c..7b8499fdc 100644 --- a/src/main/java/de/dennisguse/opentracks/viewmodels/IntervalStatistics.java +++ b/src/main/java/de/dennisguse/opentracks/viewmodels/IntervalStatistics.java @@ -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 trackPoints, float distanceInterval_m) { + public IntervalStatistics(@NonNull List 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;