Create helper functions for TrackPoints in TrackPointUtils.

This commit is contained in:
Dennis Guse
2020-04-12 20:24:54 +02:00
parent b09eeaede3
commit 95feae846a
4 changed files with 92 additions and 52 deletions
@@ -49,6 +49,7 @@ import de.dennisguse.opentracks.util.LocationUtils;
import de.dennisguse.opentracks.util.PreferencesUtils; import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.StringUtils; import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.TrackIconUtils; import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.util.TrackPointUtils;
import de.dennisguse.opentracks.util.UnitConversions; import de.dennisguse.opentracks.util.UnitConversions;
/** /**
@@ -356,7 +357,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
if (lastTrackPoint != null) { if (lastTrackPoint != null) {
boolean hasFix = !LocationUtils.isLocationOld(lastTrackPoint.getLocation()); boolean hasFix = !LocationUtils.isLocationOld(lastTrackPoint.getLocation());
boolean hasGoodFix = lastTrackPoint.hasAccuracy() && lastTrackPoint.getAccuracy() < recordingGpsAccuracy; boolean hasGoodFix = TrackPointUtils.fulfillsAccuracy(lastTrackPoint, recordingGpsAccuracy);
if (!hasFix || !hasGoodFix) { if (!hasFix || !hasGoodFix) {
lastTrackPoint = null; lastTrackPoint = null;
@@ -33,6 +33,7 @@ import android.os.IBinder;
import android.os.PowerManager.WakeLock; import android.os.PowerManager.WakeLock;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull;
import androidx.core.app.TaskStackBuilder; import androidx.core.app.TaskStackBuilder;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
@@ -59,6 +60,7 @@ import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.SystemUtils; import de.dennisguse.opentracks.util.SystemUtils;
import de.dennisguse.opentracks.util.TrackIconUtils; import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.util.TrackNameUtils; import de.dennisguse.opentracks.util.TrackNameUtils;
import de.dennisguse.opentracks.util.TrackPointUtils;
import de.dennisguse.opentracks.util.UnitConversions; import de.dennisguse.opentracks.util.UnitConversions;
/** /**
@@ -69,11 +71,8 @@ import de.dennisguse.opentracks.util.UnitConversions;
*/ */
public class TrackRecordingService extends Service { public class TrackRecordingService extends Service {
// Anything faster than that (in meters per second) will be considered moving.
private static final String TAG = TrackRecordingService.class.getSimpleName(); private static final String TAG = TrackRecordingService.class.getSimpleName();
public static final double MAX_NO_MOVEMENT_SPEED = 0.224;
// The following variables are set in onCreate: // The following variables are set in onCreate:
private ExecutorService executorService; private ExecutorService executorService;
private ContentProviderUtils contentProviderUtils; private ContentProviderUtils contentProviderUtils;
@@ -455,7 +454,9 @@ public class TrackRecordingService extends Service {
if (track != null) { if (track != null) {
// If not paused, add the last location // If not paused, add the last location
if (!paused) { if (!paused) {
insertTrackPoint(track, lastTrackPoint, getLastValidTrackPointInCurrentSegment(trackId)); if (lastTrackPoint != null) {
insertTrackPoint(track, lastTrackPoint, getLastValidTrackPointInCurrentSegment(trackId));
}
// Update the recording track time // Update the recording track time
updateRecordingTrack(track); updateRecordingTrack(track);
@@ -574,28 +575,24 @@ public class TrackRecordingService extends Service {
TrackPoint trackPoint = new TrackPoint(location, getSensorDataSet()); TrackPoint trackPoint = new TrackPoint(location, getSensorDataSet());
notificationManager.updateTrackPoint(this, trackPoint, recordingGpsAccuracy); notificationManager.updateTrackPoint(this, trackPoint, recordingGpsAccuracy);
if (!location.hasAccuracy() || location.getAccuracy() >= recordingGpsAccuracy) { if (!TrackPointUtils.fulfillsAccuracy(trackPoint, recordingGpsAccuracy)) {
Log.d(TAG, "Ignore onLocationChangedAsync. Poor accuracy."); Log.d(TAG, "Ignore onLocationChangedAsync. Poor accuracy.");
return; return;
} }
//TODO Necessary? TrackPointUtils.fixTime(trackPoint);
// Fix for phones that do not set the time field
if (location.getTime() == 0L) {
location.setTime(System.currentTimeMillis());
}
//TODO Figure out how to avoid loading the lastValidTrackPoint from the database
TrackPoint lastValidTrackPoint = getLastValidTrackPointInCurrentSegment(track.getId()); TrackPoint lastValidTrackPoint = getLastValidTrackPointInCurrentSegment(track.getId());
long idleTime = 0L; long idleTime = 0L;
if (lastValidTrackPoint != null && location.getTime() > lastValidTrackPoint.getLocation().getTime()) { if (TrackPointUtils.after(trackPoint, lastValidTrackPoint)) {
idleTime = location.getTime() - lastValidTrackPoint.getLocation().getTime(); idleTime = trackPoint.getTime() - lastValidTrackPoint.getLocation().getTime();
} }
locationListenerPolicy.updateIdleTime(idleTime); locationListenerPolicy.updateIdleTime(idleTime);
if (currentRecordingInterval != locationListenerPolicy.getDesiredPollingInterval()) { if (currentRecordingInterval != locationListenerPolicy.getDesiredPollingInterval()) {
registerLocationListener(); registerLocationListener();
} }
// Always insert the first segment location // Always insert the first segment location
if (!currentSegmentHasLocation) { if (!currentSegmentHasLocation) {
insertTrackPoint(track, trackPoint, null); insertTrackPoint(track, trackPoint, null);
@@ -611,7 +608,7 @@ public class TrackRecordingService extends Service {
return; return;
} }
double distanceToLastTrackLocation = location.distanceTo(lastValidTrackPoint.getLocation()); double distanceToLastTrackLocation = trackPoint.distanceTo(lastValidTrackPoint);
if (distanceToLastTrackLocation > maxRecordingDistance) { if (distanceToLastTrackLocation > maxRecordingDistance) {
insertTrackPoint(track, lastTrackPoint, lastValidTrackPoint); insertTrackPoint(track, lastTrackPoint, lastValidTrackPoint);
insertTrackPoint(track, TrackPoint.createPause(), null); insertTrackPoint(track, TrackPoint.createPause(), null);
@@ -622,16 +619,16 @@ public class TrackRecordingService extends Service {
insertTrackPoint(track, lastTrackPoint, lastValidTrackPoint); insertTrackPoint(track, lastTrackPoint, lastValidTrackPoint);
insertTrackPoint(track, trackPoint, null); insertTrackPoint(track, trackPoint, null);
isIdle = false; isIdle = false;
} else if (!isIdle && location.hasSpeed() && location.getSpeed() < MAX_NO_MOVEMENT_SPEED) { } else if (!isIdle && !TrackPointUtils.isMoving(trackPoint)) {
insertTrackPoint(track, lastTrackPoint, lastValidTrackPoint); insertTrackPoint(track, lastTrackPoint, lastValidTrackPoint);
insertTrackPoint(track, trackPoint, null); insertTrackPoint(track, trackPoint, null);
isIdle = true; isIdle = true;
} else if (isIdle && location.hasSpeed() && location.getSpeed() >= MAX_NO_MOVEMENT_SPEED) { } else if (isIdle && TrackPointUtils.isMoving(trackPoint)) {
insertTrackPoint(track, lastTrackPoint, lastValidTrackPoint); insertTrackPoint(track, lastTrackPoint, lastValidTrackPoint);
insertTrackPoint(track, trackPoint, null); insertTrackPoint(track, trackPoint, null);
isIdle = false; isIdle = false;
} else { } else {
Log.d(TAG, "Not recording location, idle"); Log.d(TAG, "Not recording TrackPoint, idle");
} }
lastTrackPoint = trackPoint; lastTrackPoint = trackPoint;
} }
@@ -643,14 +640,10 @@ public class TrackRecordingService extends Service {
* @param trackPoint the trackPoint * @param trackPoint the trackPoint
* @param lastValidTrackPoint the last valid track point, can be null * @param lastValidTrackPoint the last valid track point, can be null
*/ */
private void insertTrackPoint(Track track, TrackPoint trackPoint, TrackPoint lastValidTrackPoint) { private void insertTrackPoint(@NonNull Track track, @NonNull TrackPoint trackPoint, TrackPoint lastValidTrackPoint) {
if (trackPoint == null) { if (TrackPointUtils.equalTime(trackPoint, lastValidTrackPoint)) {
Log.w(TAG, "Ignore insertLocation. trackPoint is null."); // Do not insert if inserted already
return; Log.w(TAG, "Ignore insertTrackPoint. trackPoint time same as last valid track point time.");
}
// Do not insert if inserted already
if (lastValidTrackPoint != null && lastValidTrackPoint.getTime() == trackPoint.getTime()) {
Log.w(TAG, "Ignore insertLocation. trackPoint time same as last valid track point time.");
return; return;
} }
@@ -18,14 +18,14 @@ package de.dennisguse.opentracks.stats;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.content.data.TrackPoint; import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.TrackPointsColumns; import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.provider.TrackPointIterator; import de.dennisguse.opentracks.content.provider.TrackPointIterator;
import de.dennisguse.opentracks.util.LocationUtils; import de.dennisguse.opentracks.util.LocationUtils;
import de.dennisguse.opentracks.util.TrackPointUtils;
import static de.dennisguse.opentracks.services.TrackRecordingService.MAX_NO_MOVEMENT_SPEED;
/** /**
* Updater for {@link TrackStatistics}. * Updater for {@link TrackStatistics}.
@@ -143,7 +143,7 @@ public class TrackStatisticsUpdater {
} }
double movingDistance = lastMovingTrackPoint.distanceTo(trackPoint); double movingDistance = lastMovingTrackPoint.distanceTo(trackPoint);
if (movingDistance < minRecordingDistance && (!trackPoint.hasSpeed() || trackPoint.getSpeed() < MAX_NO_MOVEMENT_SPEED)) { if (movingDistance < minRecordingDistance && !TrackPointUtils.isMoving(trackPoint)) {
speedBuffer_ms.reset(); speedBuffer_ms.reset();
lastTrackPoint = trackPoint; lastTrackPoint = trackPoint;
return; return;
@@ -162,7 +162,7 @@ public class TrackStatisticsUpdater {
// Update max speed // Update max speed
if (trackPoint.hasSpeed() && lastTrackPoint.hasSpeed()) { if (trackPoint.hasSpeed() && lastTrackPoint.hasSpeed()) {
updateSpeed(trackPoint.getTime(), trackPoint.getSpeed(), lastTrackPoint.getTime(), lastTrackPoint.getSpeed()); updateSpeed(trackPoint, lastTrackPoint);
} }
lastTrackPoint = trackPoint; lastTrackPoint = trackPoint;
@@ -190,23 +190,18 @@ public class TrackStatisticsUpdater {
/** /**
* Updates a speed reading while assuming the user is moving. * Updates a speed reading while assuming the user is moving.
*
* @param time the time
* @param speed the speed
* @param lastLocationTime the last location time
* @param lastLocationSpeed the last location speed
*/ */
@VisibleForTesting @VisibleForTesting
private void updateSpeed(long time, double speed, long lastLocationTime, double lastLocationSpeed) { private void updateSpeed(@NonNull TrackPoint trackPoint, @NonNull TrackPoint lastTrackPoint) {
if (speed < MAX_NO_MOVEMENT_SPEED) { if (!TrackPointUtils.isMoving(trackPoint)) {
speedBuffer_ms.reset(); speedBuffer_ms.reset();
} else if (isValidSpeed(time, speed, lastLocationTime, lastLocationSpeed)) { } else if (isValidSpeed(trackPoint, lastTrackPoint)) {
speedBuffer_ms.setNext(speed); speedBuffer_ms.setNext(trackPoint.getSpeed());
if (speedBuffer_ms.getAverage() > currentSegment.getMaxSpeed()) { if (speedBuffer_ms.getAverage() > currentSegment.getMaxSpeed()) {
currentSegment.setMaxSpeed(speedBuffer_ms.getAverage()); currentSegment.setMaxSpeed(speedBuffer_ms.getAverage());
} }
} else { } else {
Log.d(TAG, "Invalid speed. speed: " + speed + " lastLocationSpeed: " + lastLocationSpeed); Log.d(TAG, "Invalid speed. speed: " + trackPoint.getSpeed() + " lastLocationSpeed: " + lastTrackPoint.getSpeed());
} }
} }
@@ -239,26 +234,21 @@ public class TrackStatisticsUpdater {
/** /**
* Returns true if the speed is valid. * Returns true if the speed is valid.
*
* @param time the time
* @param speed the speed
* @param lastLocationTime the last location time
* @param lastLocationSpeed the last location speed
*/ */
private boolean isValidSpeed(long time, double speed, long lastLocationTime, double lastLocationSpeed) { private boolean isValidSpeed(@NonNull TrackPoint trackPoint, @NonNull TrackPoint lastTrackPoint) {
// There are a lot of noisy speed readings. Do the cheapest checks first, most expensive last. // There are a lot of noisy speed readings. Do the cheapest checks first, most expensive last.
if (speed == 0) { if (trackPoint.getSpeed() == 0) {
return false; return false;
} }
// The following code will ignore unlikely readings. 128 m/s seems to be an internal android error code. // The following code will ignore unlikely readings. 128 m/s seems to be an internal android error code.
if (Math.abs(speed - 128) < 1) { if (Math.abs(trackPoint.getSpeed() - 128) < 1) {
return false; return false;
} }
// See if the speed seems physically likely. Ignore any speeds that imply acceleration greater than 2g. // See if the speed seems physically likely. Ignore any speeds that imply acceleration greater than 2g.
long timeDifference = time - lastLocationTime; long timeDifference = trackPoint.getTime() - lastTrackPoint.getTime();
double speedDifference = Math.abs(lastLocationSpeed - speed); double speedDifference = Math.abs(lastTrackPoint.getSpeed() - trackPoint.getSpeed());
if (speedDifference > MAX_ACCELERATION * timeDifference) { if (speedDifference > MAX_ACCELERATION * timeDifference) {
return false; return false;
} }
@@ -266,8 +256,8 @@ public class TrackStatisticsUpdater {
// Only check if the speed buffer is full. Check that the speed is less than 10X the smoothed average and the speed difference doesn't imply 2g acceleration. // Only check if the speed buffer is full. Check that the speed is less than 10X the smoothed average and the speed difference doesn't imply 2g acceleration.
if (speedBuffer_ms.isFull()) { if (speedBuffer_ms.isFull()) {
double average = speedBuffer_ms.getAverage(); double average = speedBuffer_ms.getAverage();
double diff = Math.abs(average - speed); double diff = Math.abs(average - trackPoint.getSpeed());
return (speed < average * 10) && (diff < MAX_ACCELERATION * timeDifference); return (trackPoint.getSpeed() < average * 10) && (diff < MAX_ACCELERATION * timeDifference);
} }
return true; return true;
@@ -0,0 +1,56 @@
package de.dennisguse.opentracks.util;
import android.util.Log;
import androidx.annotation.NonNull;
import de.dennisguse.opentracks.content.data.TrackPoint;
public class TrackPointUtils {
// Anything faster than that (in meters per second) will be considered moving.
private static final double MAX_NO_MOVEMENT_SPEED = 0.224;
private final static String TAG = TrackPointUtils.class.getSimpleName();
private TrackPointUtils() {
}
/**
* Ancient fix for phones that do not set the time in {@link android.location.Location}.
*/
//TODO Necessary?
public static void fixTime(@NonNull TrackPoint trackPoint) {
if (trackPoint.getTime() == 0L) {
Log.w(TAG, "Time of provided location was 0. Using current time.");
trackPoint.setTime(System.currentTimeMillis());
}
}
public static boolean isMoving(@NonNull TrackPoint trackPoint) {
return trackPoint.hasSpeed() && trackPoint.getSpeed() >= MAX_NO_MOVEMENT_SPEED;
}
public static boolean equalTime(TrackPoint t1, TrackPoint t2) {
if (t1 == null || t2 == null) {
return false;
}
return t1.getTime() == t2.getTime();
}
public static boolean after(TrackPoint t1, TrackPoint t2) {
if (t1 == null || t2 == null) {
return false;
}
return t1.getTime() > t2.getTime();
}
/**
* Is accuracy better than threshold?
*/
public static boolean fulfillsAccuracy(@NonNull TrackPoint trackPoint, int poorAccuracy) {
return trackPoint.hasAccuracy() && trackPoint.getAccuracy() < poorAccuracy;
}
}