Segments: import/export is working incl. restoring TrackStatistics.

KML exports sensor data if actually present.

#316.
This commit is contained in:
Dennis Guse
2020-12-28 21:28:39 +01:00
parent 47b2d0748a
commit ab47320216
19 changed files with 506 additions and 284 deletions
@@ -38,6 +38,7 @@ public class TestDataUtil {
* @param trackId the trackId of the track
* @param numPoints the trackPoints number in the track
*/
@Deprecated //TODO Should start with SEGMENT_START_MANUAL and end with SEGMENT_END_MANUAL.
public static Pair<Track, List<TrackPoint>> createTrack(Track.Id trackId, int numPoints) {
Track track = createTrack(trackId);
@@ -49,6 +50,51 @@ public class TestDataUtil {
return new Pair<>(track, trackPoints);
}
public static TrackData createTestingTrack(Track.Id trackId) {
Track track = createTrack(trackId);
int i = 0;
List<TrackPoint> trackPoints = List.of(
TrackPoint.createSegmentStartManualWithTime(i++ + 1),
createTrackPoint(i++),
createTrackPoint(i++),
createTrackPoint(i++),
createTrackPoint(i++, TrackPoint.Type.SEGMENT_START_AUTOMATIC),
createTrackPoint(i++),
createTrackPoint(i++),
createTrackPoint(i++),
TrackPoint.createSegmentEndWithTime(i++ + 1),
TrackPoint.createSegmentStartManualWithTime(i++),
createTrackPoint(i++),
createTrackPoint(i++),
createTrackPoint(i++),
TrackPoint.createSegmentEndWithTime(i++ + 1)
);
List<Marker> markers = List.of(
new Marker("Marker 1", "Marker description 1", "Marker category 3", "", trackId, 0.0, 0, trackPoints.get(1).getLocation(), null),
new Marker("Marker 2", "Marker description 2", "Marker category 3", "", trackId, 0.0, 0, trackPoints.get(4).getLocation(), null),
new Marker("Marker 3", "Marker description 3", "Marker category 3", "", trackId, 0.0, 0, trackPoints.get(5).getLocation(), null)
);
return new TrackData(track, trackPoints, markers);
}
public static class TrackData {
public final Track track;
public final List<TrackPoint> trackPoints;
public final List<Marker> markers;
public TrackData(Track track, List<TrackPoint> trackPoints, List<Marker> markers) {
this.track = track;
this.trackPoints = trackPoints;
this.markers = markers;
}
}
public static Track createTrackAndInsert(ContentProviderUtils contentProviderUtils, Track.Id trackId, int numPoints) {
Pair<Track, List<TrackPoint>> pair = createTrack(trackId, numPoints);
@@ -63,7 +109,7 @@ public class TestDataUtil {
* @param i the index for the TrackPoint.
*/
public static TrackPoint createTrackPoint(int i) {
TrackPoint trackPoint = new TrackPoint();
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT);
trackPoint.setLatitude(INITIAL_LATITUDE + (double) i / 10000.0);
trackPoint.setLongitude(INITIAL_LONGITUDE - (double) i / 10000.0);
trackPoint.setAccuracy((float) i / 100.0f);
@@ -79,6 +125,12 @@ public class TestDataUtil {
return trackPoint;
}
public static TrackPoint createTrackPoint(int i, TrackPoint.Type type) {
TrackPoint trackPoint = createTrackPoint(i);
trackPoint.setType(type);
return trackPoint;
}
/**
* Inserts a track with locations into the database.
*
@@ -270,7 +270,7 @@ public class CustomContentProviderUtilsTest {
Track.Id trackId2 = new Track.Id(random + 1);
Track.Id trackId3 = new Track.Id(random + 2);
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId1, 0);
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId1, 10);
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId2, 10);
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId3, 10);
@@ -282,7 +282,7 @@ public class CustomContentProviderUtilsTest {
Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
assertEquals(3, tracksCursor.getCount());
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
assertEquals(20, tracksPointsCursor.getCount());
assertEquals(30, tracksPointsCursor.getCount());
Cursor markerCursor = contentResolver.query(MarkerColumns.CONTENT_URI, null, null, null, MarkerColumns._ID);
assertEquals(1, markerCursor.getCount());
// Delete one track.
@@ -256,7 +256,7 @@ public class ChartFragmentTest {
* Add a time span here to make sure the second point is valid, the value 222 here is doesn't matter.
*/
trackPoint2.setTime(trackPoint1.getTime() + 222);
trackPoint2.setSpeed(130);
trackPoint2.setSpeed(130f);
point = chartFragment.createPendingPoint(trackPoint2);
assertEquals(130.0 * UnitConversions.MPS_TO_KMH, point.getSpeed(), 0.01);
}
@@ -281,7 +281,7 @@ public class ChartFragmentTest {
* Speed is valid if: speedDifference > Constants.MAX_ACCELERATION * timeDifference speedDifference = 102 -100 timeDifference = 222
*/
trackPoint2.setTime(trackPoint2.getTime() + 222);
trackPoint2.setSpeed(102);
trackPoint2.setSpeed(102f);
point = chartFragment.createPendingPoint(trackPoint2);
assertEquals(102.0 * UnitConversions.MPS_TO_KMH * UnitConversions.KM_TO_MI, point.getSpeed(), 0.01);
}
@@ -306,7 +306,7 @@ public class ChartFragmentTest {
* Speed is valid if: speedDifference > Constants.MAX_ACCELERATION * timeDifference speedDifference = 102 -100 timeDifference = 222
*/
trackPoint2.setTime(trackPoint2.getTime() + 222);
trackPoint2.setSpeed(102);
trackPoint2.setSpeed(102f);
point = chartFragment.createPendingPoint(trackPoint2);
assertEquals(HOURS_PER_UNIT / (102.0 * UnitConversions.MPS_TO_KMH), point.getPace(), 0.01);
}
@@ -318,7 +318,7 @@ public class ChartFragmentTest {
public void testCreatePendingPoint_pace_zeroSpeed() {
chartFragment.setReportSpeed(false);
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint();
trackPoint.setSpeed(0);
trackPoint.setSpeed(0f);
ChartPoint point = chartFragment.createPendingPoint(trackPoint);
assertEquals(0.0, point.getPace(), 0.01);
}
@@ -0,0 +1,53 @@
package de.dennisguse.opentracks.io.file.exporter;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.io.ByteArrayOutputStream;
import java.time.Instant;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import static org.junit.Assert.assertEquals;
@RunWith(JUnit4.class)
public class KmlTrackWriterTest {
private final Context context = ApplicationProvider.getApplicationContext();
/**
* Sensor data by type should only be created if present in at least on TrackPoint.
*/
@Test
public void writeCloseSegment_only_write_sensordata_if_present() {
String expected = "<when>1970-01-01T00:00:00Z</when>\n" +
"<gx:coord/>\n" +
"<ExtendedData>\n" +
"<SchemaData schemaUrl=\"#schema\">\n" +
"</SchemaData>\n" +
"</ExtendedData>\n" +
"</gx:Track>\n";
// given
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochSecond(0));
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
KmlTrackWriter kmlTrackWriter = (KmlTrackWriter) TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.newTrackWriter(context);
kmlTrackWriter.prepare(outputStream);
kmlTrackWriter.writeTrackPoint(trackPoint);
// when
kmlTrackWriter.writeCloseSegment();
kmlTrackWriter.close();
// then
assertEquals(expected, outputStream.toString());
}
}
@@ -1,15 +1,21 @@
package de.dennisguse.opentracks.io.file.importer;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.os.Looper;
import android.util.Log;
import android.util.Pair;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.LargeTest;
import androidx.test.rule.GrantPermissionRule;
import androidx.test.rule.ServiceTestRule;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -19,15 +25,18 @@ import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.TestDataUtil;
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.io.file.TrackFileFormat;
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils;
@@ -45,6 +54,20 @@ public class ExportImportTest {
private static final String TAG = ExportImportTest.class.getSimpleName();
@Rule
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
@Rule
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
@BeforeClass
public static void preSetUp() {
// Prepare looper for Android's message queue
if (Looper.myLooper() == null) Looper.prepare();
}
private TrackRecordingServiceInterface service;
private final Context context = ApplicationProvider.getApplicationContext();
private final ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
@@ -53,37 +76,44 @@ public class ExportImportTest {
private static final String TRACK_CATEGORY = "the category";
private static final String TRACK_DESCRIPTION = "the description";
private final List<Marker> markers = new ArrayList<>();
private final List<TrackPoint> trackPoints = new ArrayList<>();
private Track track;
private List<Marker> markers = new ArrayList<>();
private List<TrackPoint> trackPoints = new ArrayList<>();
private Track.Id trackId;
private Track.Id importTrackId;
private final Track.Id trackId = new Track.Id(System.currentTimeMillis());
@Before
public void setUp() {
Pair<Track, List<TrackPoint>> track = TestDataUtil.createTrack(trackId, 10);
track.first.setIcon(TRACK_ICON);
track.first.setCategory(TRACK_CATEGORY);
track.first.setDescription(TRACK_DESCRIPTION);
contentProviderUtils.insertTrack(track.first);
contentProviderUtils.bulkInsertTrackPoint(track.second, track.first.getId());
public void setUp() throws TimeoutException, InterruptedException {
service = ((TrackRecordingServiceInterface) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)));
trackPoints.clear();
trackPoints.addAll(track.second);
trackId = service.startNewTrack();
for (int i = 0; i < 3; i++) {
Marker marker = new Marker(trackId, track.second.get(i).getLocation());
marker.setName("the marker " + i);
marker.setDescription("the marker description " + i);
marker.setCategory("the marker category" + i);
marker.setIcon("the marker icon" + i);
marker.setPhotoUrl("the photo url" + i);
contentProviderUtils.insertMarker(marker);
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14, 10, 15, 10, 0, 66, 3, 50), 0);
service.insertMarker("Marker 1", "Marker 1 category", "Marker 1 desc", null);
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.001, 10, 15, 10, 0, 66, 3, 50), 0);
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.002, 10, 15, 10, 0, 66, 3, 50), 0);
service.insertMarker("Marker 2", "Marker 2 category", "Marker 2 desc", null);
Thread.sleep(10);
service.pauseCurrentTrack();
markers.add(marker);
}
service.resumeCurrentTrack();
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.003, 10, 15, 10, 0, 66, 3, 50), 0);
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 16, 10, 15, 10, 0, 66, 3, 50), 0);
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 16.001, 10, 15, 10, 0, 66, 3, 50), 0);
service.endCurrentTrack();
assertEquals(markers.size(), contentProviderUtils.getMarkerCount(trackId));
track = contentProviderUtils.getTrack(trackId);
track.setIcon(TRACK_ICON);
track.setCategory(TRACK_CATEGORY);
track.setDescription(TRACK_DESCRIPTION);
contentProviderUtils.updateTrack(track);
track = contentProviderUtils.getTrack(trackId);
trackPoints = contentProviderUtils.getTrackPoints(trackId);
markers = contentProviderUtils.getMarkers(trackId);
assertEquals(10, trackPoints.size());
assertEquals(2, markers.size());
}
@After
@@ -94,6 +124,7 @@ public class ExportImportTest {
}
}
@Ignore("Not implemented")
@LargeTest
@Test
public void kml_only_track() {
@@ -130,11 +161,14 @@ public class ExportImportTest {
assertEquals(track.getIcon(), importedTrack.getIcon());
assertEquals(track.getUuid(), importedTrack.getUuid());
// 2. markers
assertMarkers();
// 3. trackpoints
// 2. trackpoints
assertTrackpoints(false, false, false, false, false);
// 3. trackstatistics
assertTrackStatistics();
// 4. markers
assertMarkers();
}
@LargeTest
@@ -165,11 +199,14 @@ public class ExportImportTest {
assertEquals(track.getName(), importedTrack.getName());
assertEquals(track.getIcon(), importedTrack.getIcon());
// 2. markers
assertMarkers();
// 3. trackpoints
// 2. trackpoints
assertTrackpoints(true, true, true, true, true);
// 2. trackstatistics
assertTrackStatistics();
// 4. markers
assertMarkers();
}
@LargeTest
@@ -258,11 +295,14 @@ public class ExportImportTest {
//TODO exporting and importing a track icon is not yet supported by GpxTrackWriter.
//assertEquals(track.getIcon(), trackImported.getIcon());
// 2. markers
assertMarkers();
// 3. trackpoints
// 2. trackpoints
assertTrackpoints(false, true, true, true, true);
// 3. trackstatistics
assertTrackStatistics();
// 4. markers
assertMarkers();
}
@LargeTest
@@ -318,14 +358,31 @@ public class ExportImportTest {
TrackPoint importedTrackPoint = importedTrackPoints.get(i);
assertEquals(trackPoint.getTime(), importedTrackPoint.getTime(), 0.01);
TrackPoint.Type type = trackPoint.getType();
assertEquals("" + i, trackPoint.getType(), importedTrackPoint.getType());
// TODO Not exported for GPX/KML
// assertEquals(trackPoint.getAccuracy(), importedTrackPoint.getAccuracy(), 0.01);
assertEquals(trackPoint.getLatitude(), importedTrackPoint.getLatitude(), 0.001);
assertEquals(trackPoint.getLongitude(), importedTrackPoint.getLongitude(), 0.001);
assertEquals(trackPoint.getAltitude(), importedTrackPoint.getAltitude(), 0.001);
assertEquals(trackPoint.getSpeed(), importedTrackPoint.getSpeed(), 0.001);
assertEquals(trackPoint.hasLocation(), importedTrackPoint.hasLocation());
if (trackPoint.hasLocation()) {
assertEquals(trackPoint.getLatitude(), importedTrackPoint.getLatitude(), 0.001);
assertEquals(trackPoint.getLongitude(), importedTrackPoint.getLongitude(), 0.001);
}
assertEquals(trackPoint.hasSpeed(), importedTrackPoint.hasSpeed());
if (trackPoint.hasSpeed()) {
assertEquals(trackPoint.getSpeed(), importedTrackPoint.getSpeed(), 0.001);
}
assertEquals(trackPoint.hasAltitude(), importedTrackPoint.hasAltitude());
if (trackPoint.hasAltitude()) {
assertEquals(trackPoint.getAltitude(), importedTrackPoint.getAltitude(), 0.001);
}
if (type.equals(TrackPoint.Type.SEGMENT_START_MANUAL) || type.equals(TrackPoint.Type.SEGMENT_END_MANUAL)) {
//TODO REMOVE
continue;
}
if (verifyHeartrate) {
assertEquals(trackPoint.getHeartRate_bpm(), importedTrackPoint.getHeartRate_bpm(), 0.01);
}
@@ -343,4 +400,50 @@ public class ExportImportTest {
}
}
}
private void assertTrackStatistics() {
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
assertNotNull(importedTrack.getTrackStatistics());
TrackStatistics trackStatistics = track.getTrackStatistics();
TrackStatistics importedTrackStatistics = importedTrack.getTrackStatistics();
// Time
assertEquals(trackStatistics.getStartTime_ms(), trackStatistics.getStartTime_ms(), 0.01);
assertEquals(trackStatistics.getStopTime_ms(), trackStatistics.getStopTime_ms());
assertEquals(trackStatistics.getTotalTime(), trackStatistics.getTotalTime());
assertEquals(trackStatistics.getMovingTime(), trackStatistics.getMovingTime());
// Distance
assertEquals(trackStatistics.getTotalDistance(), trackStatistics.getTotalDistance(), 0.01);
// Speed
assertEquals(trackStatistics.getMaxSpeed(), trackStatistics.getMaxSpeed(), 0.01);
assertEquals(trackStatistics.getAverageSpeed(), trackStatistics.getAverageSpeed(), 0.01);
assertEquals(trackStatistics.getAverageMovingSpeed(), trackStatistics.getAverageMovingSpeed(), 0.01);
// Elevation
assertEquals(trackStatistics.getMinElevation(), trackStatistics.getMinElevation(), 0.01);
assertEquals(trackStatistics.getMaxElevation(), trackStatistics.getMaxElevation(), 0.01);
assertEquals(trackStatistics.getTotalElevationGain(), trackStatistics.getTotalElevationGain(), 0.01);
assertEquals(trackStatistics.getTotalElevationLoss(), trackStatistics.getTotalElevationLoss(), 0.01);
}
private static TrackPoint createTrackPoint(long time, double latitude, double longitude, float accuracy, long speed, long altitude, float elevationGain, float heartRate, float cyclingCadence, float power) {
Location location = new Location("gps");
location.setTime(time);
location.setLongitude(longitude);
location.setLatitude(latitude);
location.setAccuracy(accuracy);
location.setAltitude(altitude);
location.setSpeed(speed);
TrackPoint tp = new TrackPoint(location);
tp.setHeartRate_bpm(heartRate);
tp.setCyclingCadence_rpm(cyclingCadence);
tp.setPower(power);
tp.setElevationGain(elevationGain);
return tp;
}
}
@@ -65,25 +65,19 @@ public class LegacyKmlImportTest {
// 3. trackpoints
List<TrackPoint> importedTrackPoints = contentProviderUtils.getTrackPoints(importTrackId);
assertEquals(8, importedTrackPoints.size()); // 6 trackpoints + segmentEnd and segmentStart
assertEquals(6, importedTrackPoints.size());
// first 3 trackpoints
assertEquals(TrackPoint.Type.SEGMENT_START_AUTOMATIC, importedTrackPoints.get(0).getType());
assertTrackpoint(importedTrackPoints.get(0), "2020-11-28T17:06:22.401Z", 1.234156, 12.340097, 469.286376953125);
assertTrackpoint(importedTrackPoints.get(1), "2020-11-28T17:06:25.448Z", 1.23415, 12.340036, 439.1626281738281);
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, 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, 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);
assertTrackpoint(importedTrackPoints.get(6), "2020-11-28T17:06:56.905Z", 1.23405, 12.340057, 419.9036560058594);
assertTrackpoint(importedTrackPoints.get(7), "2020-11-28T17:07:20.870Z", 1.234046, 12.340082, 417.99432373046875);
assertEquals(TrackPoint.Type.SEGMENT_START_AUTOMATIC, importedTrackPoints.get(3).getType());
assertTrackpoint(importedTrackPoints.get(3), "2020-11-28T17:06:55.861Z", 1.23405, 12.340057, 419.93902587890625);
assertTrackpoint(importedTrackPoints.get(4), "2020-11-28T17:06:56.905Z", 1.23405, 12.340057, 419.9036560058594);
assertTrackpoint(importedTrackPoints.get(5), "2020-11-28T17:07:20.870Z", 1.234046, 12.340082, 417.99432373046875);
}
private void assertTrackpoint(final TrackPoint trackPoint, final String when, final double longitude, final double latitude, final double altitude) {
@@ -50,7 +50,7 @@ public class HandlerServerTest {
@Test
public void sendTrackPoint() throws InterruptedException {
// given
TrackPoint trackPoint = new TrackPoint();
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT);
int accuracy = 50;
// when
@@ -44,6 +44,8 @@ public final class Marker {
//TODO It is the distance from the track starting point; rename to something more meaningful
private double length = 0.0;
private long duration = 0;
@Deprecated //TODO Replace by storing latitude/longitude directly.
private final Location location;
@Deprecated //TODO Make an URI instead of String
private String photoUrl = "";
@@ -16,7 +16,6 @@
package de.dennisguse.opentracks.content.data;
import android.location.Location;
import android.location.LocationManager;
import android.os.Parcel;
import androidx.annotation.NonNull;
@@ -40,21 +39,25 @@ import java.util.Objects;
*
* @author Sandor Dornbush
*/
//TODO Merge constructors by use case; we have too many.
public class TrackPoint {
private TrackPoint.Id id;
private final Location location;
private long time_ms;
private Double latitude;
private Double longitude;
private Float accuracy;
private Double altitude_m;
private Float speed_mps;
private Float bearing;
//TODO Private
public enum Type {
SEGMENT_START_MANUAL(-2), //Start of a segment due to user interaction (start, resume); no useful coordinates
SEGMENT_START_MANUAL(-2), //Start of a segment due to user interaction (start, resume)
SEGMENT_START_AUTOMATIC(-1), //Start of a segment due to too much distance from previous TrackPoint
TRACKPOINT(0), //Normal trackpoint got from GPS
TRACKPOINT(0), //Just GPS data.
SEGMENT_END_MANUAL(1); //End of a segment; no useful coordinates
SEGMENT_END_MANUAL(1); //End of a segment
public final int type_db;
@@ -64,7 +67,7 @@ public class TrackPoint {
@Override
public String toString() {
return "" + type_db;
return name() + "(" + type_db + ")";
}
public static Type getById(int id) {
@@ -74,10 +77,6 @@ public class TrackPoint {
throw new RuntimeException("unknown id: " + id);
}
public boolean hasLocation() {
return this == SEGMENT_START_AUTOMATIC || this == TRACKPOINT;
}
}
@NonNull
@@ -89,35 +88,33 @@ public class TrackPoint {
private Float elevationGain = null;
private Float elevationLoss = null;
public TrackPoint() {
this(Type.TRACKPOINT, new Location(""));
}
public TrackPoint(@NonNull Type type) {
this.type = type;
this.location = new Location("");
}
public TrackPoint(@NonNull Location location) {
this.type = Type.TRACKPOINT;
this.location = location;
this(Type.TRACKPOINT);
this.latitude = location.getLatitude();
this.longitude = location.getLongitude();
this.altitude_m = location.getAltitude();
this.speed_mps = location.getSpeed();
this.accuracy = location.getAccuracy();
setTime(System.currentTimeMillis());
}
public TrackPoint(@NonNull Type type, @NonNull Location location) {
this.type = type;
this.location = location;
public TrackPoint(@NonNull Type type, long time_ms) {
this(type);
this.time_ms = time_ms;
}
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);
if (altitude != null) {
location.setAltitude(altitude);
}
location.setTime(time);
this(Type.TRACKPOINT);
this.latitude = latitude;
this.longitude = longitude;
this.altitude_m = altitude;
this.time_ms = time;
}
@Deprecated //See #316
@@ -126,9 +123,7 @@ public class TrackPoint {
}
public static TrackPoint createSegmentStartManualWithTime(long time) {
Location resume = new Location(LocationManager.GPS_PROVIDER);
resume.setTime(time);
return new TrackPoint(Type.SEGMENT_START_MANUAL, resume);
return new TrackPoint(Type.SEGMENT_START_MANUAL, time);
}
@Deprecated //See #316
@@ -137,9 +132,7 @@ public class TrackPoint {
}
public static TrackPoint createSegmentStartAutomaticWithTime(long time) {
Location resume = new Location(LocationManager.GPS_PROVIDER);
resume.setTime(time);
return new TrackPoint(Type.SEGMENT_START_AUTOMATIC, resume);
return new TrackPoint(Type.SEGMENT_START_AUTOMATIC, time);
}
public static TrackPoint createSegmentEnd() {
@@ -151,9 +144,7 @@ public class TrackPoint {
}
public static TrackPoint createSegmentEndWithTime(long time) {
Location pause = new Location(LocationManager.GPS_PROVIDER);
pause.setTime(time);
return new TrackPoint(Type.SEGMENT_END_MANUAL, pause);
return new TrackPoint(Type.SEGMENT_END_MANUAL, time);
}
@NonNull
@@ -185,8 +176,28 @@ public class TrackPoint {
this.id = id;
}
public boolean hasLocation() {
return latitude != null || longitude != null;
}
@Nullable
public Location getLocation() {
Location location = new Location("");
location.setTime(time_ms);
if (hasLocation()) {
location.setLatitude(latitude);
location.setLongitude(longitude);
}
if (hasBearing()) {
location.setBearing(bearing);
}
if (hasAccuracy()) {
location.setAccuracy(accuracy);
}
if (hasAltitude()) {
location.setAltitude(altitude_m);
}
return location;
}
@@ -194,7 +205,7 @@ public class TrackPoint {
return elevationGain != null;
}
public float getElevationGain() {
public Float getElevationGain() {
return elevationGain;
}
@@ -215,87 +226,87 @@ public class TrackPoint {
}
public double getLatitude() {
return location.getLatitude();
return latitude;
}
public void setLatitude(double latitude) {
location.setLatitude(latitude);
this.latitude = latitude;
}
public double getLongitude() {
return location.getLongitude();
return longitude;
}
public void setLongitude(double longitude) {
location.setLongitude(longitude);
this.longitude = longitude;
}
public long getTime() {
return location.getTime();
return time_ms;
}
public void setTime(long time) {
location.setTime(time);
this.time_ms = time;
}
public boolean hasAltitude() {
return location.hasAltitude();
return altitude_m != null;
}
public double getAltitude() {
return location.getAltitude();
return altitude_m;
}
public void setAltitude(double altitude) {
location.setAltitude(altitude);
this.altitude_m = altitude;
}
public boolean hasSpeed() {
return location.hasSpeed();
return speed_mps != null;
}
public float getSpeed() {
return location.getSpeed();
return speed_mps;
}
public void setSpeed(float speed) {
location.setSpeed(speed);
public void setSpeed(Float speed) {
this.speed_mps = speed;
}
public boolean hasBearing() {
return location.hasBearing();
return bearing != null;
}
public float getBearing() {
return location.getBearing();
return bearing;
}
public void setBearing(float bearing) {
location.setBearing(bearing);
public void setBearing(Float bearing) {
this.bearing = bearing;
}
public boolean hasAccuracy() {
return location.hasAccuracy();
return accuracy != null;
}
public float getAccuracy() {
return location.getAccuracy();
return accuracy;
}
public void setAccuracy(float horizontalAccuracy) {
location.setAccuracy(horizontalAccuracy);
this.accuracy = horizontalAccuracy;
}
public float distanceTo(@NonNull TrackPoint dest) {
return location.distanceTo(dest.getLocation());
return getLocation().distanceTo(dest.getLocation());
}
public float bearingTo(@NonNull TrackPoint dest) {
return location.bearingTo(dest.getLocation());
return getLocation().bearingTo(dest.getLocation());
}
public float bearingTo(@NonNull Location dest) {
return location.bearingTo(dest);
return getLocation().bearingTo(dest);
}
// Sensor data
@@ -342,7 +353,16 @@ public class TrackPoint {
@NonNull
@Override
public String toString() {
return "time=" + getTime() + " (type=" + getType() + "): lat=" + getLatitude() + " lng=" + getLongitude() + " acc=" + getAccuracy();
String result = "time=" + getTime() + " (type=" + getType() + ")";
if (!hasLocation()) {
return result;
}
result += ": lat=" + getLatitude() + " lng=" + getLongitude();
if (!hasAccuracy()) {
return result;
}
return result + " acc=" + getAccuracy();
}
public static class Id {
@@ -543,9 +543,7 @@ public class ContentProviderUtils {
*/
static TrackPoint fillTrackPoint(Cursor cursor, CachedTrackPointsIndexes indexes) {
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)));
}
trackPoint.setId(new TrackPoint.Id(cursor.getInt(indexes.idIndex)));
if (!cursor.isNull(indexes.longitudeIndex)) {
trackPoint.setLongitude(((double) cursor.getInt(indexes.longitudeIndex)) / 1E6);
@@ -694,7 +692,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.TYPE + " IN (" + TrackPoint.Type.SEGMENT_START_AUTOMATIC + "," + TrackPoint.Type.TRACKPOINT + "))";
String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns.TYPE + " IN (" + TrackPoint.Type.SEGMENT_START_AUTOMATIC.type_db + "," + TrackPoint.Type.TRACKPOINT.type_db + "))";
String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
return findTrackPointBy(selection, selectionArgs);
}
@@ -719,12 +717,12 @@ public class ContentProviderUtils {
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));
values.put(TrackPointsColumns.TYPE, trackPoint.getType().type_db);
if (trackPoint.hasLocation()) {
values.put(TrackPointsColumns.LONGITUDE, (int) (trackPoint.getLongitude() * 1E6));
values.put(TrackPointsColumns.LATITUDE, (int) (trackPoint.getLatitude() * 1E6));
}
values.put(TrackPointsColumns.TIME, trackPoint.getTime());
if (trackPoint.hasAltitude()) {
values.put(TrackPointsColumns.ALTITUDE, trackPoint.getAltitude());
@@ -12,6 +12,7 @@ import de.dennisguse.opentracks.content.data.TrackPoint;
/**
* A lightweight wrapper around the original {@link Cursor} with a method to clean up.
*/
//TODO Remove batching; that should be handled by the database/contentprovider (i.e., already in place as we use a cursor)!
public class TrackPointIterator implements Iterator<TrackPoint>, AutoCloseable {
private static final String TAG = TrackPointIterator.class.getSimpleName();
@@ -22,7 +23,6 @@ public class TrackPointIterator implements Iterator<TrackPoint>, AutoCloseable {
private TrackPoint.Id lastTrackPointId = null;
private Cursor cursor;
public TrackPointIterator(ContentProviderUtils contentProviderUtils, Track.Id trackId, TrackPoint.Id startTrackPointId) {
this.contentProviderUtils = contentProviderUtils;
this.trackId = trackId;
@@ -191,7 +191,7 @@ 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 && trackPoint.getType().hasLocation()) {
if (trackPoint != null && trackPoint.hasLocation()) {
boolean hasFix = !LocationUtils.isTrackPointOld(trackPoint);
if (!hasFix) {
@@ -45,8 +45,8 @@ public class FileTrackExporter implements TrackExporter {
/**
* Constructor.
*
* @param contentProviderUtils the content provider utils
* @param trackWriter the track writer
* @param contentProviderUtils the content provider utils
* @param trackWriter the track writer
*/
public FileTrackExporter(ContentProviderUtils contentProviderUtils, TrackWriter trackWriter) {
this.contentProviderUtils = contentProviderUtils;
@@ -70,6 +70,7 @@ public class FileTrackExporter implements TrackExporter {
if (hasMultipleTracks) {
trackWriter.writeMultiTrackBegin();
}
//TODO Why use startTime of first track for the others?
long startTime = tracks[0].getTrackStatistics().getStartTime_ms();
for (Track track : tracks) {
long offset = track.getTrackStatistics().getStartTime_ms() - startTime;
@@ -122,50 +123,40 @@ public class FileTrackExporter implements TrackExporter {
private void writeLocations(Track track, long offset) throws InterruptedException {
boolean wroteTrack = false;
boolean wroteSegment = false;
boolean isLastLocationValid = false;
TrackPoint lastTrackPoint = null;
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), null)) {
while (trackPointIterator.hasNext()) {
if (Thread.interrupted()) {
throw new InterruptedException();
}
TrackPoint trackPoint = trackPointIterator.next();
if (Thread.interrupted()) throw new InterruptedException();
TrackPoint trackPoint = trackPointIterator.next();
setLocationTime(trackPoint, offset);
boolean isLocationValid = trackPoint.getType().hasLocation();
boolean isSegmentValid = isLocationValid && isLastLocationValid;
if (!wroteTrack && isSegmentValid) {
// Found the first two consecutive locations that are valid
trackWriter.writeBeginTrack(track, lastTrackPoint);
if (!wroteTrack) {
trackWriter.writeBeginTrack(track, trackPoint);
wroteTrack = true;
}
if (isSegmentValid) {
if (!wroteSegment) {
// Start a segment
trackWriter.writeOpenSegment();
wroteSegment = true;
// Write the previous trackPoint, which we had previously skipped
trackWriter.writeTrackPoint(lastTrackPoint);
}
// Write the current trackPoint
boolean newSegment = TrackPoint.Type.SEGMENT_START_AUTOMATIC.equals(trackPoint.getType()) || TrackPoint.Type.SEGMENT_START_MANUAL.equals(trackPoint.getType());
if (newSegment) {
if (wroteSegment) trackWriter.writeCloseSegment();
trackWriter.writeOpenSegment();
trackWriter.writeTrackPoint(trackPoint);
} else {
if (wroteSegment) {
trackWriter.writeCloseSegment();
wroteSegment = false;
}
wroteSegment = true;
continue;
}
lastTrackPoint = trackPoint;
isLastLocationValid = isLocationValid;
if (TrackPoint.Type.SEGMENT_END_MANUAL.equals(trackPoint.getType())) {
if (!wroteSegment) trackWriter.writeOpenSegment();
trackWriter.writeTrackPoint(trackPoint);
trackWriter.writeCloseSegment();
wroteSegment = false;
continue;
}
trackWriter.writeTrackPoint(trackPoint);
}
if (wroteSegment) {
// Should not be necessary as tracks should end with SEGMENT_END_MANUAL.
//Close the last segment
trackWriter.writeCloseSegment();
}
@@ -188,6 +179,7 @@ public class FileTrackExporter implements TrackExporter {
* @param trackPoint the trackPoint
* @param offset the time offset
*/
//TODO Why?
private void setLocationTime(TrackPoint trackPoint, long offset) {
if (trackPoint != null) {
trackPoint.setTime(trackPoint.getTime() - offset);
@@ -190,7 +190,9 @@ public class GpxTrackWriter implements TrackWriter {
@Override
public void writeTrackPoint(TrackPoint trackPoint) {
if (printWriter != null) {
printWriter.println("<trkpt " + formatLocation(trackPoint.getLocation()) + ">");
String coordinates = trackPoint.hasLocation() ? " " + formatLocation(trackPoint.getLocation()) : "";
printWriter.println("<trkpt " + coordinates + ">");
if (trackPoint.hasAltitude()) {
printWriter.println("<ele>" + ELEVATION_FORMAT.format(trackPoint.getAltitude()) + "</ele>");
}
@@ -22,6 +22,7 @@ import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.DescriptionGenerator;
@@ -239,6 +240,8 @@ public class KmlTrackWriter implements TrackWriter {
powerList.clear();
cadenceList.clear();
heartRateList.clear();
elevationGainList.clear();
elevationLossList.clear();
}
}
@@ -247,23 +250,23 @@ public class KmlTrackWriter implements TrackWriter {
if (printWriter != null) {
printWriter.println("<ExtendedData>");
printWriter.println("<SchemaData schemaUrl=\"#" + SCHEMA_ID + "\">");
if (speedList.size() > 0) {
if (speedList.stream().anyMatch(Objects::nonNull)) {
writeSimpleArrayData(speedList, EXTENDED_DATA_TYPE_SPEED);
}
if (exportSensorData) {
if (powerList.size() > 0) {
if (powerList.stream().anyMatch(Objects::nonNull)) {
writeSimpleArrayData(powerList, EXTENDED_DATA_TYPE_POWER);
}
if (cadenceList.size() > 0) {
if (cadenceList.stream().anyMatch(Objects::nonNull)) {
writeSimpleArrayData(cadenceList, EXTENDED_DATA_TYPE_CADENCE);
}
if (heartRateList.size() > 0) {
if (heartRateList.stream().anyMatch(Objects::nonNull)) {
writeSimpleArrayData(heartRateList, EXTENDED_DATA_TYPE_HEART_RATE);
}
if (elevationGainList.size() > 0) {
if (elevationGainList.stream().anyMatch(Objects::nonNull)) {
writeSimpleArrayData(elevationGainList, EXTENDED_DATA_TYPE_ELEVATION_GAIN);
}
if (elevationLossList.size() > 0) {
if (elevationLossList.stream().anyMatch(Objects::nonNull)) {
writeSimpleArrayData(elevationLossList, EXTENDED_DATA_TYPE_ELEVATION_LOSS);
}
}
@@ -280,28 +283,20 @@ public class KmlTrackWriter implements TrackWriter {
printWriter.println("<when>" + getTime(trackPoint.getLocation()) + "</when>");
}
printWriter.println("<gx:coord>" + getCoordinates(trackPoint.getLocation(), " ") + "</gx:coord>");
if (trackPoint.hasSpeed()) {
speedList.add(trackPoint.getSpeed());
if (trackPoint.hasLocation()) {
printWriter.println("<gx:coord>" + (trackPoint.hasLocation() ? getCoordinates(trackPoint.getLocation(), " ") : "") + "</gx:coord>");
} else {
printWriter.println("<gx:coord/>");
}
speedList.add(trackPoint.hasSpeed() ? trackPoint.getSpeed() : null);
if (exportSensorData) {
if (trackPoint.hasHeartRate()) {
heartRateList.add(trackPoint.getHeartRate_bpm());
}
if (trackPoint.hasCyclingCadence()) {
cadenceList.add(trackPoint.getCyclingCadence_rpm());
}
if (trackPoint.hasPower()) {
powerList.add(trackPoint.getPower());
}
if (trackPoint.hasElevationGain()) {
elevationGainList.add(trackPoint.getElevationGain());
}
if (trackPoint.hasElevationLoss()) {
elevationLossList.add(trackPoint.getElevationLoss());
}
heartRateList.add(trackPoint.hasHeartRate() ? trackPoint.getHeartRate_bpm() : null);
cadenceList.add(trackPoint.hasCyclingCadence() ? trackPoint.getCyclingCadence_rpm() : null);
powerList.add(trackPoint.hasPower() ? trackPoint.getPower() : null);
elevationGainList.add(trackPoint.hasElevationGain() ? trackPoint.getElevationGain() : null);
elevationLossList.add(trackPoint.hasElevationLoss() ? trackPoint.getElevationLoss() : null);
}
}
}
@@ -315,7 +310,12 @@ public class KmlTrackWriter implements TrackWriter {
private void writeSimpleArrayData(List<Float> list, String name) {
printWriter.println("<gx:SimpleArrayData name=\"" + name + "\">");
for (int i = 0; i < list.size(); i++) {
printWriter.println("<gx:value>" + list.get(i) + "</gx:value>");
Float value = list.get(i);
if (value == null) {
printWriter.println("<gx:value />");
} else {
printWriter.println("<gx:value>" + list.get(i) + "</gx:value>");
}
}
printWriter.println("</gx:SimpleArrayData>");
}
@@ -158,6 +158,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
TrackPoint trackPoint = null;
TrackStatisticsUpdater trackStatisticsUpdater = new TrackStatisticsUpdater();
// TODO We are doing in memory processing for trackpoints; so we can do this in memory as well.
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), null)) {
while (true) {
@@ -190,7 +191,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
marker = null;
} else {
// The marker trackPoint time matches the track point time
if (!trackPoint.getType().hasLocation()) {
if (!trackPoint.hasLocation()) {
// Invalid trackPoint, load the next trackPoint
trackPoint = null;
continue;
@@ -274,31 +275,43 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
trackIds.add(trackId);
trackData.track.setId(trackId);
flushLocations(trackData);
flushTrackPoints();
}
/**
* On track segment start.
*/
protected void onTrackSegmentStart() {
trackData.numberOfSegments++;
//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.createSegmentEndWithTime(trackData.lastLocationInCurrentSegment.getTime()));
//If not the first segment, add a pause separator if there is at least one TrackPoint in the last segment.
if (trackData.numberOfSegments > 1
&& trackData.lastLocationInCurrentSegment != null
&& (trackData.lastLocationInCurrentSegment.getType().equals(TrackPoint.Type.SEGMENT_START_MANUAL) || trackData.lastLocationInCurrentSegment.getType().equals(TrackPoint.Type.SEGMENT_START_AUTOMATIC))
) {
insertTrackPoint(TrackPoint.createSegmentEndWithTime(trackData.lastLocationInCurrentSegment.getTime()));
}
trackData.lastLocationInCurrentSegment = null;
}
protected void onTrackSegmentEnd() {
TrackPoint trackPoint = trackData.lastLocationInCurrentSegment;
if (trackPoint == null) {
return;
}
if (!trackPoint.hasLocation()) {
trackPoint.setType(TrackPoint.Type.SEGMENT_END_MANUAL);
}
}
protected void addMarker() throws SAXException {
// Markers must have a time, else cannot match to the track points
if (time == null) {
Log.w(TAG, "Marker without time ignored.");
return;
}
TrackPoint trackPoint = createTrackPoint();
if (!trackPoint.getType().hasLocation()) {
if (!trackPoint.hasLocation()) {
Log.w(TAG, "Marker with invalid coordinates ignored: " + trackPoint.getLocation());
return;
}
@@ -320,14 +333,8 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
markers.add(marker);
}
/**
* Gets a track point.
*/
protected TrackPoint getTrackPoint() throws SAXException {
TrackPoint trackPoint = createTrackPoint();
if (trackPoint == null) {
throw new SAXException(createErrorMessage("Invalid location detected: " + trackPoint));
}
// Calculate derived attributes from the previous point
if (trackData.lastLocationInCurrentSegment != null && trackData.lastLocationInCurrentSegment.getTime() != 0) {
@@ -344,34 +351,31 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
* GPS points tend to have some inherent imprecision, speed and bearing will likely be off, so the statistics for things like max speed will also be off.
*/
double duration = timeDifference * UnitConversions.MS_TO_S;
double speed = trackData.lastLocationInCurrentSegment.distanceTo(trackPoint) / duration;
trackPoint.setSpeed((float) speed);
if (trackPoint.hasLocation() && trackData.lastLocationInCurrentSegment.hasLocation()) {
double speed = trackData.lastLocationInCurrentSegment.distanceTo(trackPoint) / duration;
trackPoint.setSpeed((float) speed);
}
}
}
if (trackPoint.hasLocation() && trackData.lastLocationInCurrentSegment.hasLocation()) {
trackPoint.setBearing(trackData.lastLocationInCurrentSegment.bearingTo(trackPoint));
long maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(context);
double distanceToLastTrackLocation = trackPoint.distanceTo(trackData.lastLocationInCurrentSegment);
if (distanceToLastTrackLocation > maxRecordingDistance) {
trackPoint.setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
}
}
trackPoint.setBearing(trackData.lastLocationInCurrentSegment.bearingTo(trackPoint));
}
if (!LocationUtils.isValidLocation(trackPoint.getLocation())) {
if (trackPoint.hasLocation() && !LocationUtils.isValidLocation(trackPoint.getLocation())) {
throw new SAXException(createErrorMessage("Invalid location detected: " + trackPoint));
}
if (trackData.numberOfSegments > 1 && trackData.lastLocationInCurrentSegment == null) {
// If not the first segment, add a resume separator before adding the first location.
insertLocation(TrackPoint.createSegmentStartManualWithTime(trackPoint.getTime()));
}
trackData.lastLocationInCurrentSegment = trackPoint;
return trackPoint;
}
/**
* Inserts a track point.
*
* @param trackPoint the trackPoint
*/
protected void insertTrackPoint(TrackPoint trackPoint) {
insertLocation(trackPoint);
}
/**
* Creates an error message.
*
@@ -411,11 +415,17 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
* Creates a location.
*/
private TrackPoint createTrackPoint() throws SAXException {
if (latitude == null || longitude == null) {
return null;
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT);
try {
trackPoint.setTime(StringUtils.parseTime(time));
} catch (Exception e) {
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse time: %s", time)), e);
}
TrackPoint trackPoint = new TrackPoint();
if (latitude == null || longitude == null) {
return trackPoint;
}
try {
trackPoint.setLatitude(Double.parseDouble(latitude));
@@ -424,11 +434,6 @@ 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);
}
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) {
try {
@@ -484,7 +489,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
*
* @param trackPoint the trackPoint
*/
private void insertLocation(TrackPoint trackPoint) {
protected void insertTrackPoint(TrackPoint trackPoint) {
if (trackData.trackStatisticsUpdater == null) {
trackData.trackStatisticsUpdater = new TrackStatisticsUpdater();
}
@@ -492,21 +497,20 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
trackData.trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
trackData.bufferedTrackPoints.add(trackPoint);
trackData.numBufferedTrackPoints++;
trackData.numberOfLocations++;
}
protected boolean isFirstTrackPointInSegment() {
return trackData.lastLocationInCurrentSegment == null;
}
/**
* Flushes the locations to the database.
*
* @param data the track data
* Flushes the TrackPoints to the database.
*/
private void flushLocations(TrackData data) {
if (data.numBufferedTrackPoints <= 0) {
return;
private void flushTrackPoints() {
if (trackData.bufferedTrackPoints.size() > 0) {
contentProviderUtils.bulkInsertTrackPoint(trackData.bufferedTrackPoints, trackData.track.getId());
trackData.bufferedTrackPoints.clear();
}
contentProviderUtils.bulkInsertTrackPoint(data.bufferedTrackPoints, data.track.getId());
data.numBufferedTrackPoints = 0;
}
/**
@@ -532,16 +536,10 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
// The last location in the current segment; Null if the current segment doesn't have a last location
TrackPoint lastLocationInCurrentSegment;
// The number of locations processed for the current track
int numberOfLocations = 0;
// The TrackStatisticsUpdater for the current track
TrackStatisticsUpdater trackStatisticsUpdater;
// The buffered locations
final List<TrackPoint> bufferedTrackPoints = new ArrayList<>();
// The number of buffered locations
int numBufferedTrackPoints = 0;
}
}
@@ -101,6 +101,9 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
case TAG_TRACK:
onTrackEnd();
break;
case TAG_TRACK_SEGMENT:
onTrackSegmentEnd();
break;
case TAG_TRACK_POINT:
onTrackPointEnd();
break;
@@ -193,13 +196,13 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
loss = null;
}
/**
* On track point end.
*/
private void onTrackPointEnd() throws SAXException {
boolean isFirstTrackPointInSegment = isFirstTrackPointInSegment();
TrackPoint trackPoint = getTrackPoint();
if (trackPoint == null) {
return;
if (isFirstTrackPointInSegment) {
TrackPoint.Type type = !trackPoint.hasLocation() ? TrackPoint.Type.SEGMENT_START_MANUAL : TrackPoint.Type.SEGMENT_START_AUTOMATIC;
trackPoint.setType(type);
}
insertTrackPoint(trackPoint);
}
@@ -159,6 +159,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
if (content != null) {
time = content.trim();
}
break;
case TAG_STYLE_URL:
if (content != null) {
@@ -221,10 +222,8 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
elevationLossList.clear();
}
/**
* On track segment end.
*/
private void onTrackSegmentEnd() {
protected void onTrackSegmentEnd() {
super.onTrackSegmentEnd();
// Close a track segment by inserting the segment locations
for (int i = 0; i < trackPoints.size(); i++) {
TrackPoint trackPoint = trackPoints.get(i);
@@ -256,24 +255,32 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
* On track point end. gx:coord end tag.
*/
private void onTrackPointEnd() throws SAXException {
// Add location to locationList
// Add trackPoint to trackPoints
if (content == null) {
return;
}
String[] parts = content.trim().split(" ");
if (parts.length != 2 && parts.length != 3) {
return;
if (parts.length == 2 || parts.length == 3) {
longitude = parts[0];
latitude = parts[1];
altitude = parts.length == 3 ? parts[2] : null;
}
longitude = parts[0];
latitude = parts[1];
altitude = parts.length == 3 ? parts[2] : null;
TrackPoint location = getTrackPoint();
if (location == null) {
return;
// Similar to GPX
boolean isFirstTrackPointInSegment = isFirstTrackPointInSegment();
TrackPoint trackPoint = getTrackPoint();
if (isFirstTrackPointInSegment) {
TrackPoint.Type type = !trackPoint.hasLocation() ? TrackPoint.Type.SEGMENT_START_MANUAL : TrackPoint.Type.SEGMENT_START_AUTOMATIC;
trackPoint.setType(type);
}
trackPoints.add(location);
trackPoints.add(trackPoint);
// Reset variables for next trackpoint (which might not have such data).
time = null;
longitude = null;
latitude = null;
altitude = null;
}
/**
@@ -287,18 +294,16 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
* On extended data value end. gx:value end tag.
*/
private void onExtendedDataValueEnd() throws SAXException {
if (content == null) {
return;
}
content = content.trim();
if (content.equals("")) {
return;
}
float value;
try {
value = Float.parseFloat(content);
} catch (NumberFormatException e) {
throw new SAXException(createErrorMessage("Unable to parse gx:value:" + content), e);
Float value = null;
if (content != null) {
content = content.trim();
if (!content.equals("")) {
try {
value = Float.parseFloat(content);
} catch (NumberFormatException e) {
throw new SAXException(createErrorMessage("Unable to parse gx:value:" + content), e);
}
}
}
switch (extendedDataType) {
case KmlTrackWriter.EXTENDED_DATA_TYPE_SPEED:
@@ -31,7 +31,7 @@ public class IntervalStatistics {
TrackPoint prevTrackPoint = trackPoints.get(i - 1);
TrackPoint trackPoint = trackPoints.get(i);
if (trackPoint.getType().hasLocation() && prevTrackPoint.getType().hasLocation()) {
if (trackPoint.hasLocation() && prevTrackPoint.hasLocation()) {
interval.distance_m += prevTrackPoint.distanceTo(trackPoint);
interval.time_ms += trackPoint.getTime() - prevTrackPoint.getTime();
interval.gain_m += trackPoint.hasElevationGain() ? trackPoint.getElevationGain() : 0;