SENSORPOINT and TRACKPOINT require a distance to be stored.

TrackRecording and TrackStatisticsUpdater.

Part of #500
Fixes #1074.
This commit is contained in:
Dennis Guse
2022-01-02 14:50:57 +01:00
parent c4b8082efc
commit 1163f74b98
10 changed files with 200 additions and 149 deletions
@@ -59,5 +59,12 @@ public abstract class Altitude {
return new EGM2008(altitude_m);
}
}
@Override
public String toString() {
return "Altitude{" +
"altitude_m=" + altitude_m + this.getClass().getSimpleName() +
'}';
}
}
@@ -64,7 +64,7 @@ public class TrackPoint {
SEGMENT_START_AUTOMATIC(-1), //Start of a segment due to too much distance from previous TrackPoint
TRACKPOINT(0), //Just GPS data and may contain BLE sensor data
SENSORPOINT(2), //Just BLE sensor data
SENSORPOINT(2), //Just BLE sensor data; required to have speed and sensorDistance
SEGMENT_END_MANUAL(1); //End of a segment
@@ -109,13 +109,7 @@ public class TrackPoint {
public TrackPoint(@NonNull Type type, @NonNull Location location, @NonNull Instant time) {
this(type, time);
this.latitude = location.getLatitude();
this.longitude = location.getLongitude();
this.altitude = location.hasAltitude() ? Altitude.WGS84.of(location.getAltitude()) : null;
this.speed = location.hasSpeed() ? Speed.of(location.getSpeed()) : null;
this.horizontalAccuracy = location.hasAccuracy() ? Distance.of(location.getAccuracy()) : null;
//TODO Should we copy the bearing?
setLocation(location);
}
@VisibleForTesting
@@ -152,6 +146,10 @@ public class TrackPoint {
return type == Type.SEGMENT_END_MANUAL;
}
public boolean wasCreatedManually() {
return hasLocation() || hasSpeed();
}
/**
* May be null if the track was not loaded from the database.
*/
@@ -211,6 +209,17 @@ public class TrackPoint {
return location;
}
public TrackPoint setLocation(@NonNull Location location) {
this.latitude = location.getLatitude();
this.longitude = location.getLongitude();
this.altitude = location.hasAltitude() ? Altitude.WGS84.of(location.getAltitude()) : null;
this.speed = location.hasSpeed() ? Speed.of(location.getSpeed()) : null;
this.horizontalAccuracy = location.hasAccuracy() ? Distance.of(location.getAccuracy()) : null;
//TODO Should we copy the bearing?
return this;
}
public boolean hasAltitudeGain() {
return altitudeGain_m != null;
}
@@ -310,13 +319,13 @@ public class TrackPoint {
return this;
}
@Nullable
public Distance distanceToPrevious(@Nullable TrackPoint previous) {
@NonNull
public Distance distanceToPrevious(@NonNull TrackPoint previous) {
if (hasSensorDistance()) {
return getSensorDistance();
}
if (previous == null || !(hasLocation() && previous.hasLocation())) {
return null;
if (!hasLocation() || hasLocation() != previous.hasLocation()) {
throw new RuntimeException("Cannot compute distance.");
}
return Distance.of(getLocation().distanceTo(previous.getLocation()));
@@ -411,7 +420,7 @@ public class TrackPoint {
public String toString() {
String result = "time=" + getTime() + " (type=" + getType() + ")";
if (hasLocation()) {
result += ": lat=" + getLatitude() + " lng=" + getLongitude();
result += ": lat=" + getLatitude() + " lng=" + getLongitude() + " alt=" + getAltitude();
}
if (hasHorizontalAccuracy()) {
result += " acc=" + getHorizontalAccuracy();
@@ -419,6 +428,12 @@ public class TrackPoint {
if (hasSensorDistance()) {
result += " distance=" + getSensorDistance();
}
if (hasAltitudeGain()) {
result += " altitudeGain= " + getAltitudeGain();
}
if (hasAltitudeLoss()) {
result += " altitudeLoss= " + getAltitudeLoss();
}
return result;
}
@@ -306,23 +306,10 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac
Instant time = whenList.get(i);
Location location = locationList.get(i);
TrackPoint trackPoint;
if (i == 0) {
//first
if (location == null) {
trackPoint = TrackPoint.createSegmentStartManualWithTime(time);
} else {
trackPoint = new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, location, time);
}
} else if (i == locationList.size() - 1 && location == null) {
//last
trackPoint = TrackPoint.createSegmentEndWithTime(time);
} else {
if (location == null) {
trackPoint = new TrackPoint(TrackPoint.Type.SENSORPOINT, time);
} else {
trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, location, time);
}
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.SENSORPOINT, time);
if (location != null) {
trackPoint.setType(TrackPoint.Type.TRACKPOINT);
trackPoint.setLocation(location);
}
if (i < sensorSpeedList.size() && sensorSpeedList.get(i) != null) {
@@ -347,6 +334,21 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac
trackPoint.setAltitudeLoss(altitudeLossList.get(i));
}
// Update TrackPoint type for START / STOP.
TrackPoint.Type type = trackPoint.getType();
if (i == 0) {
//first
if (!trackPoint.wasCreatedManually()) {
type = TrackPoint.Type.SEGMENT_START_MANUAL;
} else {
type = TrackPoint.Type.SEGMENT_START_AUTOMATIC;
}
} else if (i == locationList.size() - 1 && !trackPoint.wasCreatedManually()) {
//last
type = TrackPoint.Type.SEGMENT_END_MANUAL;
}
trackPoint.setType(type);
trackImporter.addTrackPoint(trackPoint);
}
}
@@ -198,8 +198,8 @@ public class TrackImporter {
TrackPoint previous = trackPoints.get(i - 1);
TrackPoint current = trackPoints.get(i);
Distance distanceToPrevious = current.distanceToPrevious(previous);
if (distanceToPrevious != null) {
if (current.hasSensorDistance() || (previous.hasLocation() && current.hasLocation())) {
Distance distanceToPrevious = current.distanceToPrevious(previous);
if (!current.hasSpeed()) {
Duration timeDifference = Duration.between(previous.getTime(), current.getTime());
current.setSpeed(Speed.of(distanceToPrevious, timeDifference));
@@ -39,8 +39,6 @@ class TrackRecordingManager {
private Track.Id trackId;
private TrackStatisticsUpdater trackStatisticsUpdater;
//TDOO use lastStoredTrackPoint?
private boolean currentSegmentHasTrackPoint;
private TrackPoint lastTrackPoint;
private TrackPoint lastStoredTrackPoint;
private TrackPoint lastStoredTrackPointWithLocation;
@@ -68,8 +66,6 @@ class TrackRecordingManager {
track.setName(TrackNameUtils.getTrackName(context, trackId, track.getStartTime()));
contentProviderUtils.updateTrack(track);
currentSegmentHasTrackPoint = false;
return trackId;
}
@@ -85,7 +81,6 @@ class TrackRecordingManager {
trackStatisticsUpdater = new TrackStatisticsUpdater(track.getTrackStatistics());
insertTrackPoint(trackId, segmentStartTrackPoint);
currentSegmentHasTrackPoint = false;
lastTrackPoint = null;
lastStoredTrackPoint = null;
lastStoredTrackPointWithLocation = null;
@@ -94,7 +89,6 @@ class TrackRecordingManager {
void pause(TrackPointCreator trackPointCreator) {
insertTrackPoint(trackId, trackPointCreator.createSegmentEnd());
currentSegmentHasTrackPoint = false;
lastTrackPoint = null;
lastStoredTrackPoint = null;
lastStoredTrackPointWithLocation = null;
@@ -107,7 +101,6 @@ class TrackRecordingManager {
trackId = null;
trackStatisticsUpdater = null;
currentSegmentHasTrackPoint = false;
lastTrackPoint = null;
lastStoredTrackPoint = null;
lastStoredTrackPointWithLocation = null;
@@ -161,44 +154,43 @@ class TrackRecordingManager {
/**
* @return TrackPoint was stored?
*/
boolean onNewTrackPoint(TrackPoint trackPoint) {
boolean onNewTrackPoint(@NonNull TrackPoint trackPoint) {
//Storing trackPoint
// Always insert the first segment location
if (!currentSegmentHasTrackPoint) {
if (lastStoredTrackPoint == null) {
insertTrackPoint(trackId, trackPoint);
currentSegmentHasTrackPoint = true;
return true;
}
Distance distanceToLastStoredTrackPoint = trackPoint.distanceToPrevious(lastStoredTrackPoint);
if (distanceToLastStoredTrackPoint != null) {
if (distanceToLastStoredTrackPoint.greaterThan(maxRecordingDistance)) {
trackPoint.setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
insertTrackPoint(trackId, trackPoint);
return true;
}
if (distanceToLastStoredTrackPoint.greaterOrEqualThan(recordingDistanceInterval) && trackPoint.isMoving()) {
insertTrackPoint(trackId, trackPoint);
return true;
}
if (trackPoint.hasLocation()) {
if (lastStoredTrackPointWithLocation == null) {
insertTrackPoint(trackId, trackPoint);
return true;
}
Distance distanceToLastStoredTrackPointWithLocation = trackPoint.distanceToPrevious(lastStoredTrackPointWithLocation);
if (distanceToLastStoredTrackPointWithLocation != null && distanceToLastStoredTrackPointWithLocation.greaterOrEqualThan(recordingDistanceInterval)) {
insertTrackPoint(trackId, trackPoint);
return true;
}
}
if (trackPoint.hasLocation() && lastStoredTrackPointWithLocation == null) {
insertTrackPoint(trackId, trackPoint);
return true;
}
if (lastStoredTrackPoint != null && trackPoint.isMoving() != lastStoredTrackPoint.isMoving()) {
if (!trackPoint.hasLocation() && !trackPoint.hasSensorDistance()) {
Log.d(TAG, "Ignoring TrackPoint as it has no distance.");
return false;
}
TrackPoint distanceTo = lastStoredTrackPoint;
if (trackPoint.hasLocation() && !lastStoredTrackPoint.hasLocation()) {
distanceTo = lastStoredTrackPointWithLocation;
}
Distance distanceToLastStoredTrackPoint = trackPoint.distanceToPrevious(distanceTo);
if (distanceToLastStoredTrackPoint.greaterThan(maxRecordingDistance)) {
trackPoint.setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
insertTrackPoint(trackId, trackPoint);
return true;
}
if (distanceToLastStoredTrackPoint.greaterOrEqualThan(recordingDistanceInterval)
&& trackPoint.isMoving()) {
insertTrackPoint(trackId, trackPoint);
return true;
}
if (trackPoint.isMoving() != lastStoredTrackPoint.isMoving()) {
// Moving from non-moving to moving or vice versa; required to compute moving time correctly.
insertTrackPoint(trackId, trackPoint);
return true;
@@ -206,6 +198,7 @@ class TrackRecordingManager {
Log.d(TAG, "Not recording TrackPoint");
lastTrackPoint = trackPoint;
return false;
}
@@ -213,6 +206,7 @@ class TrackRecordingManager {
return trackStatisticsUpdater.getTrackStatistics();
}
//TODO Should be only be called from onNewTrackPoint().
private void insertTrackPoint(@NonNull Track.Id trackId, @NonNull TrackPoint trackPoint) {
if (lastTrackPoint != null) {
if (lastStoredTrackPoint != null && lastTrackPoint.getTime().equals(lastStoredTrackPoint.getTime())) {
@@ -26,6 +26,7 @@ import java.time.Instant;
import de.dennisguse.opentracks.content.data.Altitude;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Speed;
import de.dennisguse.opentracks.content.data.TrackPoint;
/**
* Statistical data about a {@link de.dennisguse.opentracks.content.data.Track}.
@@ -161,6 +162,7 @@ public class TrackStatistics {
public void setStopTime(Instant stopTime) {
if (stopTime.isBefore(startTime)) {
// Time must be monotonically increasing, but we might have events at the same point in time (BLE and GPS)
throw new RuntimeException("stopTime cannot be less than startTime: " + startTime + " " + stopTime);
}
this.stopTime = stopTime;
@@ -199,8 +201,15 @@ public class TrackStatistics {
this.movingTime = movingTime;
}
public void addMovingTime(TrackPoint trackPoint, TrackPoint lastTrackPoint) {
addMovingTime(Duration.between(lastTrackPoint.getTime(), trackPoint.getTime()));
}
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public void addMovingTime(Duration time) {
if (time.isNegative()) {
throw new RuntimeException("Moving time cannot be negative");
}
movingTime = movingTime.plus(time);
}
@@ -148,23 +148,19 @@ public class TrackStatisticsUpdater {
if (trackPoint.hasSensorDistance()) {
// Sensor-based distance/speed
currentSegment.addTotalDistance(trackPoint.getSensorDistance());
} else if (lastTrackPoint != null && trackPoint.isMoving()) {
} else if (lastTrackPoint != null
&& lastTrackPoint.hasLocation()
&& trackPoint.hasLocation() && trackPoint.isMoving()) {
// GPS-based distance/speed
// Assumption: we ignore TrackPoints that are not moving as those are likely imprecise GPS measurements
Distance movingDistance = trackPoint.distanceToPrevious(lastTrackPoint);
if (movingDistance != null) {
currentSegment.addTotalDistance(movingDistance);
}
currentSegment.addTotalDistance(movingDistance);
}
// Update moving time
if (trackPoint.isMoving() && lastTrackPoint != null && lastTrackPoint.isMoving()) {
Duration movingTime = Duration.between(lastTrackPoint.getTime(), trackPoint.getTime());
if (movingTime.isNegative()) {
throw new RuntimeException("Moving time cannot be negative");
}
currentSegment.addMovingTime(movingTime);
currentSegment.addMovingTime(trackPoint, lastTrackPoint);
// Update max speed
updateSpeed(trackPoint, lastTrackPoint);