Use TrackPoint instead of Location (and omit casting).

This commit is contained in:
Dennis Guse
2020-03-26 22:52:42 +01:00
parent d93c2ec5f3
commit 901cffee97
33 changed files with 473 additions and 490 deletions
@@ -19,7 +19,6 @@ package de.dennisguse.opentracks.content;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.location.Location;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
@@ -32,6 +31,7 @@ import java.util.Set;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.TrackPointFactory;
@@ -395,7 +395,7 @@ public class TrackDataHub implements DataSourceListener, SharedPreferences.OnSha
try (TrackPointIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(selectedTrackId, localLastSeenLocationId + 1, false, TrackPointFactory.DEFAULT_LOCATION_FACTORY)) {
while (locationIterator.hasNext()) {
Location location = locationIterator.next();
TrackPoint trackPoint = locationIterator.next();
long locationId = locationIterator.getTrackPointId();
// Stop if past the last wanted point
@@ -412,18 +412,18 @@ public class TrackDataHub implements DataSourceListener, SharedPreferences.OnSha
samplingFrequency = 1 + (int) (numTotalPoints / targetNumPoints);
}
if (!LocationUtils.isValidLocation(location)) { //This can be split markers (not anymore supported feature)
if (!LocationUtils.isValidLocation(trackPoint)) { //This can be split markers (not anymore supported feature)
includeNextPoint = true;
} else {
// Also include the last point if the selected track is not recording.
if (includeNextPoint || (localNumLoadedPoints % samplingFrequency == 0) || (locationId == lastTrackPointId && !isSelectedTrackRecording())) {
includeNextPoint = false;
for (TrackDataListener trackDataListener : sampledInListeners) {
trackDataListener.onSampledInTrackPoint(location);
trackDataListener.onSampledInTrackPoint(trackPoint);
}
} else {
for (TrackDataListener trackDataListener : sampledOutListeners) {
trackDataListener.onSampledOutTrackPoint(location);
trackDataListener.onSampledOutTrackPoint(trackPoint);
}
}
}
@@ -16,9 +16,8 @@
package de.dennisguse.opentracks.content;
import android.location.Location;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.Waypoint;
/**
@@ -43,20 +42,20 @@ public interface TrackDataListener {
/**
* Called when a sampled in track point is read.
*
* @param location the location
* @param trackPoint the trackPoint
*/
void onSampledInTrackPoint(Location location);
void onSampledInTrackPoint(TrackPoint trackPoint);
/**
* Called when a sampled out track point is read.
*
* @param location the location
* @param trackPoint the trackPoint
*/
void onSampledOutTrackPoint(Location location);
void onSampledOutTrackPoint(TrackPoint trackPoint);
/**
* Called when finish sending new track points.
* This gets called after every batch of calls to {@link #onSampledInTrackPoint(Location)} and {@link #onSampledOutTrackPoint(Location)}.
* This gets called after every batch of calls to {@link #onSampledInTrackPoint(TrackPoint)} and {@link #onSampledOutTrackPoint(TrackPoint)}.
*/
void onNewTrackPointsDone();
@@ -16,8 +16,6 @@
package de.dennisguse.opentracks.content.data;
import android.location.Location;
import androidx.annotation.VisibleForTesting;
import java.util.ArrayList;
@@ -43,7 +41,7 @@ public class Track {
private TripStatistics tripStatistics = new TripStatistics();
// Location points (which may not have been loaded)
private List<Location> locations = new ArrayList<>();
private List<TrackPoint> trackPoints = new ArrayList<>();
public Track() {
}
@@ -97,15 +95,15 @@ public class Track {
}
@VisibleForTesting
public void addLocation(Location location) {
locations.add(location);
public void addTrackPoint(TrackPoint location) {
trackPoints.add(location);
}
public List<Location> getLocations() {
return locations;
public List<TrackPoint> getTrackPoints() {
return trackPoints;
}
public void setLocations(ArrayList<Location> locations) {
this.locations = locations;
public void setTrackPoints(ArrayList<TrackPoint> trackPoints) {
this.trackPoints = trackPoints;
}
}
@@ -16,6 +16,7 @@
package de.dennisguse.opentracks.content.data;
import android.location.Location;
import android.location.LocationManager;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
@@ -24,8 +25,25 @@ import de.dennisguse.opentracks.content.sensor.SensorDataSet;
*
* @author Sandor Dornbush
*/
//TODO Check if we can drop inheritance from Location and use attributes instead.
public class TrackPoint extends Location {
public static TrackPoint createPause() {
TrackPoint pause = new TrackPoint(LocationManager.GPS_PROVIDER);
pause.setLongitude(0);
pause.setLatitude(TrackPointsColumns.PAUSE_LATITUDE);
pause.setTime(System.currentTimeMillis());
return pause;
}
public static TrackPoint createResume() {
TrackPoint resume = new TrackPoint(LocationManager.GPS_PROVIDER);
resume.setLongitude(0);
resume.setLatitude(TrackPointsColumns.RESUME_LATITUDE);
resume.setTime(System.currentTimeMillis());
return resume;
}
private SensorDataSet sensorDataSet = null;
public TrackPoint(Location location, SensorDataSet sensorDataSet) {
@@ -622,34 +622,62 @@ public class ContentProviderUtils {
}
/**
* Inserts multiple track points.
* Fills a {@link TrackPoint} from a cursor.
*
* @param locations an array of locations
* @param length the number of locations (from the beginning of the array) to
* insert, or -1 for all of them
* @param trackId the track id
* @return the number of points inserted
* @param cursor the cursor pointing to a trackPoint.
* @param indexes the cached track points indexes
* @param trackPoint the track point
*/
public int bulkInsertTrackPoint(Location[] locations, int length, long trackId) {
if (length == -1) {
length = locations.length;
static void fillTrackPoint(Cursor cursor, CachedTrackPointsIndexes indexes, TrackPoint trackPoint) {
trackPoint.reset();
if (!cursor.isNull(indexes.longitudeIndex)) {
trackPoint.setLongitude(((double) cursor.getInt(indexes.longitudeIndex)) / 1E6);
}
ContentValues[] values = new ContentValues[length];
for (int i = 0; i < length; i++) {
values[i] = createContentValues(locations[i], trackId);
if (!cursor.isNull(indexes.latitudeIndex)) {
trackPoint.setLatitude(((double) cursor.getInt(indexes.latitudeIndex)) / 1E6);
}
return contentResolver.bulkInsert(TrackPointsColumns.CONTENT_URI_BY_ID, values);
if (!cursor.isNull(indexes.timeIndex)) {
trackPoint.setTime(cursor.getLong(indexes.timeIndex));
}
if (!cursor.isNull(indexes.altitudeIndex)) {
trackPoint.setAltitude(cursor.getFloat(indexes.altitudeIndex));
}
if (!cursor.isNull(indexes.accuracyIndex)) {
trackPoint.setAccuracy(cursor.getFloat(indexes.accuracyIndex));
}
if (!cursor.isNull(indexes.speedIndex)) {
trackPoint.setSpeed(cursor.getFloat(indexes.speedIndex));
}
if (!cursor.isNull(indexes.bearingIndex)) {
trackPoint.setBearing(cursor.getFloat(indexes.bearingIndex));
}
float heartRate = cursor.isNull(indexes.sensorHeartRateIndex) ? SensorDataSet.DATA_UNAVAILABLE : cursor.getFloat(indexes.sensorHeartRateIndex);
float cadence = cursor.isNull(indexes.sensorCadenceIndex) ? SensorDataSet.DATA_UNAVAILABLE : cursor.getFloat(indexes.sensorCadenceIndex);
float power = cursor.isNull(indexes.sensorPowerIndex) ? SensorDataSet.DATA_UNAVAILABLE : cursor.getFloat(indexes.sensorPowerIndex);
trackPoint.setSensorDataSet(new SensorDataSet(heartRate, cadence, power, SensorDataSet.DATA_UNAVAILABLE, trackPoint.getTime()));
}
/**
* Creates a location object from a cursor.
* Inserts multiple trackPoints points.
*
* @param cursor the cursor pointing to the location
* @param trackPoints an array of trackPoints
* @param length the number of trackPoints (from the beginning of the array) to
* insert, or -1 for all of them
* @param trackId the trackPoints id
* @return the number of points inserted
*/
public Location createTrackPoint(Cursor cursor) {
Location location = new TrackPoint("");
fillTrackPoint(cursor, new CachedTrackPointsIndexes(cursor), location);
return location;
public int bulkInsertTrackPoint(TrackPoint[] trackPoints, int length, long trackId) {
if (length == -1) {
length = trackPoints.length;
}
ContentValues[] values = new ContentValues[length];
for (int i = 0; i < length; i++) {
values[i] = createContentValues(trackPoints[i], trackId);
}
return contentResolver.bulkInsert(TrackPointsColumns.CONTENT_URI_BY_ID, values);
}
/**
@@ -722,21 +750,14 @@ public class ContentProviderUtils {
}
/**
* Gets the last valid location for a track.
* Returns null if it doesn't exist.
* Creates a location object from a cursor.
*
* @param trackId the track id
* @param cursor the cursor pointing to the location
*/
@Deprecated
public Location getLastValidTrackPoint(long trackId) {
if (trackId < 0) {
return null;
}
String selection = TrackPointsColumns._ID + "=(select max(" + TrackPointsColumns._ID + ") from "
+ TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND "
+ TrackPointsColumns.LATITUDE + "<=" + MAX_LATITUDE + ")";
String[] selectionArgs = new String[]{Long.toString(trackId)};
return findTrackPointBy(selection, selectionArgs);
public TrackPoint createTrackPoint(Cursor cursor) {
TrackPoint location = new TrackPoint("");
fillTrackPoint(cursor, new CachedTrackPointsIndexes(cursor), location);
return location;
}
/**
@@ -774,99 +795,71 @@ public class ContentProviderUtils {
return getTrackPointCursor(null, selection, selectionArgs, sortOrder);
}
/**
* Fills a track point from a cursor.
* Gets the last valid location for a track.
* Returns null if it doesn't exist.
*
* @param cursor the cursor pointing to a location.
* @param indexes the cached track points indexes
* @param location the track point
* @param trackId the track id
*/
static void fillTrackPoint(Cursor cursor, CachedTrackPointsIndexes indexes, Location location) {
location.reset();
if (!cursor.isNull(indexes.longitudeIndex)) {
location.setLongitude(((double) cursor.getInt(indexes.longitudeIndex)) / 1E6);
}
if (!cursor.isNull(indexes.latitudeIndex)) {
location.setLatitude(((double) cursor.getInt(indexes.latitudeIndex)) / 1E6);
}
if (!cursor.isNull(indexes.timeIndex)) {
location.setTime(cursor.getLong(indexes.timeIndex));
}
if (!cursor.isNull(indexes.altitudeIndex)) {
location.setAltitude(cursor.getFloat(indexes.altitudeIndex));
}
if (!cursor.isNull(indexes.accuracyIndex)) {
location.setAccuracy(cursor.getFloat(indexes.accuracyIndex));
}
if (!cursor.isNull(indexes.speedIndex)) {
location.setSpeed(cursor.getFloat(indexes.speedIndex));
}
if (!cursor.isNull(indexes.bearingIndex)) {
location.setBearing(cursor.getFloat(indexes.bearingIndex));
}
if (location instanceof TrackPoint) {
TrackPoint sensorDataSetLocation = (TrackPoint) location;
float heartRate = cursor.isNull(indexes.sensorHeartRateIndex) ? SensorDataSet.DATA_UNAVAILABLE : cursor.getFloat(indexes.sensorHeartRateIndex);
float cadence = cursor.isNull(indexes.sensorCadenceIndex) ? SensorDataSet.DATA_UNAVAILABLE : cursor.getFloat(indexes.sensorCadenceIndex);
float power = cursor.isNull(indexes.sensorPowerIndex) ? SensorDataSet.DATA_UNAVAILABLE : cursor.getFloat(indexes.sensorPowerIndex);
sensorDataSetLocation.setSensorDataSet(new SensorDataSet(heartRate, cadence, power, SensorDataSet.DATA_UNAVAILABLE, location.getTime()));
@Deprecated
public TrackPoint getLastValidTrackPoint(long trackId) {
if (trackId < 0) {
return null;
}
String selection = TrackPointsColumns._ID + "=(select max(" + TrackPointsColumns._ID + ") from "
+ TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND "
+ TrackPointsColumns.LATITUDE + "<=" + MAX_LATITUDE + ")";
String[] selectionArgs = new String[]{Long.toString(trackId)};
return findTrackPointBy(selection, selectionArgs);
}
/**
* Inserts a track point.
*
* @param location the location
* @param trackPoint the trackPoint
* @param trackId the track id
* @return the content provider URI of the inserted track point
*/
public Uri insertTrackPoint(Location location, long trackId) {
return contentResolver.insert(TrackPointsColumns.CONTENT_URI_BY_ID, createContentValues(location, trackId));
public Uri insertTrackPoint(TrackPoint trackPoint, long trackId) {
return contentResolver.insert(TrackPointsColumns.CONTENT_URI_BY_ID, createContentValues(trackPoint, trackId));
}
/**
* Creates the {@link ContentValues} for a {@link Location}.
* Creates the {@link ContentValues} for a {@link TrackPoint}.
*
* @param location the location
* @param trackPoint the trackPoint
* @param trackId the track id
*/
private ContentValues createContentValues(Location location, long trackId) {
private ContentValues createContentValues(TrackPoint trackPoint, long trackId) {
ContentValues values = new ContentValues();
values.put(TrackPointsColumns.TRACKID, trackId);
values.put(TrackPointsColumns.LONGITUDE, (int) (location.getLongitude() * 1E6));
values.put(TrackPointsColumns.LATITUDE, (int) (location.getLatitude() * 1E6));
values.put(TrackPointsColumns.LONGITUDE, (int) (trackPoint.getLongitude() * 1E6));
values.put(TrackPointsColumns.LATITUDE, (int) (trackPoint.getLatitude() * 1E6));
values.put(TrackPointsColumns.TIME, location.getTime());
if (location.hasAltitude()) {
values.put(TrackPointsColumns.ALTITUDE, location.getAltitude());
values.put(TrackPointsColumns.TIME, trackPoint.getTime());
if (trackPoint.hasAltitude()) {
values.put(TrackPointsColumns.ALTITUDE, trackPoint.getAltitude());
}
if (location.hasAccuracy()) {
values.put(TrackPointsColumns.ACCURACY, location.getAccuracy());
if (trackPoint.hasAccuracy()) {
values.put(TrackPointsColumns.ACCURACY, trackPoint.getAccuracy());
}
if (location.hasSpeed()) {
values.put(TrackPointsColumns.SPEED, location.getSpeed());
if (trackPoint.hasSpeed()) {
values.put(TrackPointsColumns.SPEED, trackPoint.getSpeed());
}
if (location.hasBearing()) {
values.put(TrackPointsColumns.BEARING, location.getBearing());
if (trackPoint.hasBearing()) {
values.put(TrackPointsColumns.BEARING, trackPoint.getBearing());
}
//SensorData
if (location instanceof TrackPoint) {
TrackPoint sensorDataSetLocation = (TrackPoint) location;
SensorDataSet sensorDataSet = sensorDataSetLocation.getSensorDataSet();
if (sensorDataSet != null && sensorDataSet.hasHeartRate()) {
values.put(TrackPointsColumns.SENSOR_HEARTRATE, sensorDataSetLocation.getSensorDataSet().getHeartRate());
}
if (sensorDataSet != null && sensorDataSet.hasCadence()) {
values.put(TrackPointsColumns.SENSOR_CADENCE, sensorDataSetLocation.getSensorDataSet().getCadence());
}
if (sensorDataSet != null && sensorDataSet.hasPower()) {
values.put(TrackPointsColumns.SENSOR_POWER, sensorDataSetLocation.getSensorDataSet().getPower());
}
SensorDataSet sensorDataSet = trackPoint.getSensorDataSet();
if (sensorDataSet != null && sensorDataSet.hasHeartRate()) {
values.put(TrackPointsColumns.SENSOR_HEARTRATE, trackPoint.getSensorDataSet().getHeartRate());
}
if (sensorDataSet != null && sensorDataSet.hasCadence()) {
values.put(TrackPointsColumns.SENSOR_CADENCE, trackPoint.getSensorDataSet().getCadence());
}
if (sensorDataSet != null && sensorDataSet.hasPower()) {
values.put(TrackPointsColumns.SENSOR_POWER, trackPoint.getSensorDataSet().getPower());
}
return values;
}
@@ -875,7 +868,7 @@ public class ContentProviderUtils {
* Creates a new read-only iterator over a given track's points.
* It provides a lightweight way of iterating over long tracks without failing due to the underlying cursor limitations.
* Since it's a read-only iterator, {@link Iterator#remove()} always throws {@link UnsupportedOperationException}.
* Each call to {@link TrackPointIterator#next()} may advance to the next DB record, and if so, the iterator calls {@link TrackPointFactory#createLocation()} and populates it with information retrieved from the record.
* Each call to {@link TrackPointIterator#next()} may advance to the next DB record, and if so, the iterator calls {@link TrackPointFactory#create()} and populates it with information retrieved from the record.
* When done with iteration, {@link TrackPointIterator#close()} must be called.
*
* @param trackId the track id
@@ -887,7 +880,7 @@ public class ContentProviderUtils {
return new TrackPointIterator(this, trackId, startTrackPointId, descending, trackPointFactory);
}
private Location findTrackPointBy(String selection, String[] selectionArgs) {
private TrackPoint findTrackPointBy(String selection, String[] selectionArgs) {
try (Cursor cursor = getTrackPointCursor(null, selection, selectionArgs, TrackPointsColumns._ID)) {
if (cursor != null && cursor.moveToNext()) {
return createTrackPoint(cursor);
@@ -15,7 +15,7 @@ public class TrackPointFactory {
*/
public static final TrackPointFactory DEFAULT_LOCATION_FACTORY = new TrackPointFactory();
public TrackPoint createLocation() {
public TrackPoint create() {
return new TrackPoint(LocationManager.GPS_PROVIDER);
}
}
@@ -91,7 +91,7 @@ public class TrackPointIterator implements Iterator<TrackPoint>, AutoCloseable {
}
}
lastTrackPointId = cursor.getLong(indexes.idIndex);
TrackPoint trackPoint = trackPointFactory.createLocation();
TrackPoint trackPoint = trackPointFactory.create();
ContentProviderUtils.fillTrackPoint(cursor, indexes, trackPoint);
return trackPoint;
}
@@ -17,7 +17,6 @@
package de.dennisguse.opentracks.fragments;
import android.content.SharedPreferences;
import android.location.Location;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
@@ -220,18 +219,18 @@ public class ChartFragment extends Fragment implements TrackDataListener {
}
@Override
public void onSampledInTrackPoint(Location location) {
public void onSampledInTrackPoint(TrackPoint trackPoint) {
if (isResumed()) {
double[] data = new double[ChartView.NUM_SERIES + 1];
fillDataPoint(location, data);
fillDataPoint(trackPoint, data);
pendingPoints.add(data);
}
}
@Override
public void onSampledOutTrackPoint(Location location) {
public void onSampledOutTrackPoint(TrackPoint trackPoint) {
if (isResumed()) {
fillDataPoint(location, null);
fillDataPoint(trackPoint, null);
}
}
@@ -351,7 +350,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
}
/**
* Given a location, fill in a data point, an array of double[]. <br>
* Given a trackPoint, fill in a data point, an array of double[]. <br>
* data[0] = time/distance <br>
* data[1] = elevation <br>
* data[2] = speed <br>
@@ -360,18 +359,18 @@ public class ChartFragment extends Fragment implements TrackDataListener {
* data[5] = cadence <br>
* data[6] = power <br>
*
* @param location the location
* @param trackPoint the trackPoint
* @param data the data point to fill in, can be null
*/
@VisibleForTesting
void fillDataPoint(Location location, double[] data) {
void fillDataPoint(@NonNull TrackPoint trackPoint, double[] data) {
double timeOrDistance = Double.NaN;
double elevation = Double.NaN;
double speed = Double.NaN;
double pace = Double.NaN;
if (tripStatisticsUpdater != null) {
tripStatisticsUpdater.addLocation(location, recordingDistanceInterval);
tripStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
TripStatistics tripStatistics = tripStatisticsUpdater.getTripStatistics();
if (chartByDistance) {
double distance = tripStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
@@ -398,8 +397,8 @@ public class ChartFragment extends Fragment implements TrackDataListener {
double heartRate = Double.NaN;
double cadence = Double.NaN;
double power = Double.NaN;
if (location instanceof TrackPoint && ((TrackPoint) location).getSensorDataSet() != null) {
SensorDataSet sensorDataSet = ((TrackPoint) location).getSensorDataSet();
if (trackPoint.getSensorDataSet() != null) {
SensorDataSet sensorDataSet = trackPoint.getSensorDataSet();
if (sensorDataSet.hasHeartRate()) {
heartRate = sensorDataSet.getHeartRate();
}
@@ -17,7 +17,6 @@
package de.dennisguse.opentracks.fragments;
import android.content.SharedPreferences;
import android.location.Location;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
@@ -42,6 +41,7 @@ import de.dennisguse.opentracks.content.TrackDataHub;
import de.dennisguse.opentracks.content.TrackDataListener;
import de.dennisguse.opentracks.content.TrackDataType;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
@@ -72,7 +72,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
//TODO Initialize immediately and remove in onDestroy()
private TrackRecordingServiceConnection trackRecordingServiceConnection;
private Location lastLocation = null;
private TrackPoint lastTrackPoint = null;
private TripStatistics lastTripStatistics = null;
private String category = "";
@@ -333,17 +333,17 @@ public class StatsFragment extends Fragment implements TrackDataListener {
@Override
public void clearTrackPoints() {
lastLocation = null;
lastTrackPoint = null;
}
@Override
public void onSampledInTrackPoint(Location location) {
lastLocation = location;
public void onSampledInTrackPoint(TrackPoint trackPoint) {
lastTrackPoint = trackPoint;
}
@Override
public void onSampledOutTrackPoint(Location location) {
lastLocation = location;
public void onSampledOutTrackPoint(TrackPoint trackPoint) {
lastTrackPoint = trackPoint;
}
@Override
@@ -354,15 +354,15 @@ public class StatsFragment extends Fragment implements TrackDataListener {
public void run() {
if (isResumed()) {
if (!isSelectedTrackRecording() || isSelectedTrackPaused()) {
lastLocation = null;
lastTrackPoint = null;
}
if (lastLocation != null) {
boolean hasFix = !LocationUtils.isLocationOld(lastLocation);
boolean hasGoodFix = lastLocation.hasAccuracy() && lastLocation.getAccuracy() < recordingGpsAccuracy;
if (lastTrackPoint != null) {
boolean hasFix = !LocationUtils.isLocationOld(lastTrackPoint);
boolean hasGoodFix = lastTrackPoint.hasAccuracy() && lastTrackPoint.getAccuracy() < recordingGpsAccuracy;
if (!hasFix || !hasGoodFix) {
lastLocation = null;
lastTrackPoint = null;
}
}
setLocationValues();
@@ -573,7 +573,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
if (isRecording) {
speedLabel.setText(reportSpeed ? R.string.stats_speed : R.string.stats_pace);
double speed = lastLocation != null && lastLocation.hasSpeed() ? lastLocation.getSpeed() : Double.NaN;
double speed = lastTrackPoint != null && lastTrackPoint.hasSpeed() ? lastTrackPoint.getSpeed() : Double.NaN;
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, metricUnits, reportSpeed);
speedValue.setText(parts.first);
speedUnit.setText(parts.second);
@@ -584,7 +584,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
gradeElevationContainer.setVisibility(showGradeElevation ? View.VISIBLE : View.GONE);
if (showGradeElevation) {
double altitude = lastLocation != null && lastLocation.hasAltitude() ? lastLocation.getAltitude() : Double.NaN;
double altitude = lastTrackPoint != null && lastTrackPoint.hasAltitude() ? lastTrackPoint.getAltitude() : Double.NaN;
Pair<String, String> parts = StringUtils.formatElevation(getContext(), altitude, metricUnits);
elevationValue.setText(parts.first);
@@ -597,11 +597,11 @@ public class StatsFragment extends Fragment implements TrackDataListener {
coordinateSeparator.setVisibility(showCoordinate ? View.VISIBLE : View.GONE);
coordinateContainer.setVisibility(showCoordinate ? View.VISIBLE : View.GONE);
if (showCoordinate) {
double latitude = lastLocation != null ? lastLocation.getLatitude() : Double.NaN;
double latitude = lastTrackPoint != null ? lastTrackPoint.getLatitude() : Double.NaN;
String latitudeText = Double.isNaN(latitude) || Double.isInfinite(latitude) ? getContext().getString(R.string.value_unknown) : StringUtils.formatCoordinate(latitude);
latitudeValue.setText(latitudeText);
double longitude = lastLocation != null ? lastLocation.getLongitude() : Double.NaN;
double longitude = lastTrackPoint != null ? lastTrackPoint.getLongitude() : Double.NaN;
String longitudeText = Double.isNaN(longitude) || Double.isInfinite(longitude) ? getContext().getString(R.string.value_unknown) : StringUtils.formatCoordinate(longitude);
longitudeValue.setText(longitudeText);
}
@@ -18,7 +18,6 @@ package de.dennisguse.opentracks.io.file.exporter;
import android.content.Context;
import android.database.Cursor;
import android.location.Location;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -129,22 +128,22 @@ public class FileTrackExporter implements TrackExporter {
TrackWriterTrackPointFactory trackPointFactory = new TrackWriterTrackPointFactory();
int locationNumber = 0;
try (TrackPointIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false, trackPointFactory)) {
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false, trackPointFactory)) {
while (locationIterator.hasNext()) {
while (trackPointIterator.hasNext()) {
if (Thread.interrupted()) {
throw new InterruptedException();
}
Location location = locationIterator.next();
TrackPoint trackPoint = trackPointIterator.next();
setLocationTime(location, offset);
setLocationTime(trackPoint, offset);
locationNumber++;
boolean isLocationValid = LocationUtils.isValidLocation(location);
boolean isLocationValid = LocationUtils.isValidLocation(trackPoint);
boolean isSegmentValid = isLocationValid && isLastLocationValid;
if (!wroteTrack && isSegmentValid) {
// Found the first two consecutive locations that are valid
trackWriter.writeBeginTrack(track, trackPointFactory.lastLocation);
trackWriter.writeBeginTrack(track, trackPointFactory.lastTrackPoint);
wroteTrack = true;
}
@@ -154,14 +153,14 @@ public class FileTrackExporter implements TrackExporter {
trackWriter.writeOpenSegment();
wroteSegment = true;
// Write the previous location, which we had previously skipped
trackWriter.writeLocation(trackPointFactory.lastLocation);
// Write the previous trackPoint, which we had previously skipped
trackWriter.writeTrackPoint(trackPointFactory.lastTrackPoint);
}
// Write the current location
trackWriter.writeLocation(location);
// Write the current trackPoint
trackWriter.writeTrackPoint(trackPoint);
if (trackExporterListener != null) {
trackExporterListener.onProgressUpdate(locationNumber, locationIterator.getCount());
trackExporterListener.onProgressUpdate(locationNumber, trackPointIterator.getCount());
}
} else {
if (wroteSegment) {
@@ -179,7 +178,7 @@ public class FileTrackExporter implements TrackExporter {
}
if (wroteTrack) {
Location lastValidTrackPoint = contentProviderUtils.getLastValidTrackPoint(track.getId());
TrackPoint lastValidTrackPoint = contentProviderUtils.getLastValidTrackPoint(track.getId());
setLocationTime(lastValidTrackPoint, offset);
trackWriter.writeEndTrack(track, lastValidTrackPoint);
} else {
@@ -191,40 +190,40 @@ public class FileTrackExporter implements TrackExporter {
}
/**
* Sets a location time.
* Sets a trackPoint time.
*
* @param location the location
* @param trackPoint the trackPoint
* @param offset the time offset
*/
private void setLocationTime(Location location, long offset) {
if (location != null) {
location.setTime(location.getTime() - offset);
private void setLocationTime(TrackPoint trackPoint, long offset) {
if (trackPoint != null) {
trackPoint.setTime(trackPoint.getTime() - offset);
}
}
/**
* Track writer location factory. Keeping the last two locations.
* Keeping the last two locations.
*
* @author Jimmy Shih
*/
private class TrackWriterTrackPointFactory extends TrackPointFactory {
TrackPoint currentLocation;
TrackPoint lastLocation;
TrackPoint currentTrackPoint;
TrackPoint lastTrackPoint;
@Override
public TrackPoint createLocation() {
if (currentLocation == null) {
currentLocation = new TrackPoint("");
public TrackPoint create() {
if (currentTrackPoint == null) {
currentTrackPoint = new TrackPoint("");
}
return currentLocation;
return currentTrackPoint;
}
void swapLocations() {
TrackPoint tempLocation = lastLocation;
lastLocation = currentLocation;
currentLocation = tempLocation;
if (currentLocation != null) {
currentLocation.reset();
TrackPoint tempTrackPoint = lastTrackPoint;
lastTrackPoint = currentTrackPoint;
currentTrackPoint = tempTrackPoint;
if (currentTrackPoint != null) {
currentTrackPoint.reset();
}
}
}
@@ -24,6 +24,7 @@ import java.text.NumberFormat;
import java.util.Locale;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.util.StringUtils;
@@ -139,7 +140,7 @@ public class GpxTrackWriter implements TrackWriter {
}
@Override
public void writeBeginTrack(Track track, Location startLocation) {
public void writeBeginTrack(Track track, TrackPoint startTrackPoint) {
if (printWriter != null) {
printWriter.println("<trk>");
printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>");
@@ -150,7 +151,7 @@ public class GpxTrackWriter implements TrackWriter {
}
@Override
public void writeEndTrack(Track track, Location endLocation) {
public void writeEndTrack(Track track, TrackPoint endTrackPoint) {
if (printWriter != null) {
printWriter.println("</trk>");
}
@@ -167,14 +168,14 @@ public class GpxTrackWriter implements TrackWriter {
}
@Override
public void writeLocation(Location location) {
public void writeTrackPoint(TrackPoint trackPoint) {
if (printWriter != null) {
printWriter.println("<trkpt " + formatLocation(location) + ">");
if (location.hasAltitude()) {
printWriter.println("<ele>" + ELEVATION_FORMAT.format(location.getAltitude()) + "</ele>");
printWriter.println("<trkpt " + formatLocation(trackPoint) + ">");
if (trackPoint.hasAltitude()) {
printWriter.println("<ele>" + ELEVATION_FORMAT.format(trackPoint.getAltitude()) + "</ele>");
}
printWriter.println(
"<time>" + StringUtils.formatDateTimeIso8601(location.getTime()) + "</time>");
"<time>" + StringUtils.formatDateTimeIso8601(trackPoint.getTime()) + "</time>");
printWriter.println("</trkpt>");
}
}
@@ -69,7 +69,7 @@ public class KmlTrackWriter implements TrackWriter {
private final List<Float> cadenceList = new ArrayList<>();
private final List<Float> heartRateList = new ArrayList<>();
private Location startLocation;
private TrackPoint startTrackPoint;
/**
* @param context the context
@@ -188,11 +188,11 @@ public class KmlTrackWriter implements TrackWriter {
}
@Override
public void writeBeginTrack(Track track, Location startLocation) {
this.startLocation = startLocation;
public void writeBeginTrack(Track track, TrackPoint startTrackPoint) {
this.startTrackPoint = startTrackPoint;
if (printWriter != null) {
String name = context.getString(R.string.marker_label_start, track.getName());
writePlacemark(name, "", "", START_STYLE, startLocation);
writePlacemark(name, "", "", START_STYLE, startTrackPoint);
printWriter.println("<Placemark>");
if (exportTrackDetail) {
@@ -210,7 +210,7 @@ public class KmlTrackWriter implements TrackWriter {
}
@Override
public void writeEndTrack(Track track, Location endLocation) {
public void writeEndTrack(Track track, TrackPoint endTrackPoint) {
if (printWriter != null) {
printWriter.println("</gx:MultiTrack>");
printWriter.println("</Placemark>");
@@ -218,7 +218,7 @@ public class KmlTrackWriter implements TrackWriter {
if (exportTrackDetail) {
String name = context.getString(R.string.marker_label_end, track.getName());
String description = descriptionGenerator.generateTrackDescription(track, false);
writePlacemark(name, "", description, END_STYLE, endLocation);
writePlacemark(name, "", description, END_STYLE, endTrackPoint);
}
}
}
@@ -256,16 +256,16 @@ public class KmlTrackWriter implements TrackWriter {
}
@Override
public void writeLocation(Location location) {
public void writeTrackPoint(TrackPoint trackPoint) {
if (printWriter != null) {
if (exportTrackDetail) {
printWriter.println("<when>" + getTime(location) + "</when>");
printWriter.println("<when>" + getTime(trackPoint) + "</when>");
}
printWriter.println("<gx:coord>" + getCoordinates(location, " ") + "</gx:coord>");
printWriter.println("<gx:coord>" + getCoordinates(trackPoint, " ") + "</gx:coord>");
if (exportSensorData && location instanceof TrackPoint) {
SensorDataSet sensorDataSet = ((TrackPoint) location).getSensorDataSet();
if (exportSensorData) {
SensorDataSet sensorDataSet = trackPoint.getSensorDataSet();
if (sensorDataSet != null) {
if (sensorDataSet.hasHeartRate()) {
heartRateList.add(sensorDataSet.getHeartRate());
@@ -371,7 +371,7 @@ public class KmlTrackWriter implements TrackWriter {
if (exportTrackDetail) {
return StringUtils.formatDateTimeIso8601(location.getTime());
} else {
return StringUtils.formatDateTimeIso8601(location.getTime() - startLocation.getTime());
return StringUtils.formatDateTimeIso8601(location.getTime() - startTrackPoint.getTime());
}
}
@@ -25,8 +25,8 @@ public interface TrackExporterListener {
/**
* Called to update progress.
*
* @param number the number of locations written
* @param max the maximum number of locations in a track, for calculation of
* @param number the number of {@link de.dennisguse.opentracks.content.data.TrackPoint}s written
* @param max the maximum number of {@link de.dennisguse.opentracks.content.data.TrackPoint}s in a {@link de.dennisguse.opentracks.content.data.Track}, for calculation of
* completion percentage
*/
void onProgressUpdate(int number, int max);
@@ -15,11 +15,10 @@
*/
package de.dennisguse.opentracks.io.file.exporter;
import android.location.Location;
import java.io.OutputStream;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.Waypoint;
/**
@@ -35,13 +34,13 @@ import de.dennisguse.opentracks.content.data.Waypoint;
* {@link #writeEndWaypoints()}
* {@link #writeBeginTracks()}
* For each track:
* {@link #writeBeginTrack(Track, Location)}
* {@link #writeBeginTrack(Track, TrackPoint)}
* For each segment:
* {@link #writeOpenSegment()}
* For each location in the segment:
* {@link #writeLocation(Location)}
* For each trackPoint in the segment:
* {@link #writeTrackPoint(TrackPoint)}
* {@link #writeCloseSegment()}
* {@link #writeEndTrack(Track, Location)}
* {@link #writeEndTrack(Track, TrackPoint)}
* {@link #writeEndTracks()}
* {@link #writeFooter()}
* {@link #close()}
@@ -107,18 +106,18 @@ public interface TrackWriter {
/**
* Writes the beginning of a track.
*
* @param track the track
* @param startLocation the start location
* @param track the track
* @param startTrackPoint the start location
*/
void writeBeginTrack(Track track, Location startLocation);
void writeBeginTrack(Track track, TrackPoint startTrackPoint);
/**
* Writes the end of a track.
*
* @param track the track
* @param endLocation the end location
* @param track the track
* @param endTrackPoint the end location
*/
void writeEndTrack(Track track, Location endLocation);
void writeEndTrack(Track track, TrackPoint endTrackPoint);
/**
* Writes open segment.
@@ -131,9 +130,9 @@ public interface TrackWriter {
void writeCloseSegment();
/**
* Writes a location.
* Writes a trackPoint.
*
* @param location the location
* @param trackPoint the trackPoint
*/
void writeLocation(Location location);
void writeTrackPoint(TrackPoint trackPoint);
}
@@ -39,6 +39,7 @@ import javax.xml.parsers.SAXParserFactory;
import de.dennisguse.opentracks.R;
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.data.Waypoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
@@ -158,12 +159,12 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
int waypointPosition = -1;
Waypoint waypoint = null;
Location location = null;
TrackPoint trackPoint = null;
TripStatisticsUpdater trackTripStatisticstrackUpdater = new TripStatisticsUpdater(track.getTripStatistics().getStartTime());
@Deprecated // TODO Should not be necessary anymore?
TripStatisticsUpdater markerTripStatisticsUpdater = new TripStatisticsUpdater(track.getTripStatistics().getStartTime());
try (TrackPointIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false, TrackPointFactory.DEFAULT_LOCATION_FACTORY)) {
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false, TrackPointFactory.DEFAULT_LOCATION_FACTORY)) {
while (true) {
if (waypoint == null) {
@@ -175,38 +176,38 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
}
}
if (location == null) {
if (!locationIterator.hasNext()) {
if (trackPoint == null) {
if (!trackPointIterator.hasNext()) {
// No more track points. Ignore the rest of the waypoints.
return;
}
location = locationIterator.next();
trackTripStatisticstrackUpdater.addLocation(location, recordingDistanceInterval);
markerTripStatisticsUpdater.addLocation(location, recordingDistanceInterval);
trackPoint = trackPointIterator.next();
trackTripStatisticstrackUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
markerTripStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
}
if (waypoint.getLocation().getTime() > location.getTime()) {
location = null;
} else if (waypoint.getLocation().getTime() < location.getTime()) {
Log.w(TAG, "Ignoring waypoint: current location was after waypoint.");
if (waypoint.getLocation().getTime() > trackPoint.getTime()) {
trackPoint = null;
} else if (waypoint.getLocation().getTime() < trackPoint.getTime()) {
Log.w(TAG, "Ignoring waypoint: current trackPoint was after waypoint.");
waypoint = null;
} else {
// The waypoint location time matches the track point time
if (!LocationUtils.isValidLocation(location)) {
// Invalid location, load the next location
location = null;
// The waypoint trackPoint time matches the track point time
if (!LocationUtils.isValidLocation(trackPoint)) {
// Invalid trackPoint, load the next trackPoint
trackPoint = null;
continue;
}
// Valid location
if (location.getLatitude() == waypoint.getLocation().getLatitude() && location.getLongitude() == waypoint.getLocation().getLongitude()) {
// Valid trackPoint
if (trackPoint.getLatitude() == waypoint.getLocation().getLatitude() && trackPoint.getLongitude() == waypoint.getLocation().getLongitude()) {
String waypointDescription = waypoint.getDescription();
String icon = context.getString(R.string.marker_waypoint_icon_url);
double length = trackTripStatisticstrackUpdater.getTripStatistics().getTotalDistance();
long duration = trackTripStatisticstrackUpdater.getTripStatistics().getTotalTime();
// Insert waypoint
Waypoint newWaypoint = new Waypoint(waypoint.getName(), waypointDescription, waypoint.getCategory(), icon, track.getId(), length, duration, location, waypoint.getPhotoUrl());
Waypoint newWaypoint = new Waypoint(waypoint.getName(), waypointDescription, waypoint.getCategory(), icon, track.getId(), length, duration, trackPoint, waypoint.getPhotoUrl());
contentProviderUtils.insertWaypoint(newWaypoint);
}
@@ -273,7 +274,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(createLocation(TrackPointsColumns.PAUSE_LATITUDE, 0.0, 0.0, trackData.lastLocationInCurrentSegment.getTime()));
insertLocation(createTrackPoint(TrackPointsColumns.PAUSE_LATITUDE, 0.0, 0.0, trackData.lastLocationInCurrentSegment.getTime()));
}
trackData.lastLocationInCurrentSegment = null;
}
@@ -288,7 +289,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
}
Waypoint waypoint = new Waypoint();
Location location = createLocation();
TrackPoint location = createTrackPoint();
if (!LocationUtils.isValidLocation(location)) {
throw new SAXException(createErrorMessage("Invalid location detected: " + location));
@@ -314,8 +315,8 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
/**
* Gets a track point.
*/
protected Location getTrackPoint() throws SAXException {
Location location = createLocation();
protected TrackPoint getTrackPoint() throws SAXException {
TrackPoint location = createTrackPoint();
// Calculate derived attributes from the previous point
if (trackData.lastLocationInCurrentSegment != null && trackData.lastLocationInCurrentSegment.getTime() != 0) {
@@ -343,7 +344,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(createLocation(TrackPointsColumns.RESUME_LATITUDE, 0.0, 0.0, location.getTime()));
insertLocation(createTrackPoint(TrackPointsColumns.RESUME_LATITUDE, 0.0, 0.0, location.getTime()));
}
trackData.lastLocationInCurrentSegment = location;
return location;
@@ -352,10 +353,10 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
/**
* Inserts a track point.
*
* @param location the location
* @param trackPoint the trackPoint
*/
protected void insertTrackPoint(Location location) {
insertLocation(location);
protected void insertTrackPoint(TrackPoint trackPoint) {
insertLocation(trackPoint);
}
/**
@@ -384,7 +385,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
/**
* Creates a location.
*/
private Location createLocation() throws SAXException {
private TrackPoint createTrackPoint() throws SAXException {
if (latitude == null || longitude == null) {
return null;
}
@@ -415,7 +416,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse time: %s", time)), e);
}
}
return createLocation(latitudeValue, longitudeValue, altitudeValue, timeValue);
return createTrackPoint(latitudeValue, longitudeValue, altitudeValue, timeValue);
}
/**
@@ -426,8 +427,8 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
* @param altitudeValue the altitude value
* @param timeValue the time value
*/
private Location createLocation(double latitudeValue, double longitudeValue, Double altitudeValue, long timeValue) {
Location location = new Location(LocationManager.GPS_PROVIDER);
private TrackPoint createTrackPoint(double latitudeValue, double longitudeValue, Double altitudeValue, long timeValue) {
TrackPoint location = new TrackPoint(LocationManager.GPS_PROVIDER);
location.setLatitude(latitudeValue);
location.setLongitude(longitudeValue);
if (altitudeValue != null) {
@@ -443,17 +444,17 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
}
/**
* Inserts a location.
* Inserts a trackPoint.
*
* @param location the location
* @param trackPoint the trackPoint
*/
private void insertLocation(Location location) {
private void insertLocation(TrackPoint trackPoint) {
if (trackData.tripStatisticsUpdater == null) {
trackData.tripStatisticsUpdater = new TripStatisticsUpdater(location.getTime() != -1L ? location.getTime() : trackData.importTime);
trackData.tripStatisticsUpdater = new TripStatisticsUpdater(trackPoint.getTime() != -1L ? trackPoint.getTime() : trackData.importTime);
}
trackData.tripStatisticsUpdater.addLocation(location, recordingDistanceInterval);
trackData.tripStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
trackData.bufferedLocations[trackData.numBufferedLocations] = location;
trackData.bufferedTrackPoints[trackData.numBufferedLocations] = trackPoint;
trackData.numBufferedLocations++;
trackData.numberOfLocations++;
@@ -471,7 +472,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
if (data.numBufferedLocations <= 0) {
return;
}
contentProviderUtils.bulkInsertTrackPoint(data.bufferedLocations, data.numBufferedLocations, data.track.getId());
contentProviderUtils.bulkInsertTrackPoint(data.bufferedTrackPoints, data.numBufferedLocations, data.track.getId());
data.numBufferedLocations = 0;
}
@@ -510,7 +511,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
final long importTime = System.currentTimeMillis();
// The buffered locations
final Location[] bufferedLocations = new Location[MAX_BUFFERED_LOCATIONS];
final TrackPoint[] bufferedTrackPoints = new TrackPoint[MAX_BUFFERED_LOCATIONS];
// The number of buffered locations
int numBufferedLocations = 0;
@@ -17,13 +17,13 @@
package de.dennisguse.opentracks.io.file.importer;
import android.content.Context;
import android.location.Location;
import androidx.annotation.VisibleForTesting;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
/**
@@ -155,11 +155,11 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
* On track point end.
*/
private void onTrackPointEnd() throws SAXException {
Location location = getTrackPoint();
if (location == null) {
TrackPoint trackPoint = getTrackPoint();
if (trackPoint == null) {
return;
}
insertTrackPoint(location);
insertTrackPoint(trackPoint);
}
/**
@@ -17,7 +17,6 @@
package de.dennisguse.opentracks.io.file.importer;
import android.content.Context;
import android.location.Location;
import android.net.Uri;
import android.util.Log;
@@ -65,7 +64,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
private boolean trackStarted = false;
private String sensorType;
private ArrayList<Location> locationList;
private ArrayList<TrackPoint> trackPoints;
private ArrayList<Float> cadenceList;
private ArrayList<Float> heartRateList;
private ArrayList<Float> powerList;
@@ -213,7 +212,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
@Override
protected void onTrackSegmentStart() {
super.onTrackSegmentStart();
locationList = new ArrayList<>();
trackPoints = new ArrayList<>();
heartRateList = new ArrayList<>();
cadenceList = new ArrayList<>();
powerList = new ArrayList<>();
@@ -224,8 +223,8 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
*/
private void onTrackSegmentEnd() {
// Close a track segment by inserting the segment locations
for (int i = 0; i < locationList.size(); i++) {
Location location = locationList.get(i);
for (int i = 0; i < trackPoints.size(); i++) {
TrackPoint trackPoint = trackPoints.get(i);
boolean hasSensorData = false;
float heartrate = SensorDataSet.DATA_UNAVAILABLE;
@@ -246,9 +245,9 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
}
if (!hasSensorData) {
insertTrackPoint(location);
insertTrackPoint(trackPoint);
} else {
TrackPoint sensorDataSetLocation = new TrackPoint(location, new SensorDataSet(heartrate, cadence, power, SensorDataSet.DATA_UNAVAILABLE, location.getTime()));
TrackPoint sensorDataSetLocation = new TrackPoint(trackPoint, new SensorDataSet(heartrate, cadence, power, SensorDataSet.DATA_UNAVAILABLE, trackPoint.getTime()));
insertTrackPoint(sensorDataSetLocation);
}
}
@@ -270,11 +269,11 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
latitude = parts[1];
altitude = parts.length == 3 ? parts[2] : null;
Location location = getTrackPoint();
TrackPoint location = getTrackPoint();
if (location == null) {
return;
}
locationList.add(location);
trackPoints.add(location);
time = null;
}
@@ -43,7 +43,6 @@ import de.dennisguse.opentracks.TrackDetailActivity;
import de.dennisguse.opentracks.TrackListActivity;
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.data.Waypoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
@@ -140,7 +139,7 @@ public class TrackRecordingService extends Service {
private BluetoothRemoteSensorManager remoteSensorManager;
private TripStatisticsUpdater trackTripStatisticsUpdater;
private Location lastLocation;
private TrackPoint lastTrackPoint;
private boolean currentSegmentHasLocation;
private boolean isIdle;
@@ -296,9 +295,9 @@ public class TrackRecordingService extends Service {
name = getString(R.string.marker_name_format, nextWaypointNumber + 1);
}
Location location = getLastValidTrackPointInCurrentSegment(recordingTrackId);
if (location == null) {
Log.i(TAG, "Could not create a waypoint as location is unknown.");
TrackPoint trackPoint = getLastValidTrackPointInCurrentSegment(recordingTrackId);
if (trackPoint == null) {
Log.i(TAG, "Could not create a waypoint as trackPoint is unknown.");
return -1L;
}
@@ -312,7 +311,7 @@ public class TrackRecordingService extends Service {
long duration = stats.getTotalTime();
// Insert waypoint
Waypoint waypoint = new Waypoint(name, description, category, icon, recordingTrackId, length, duration, location, photoUrl);
Waypoint waypoint = new Waypoint(name, description, category, icon, recordingTrackId, length, duration, trackPoint, photoUrl);
Uri uri = contentProviderUtils.insertWaypoint(waypoint);
return ContentUris.parseId(uri);
}
@@ -380,7 +379,7 @@ public class TrackRecordingService extends Service {
trackTripStatisticsUpdater = new TripStatisticsUpdater(tripStatistics.getStartTime());
try (TrackPointIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false, TrackPointFactory.DEFAULT_LOCATION_FACTORY)) {
trackTripStatisticsUpdater.addLocation(locationIterator, recordingDistanceInterval);
trackTripStatisticsUpdater.addTrackPoint(locationIterator, recordingDistanceInterval);
} catch (RuntimeException e) {
Log.e(TAG, "RuntimeException", e);
}
@@ -400,11 +399,7 @@ public class TrackRecordingService extends Service {
// Update database
Track track = contentProviderUtils.getTrack(recordingTrackId);
if (track != null) {
Location resume = new Location(LocationManager.GPS_PROVIDER);
resume.setLongitude(0);
resume.setLatitude(TrackPointsColumns.RESUME_LATITUDE);
resume.setTime(System.currentTimeMillis());
insertLocation(track, resume, null);
insertTrackPoint(track, TrackPoint.createResume(), null);
}
startRecording();
@@ -417,7 +412,7 @@ public class TrackRecordingService extends Service {
// Update instance variables
remoteSensorManager = new BluetoothRemoteSensorManager(this);
remoteSensorManager.start();
lastLocation = null;
lastTrackPoint = null;
currentSegmentHasLocation = false;
isIdle = false;
@@ -457,7 +452,7 @@ public class TrackRecordingService extends Service {
if (track != null) {
// If not paused, add the last location
if (!paused) {
insertLocation(track, lastLocation, getLastValidTrackPointInCurrentSegment(trackId));
insertTrackPoint(track, lastTrackPoint, getLastValidTrackPointInCurrentSegment(trackId));
// Update the recording track time
updateRecordingTrack(track);
@@ -485,13 +480,9 @@ public class TrackRecordingService extends Service {
// Update database
Track track = contentProviderUtils.getTrack(recordingTrackId);
if (track != null) {
insertLocation(track, lastLocation, getLastValidTrackPointInCurrentSegment(track.getId()));
insertTrackPoint(track, lastTrackPoint, getLastValidTrackPointInCurrentSegment(track.getId()));
Location pause = new Location(LocationManager.GPS_PROVIDER);
pause.setLongitude(0);
pause.setLatitude(TrackPointsColumns.PAUSE_LATITUDE);
pause.setTime(System.currentTimeMillis());
insertLocation(track, pause, null);
insertTrackPoint(track, TrackPoint.createPause(), null);
}
endRecording(false);
@@ -513,7 +504,7 @@ public class TrackRecordingService extends Service {
remoteSensorManager.stop();
remoteSensorManager = null;
}
lastLocation = null;
lastTrackPoint = null;
stopGps(trackStopped);
}
@@ -540,7 +531,7 @@ public class TrackRecordingService extends Service {
* @param trackId the track id
* @return the location or null
*/
private Location getLastValidTrackPointInCurrentSegment(long trackId) {
private TrackPoint getLastValidTrackPointInCurrentSegment(long trackId) {
if (!currentSegmentHasLocation) {
return null;
}
@@ -590,7 +581,7 @@ public class TrackRecordingService extends Service {
location.setTime(System.currentTimeMillis());
}
Location lastValidTrackPoint = getLastValidTrackPointInCurrentSegment(track.getId());
TrackPoint lastValidTrackPoint = getLastValidTrackPointInCurrentSegment(track.getId());
long idleTime = 0L;
if (lastValidTrackPoint != null && location.getTime() > lastValidTrackPoint.getTime()) {
idleTime = location.getTime() - lastValidTrackPoint.getTime();
@@ -600,77 +591,69 @@ public class TrackRecordingService extends Service {
registerLocationListener();
}
SensorDataSet sensorDataSet = getSensorDataSet();
if (sensorDataSet != null) {
location = new TrackPoint(location, sensorDataSet);
}
TrackPoint trackPoint = new TrackPoint(location, getSensorDataSet());
// Always insert the first segment location
if (!currentSegmentHasLocation) {
insertLocation(track, location, null);
insertTrackPoint(track, trackPoint, null);
currentSegmentHasLocation = true;
lastLocation = location;
lastTrackPoint = trackPoint;
return;
}
if (!LocationUtils.isValidLocation(lastValidTrackPoint)) {
// Should not happen. The current segment should have a location. Just insert the current location.
insertLocation(track, location, null);
lastLocation = location;
insertTrackPoint(track, trackPoint, null);
lastTrackPoint = trackPoint;
return;
}
double distanceToLastTrackLocation = location.distanceTo(lastValidTrackPoint);
if (distanceToLastTrackLocation > maxRecordingDistance) {
insertLocation(track, lastLocation, lastValidTrackPoint);
insertTrackPoint(track, lastTrackPoint, lastValidTrackPoint);
insertTrackPoint(track, TrackPoint.createPause(), null);
Location pause = new Location(LocationManager.GPS_PROVIDER);
pause.setLongitude(0);
pause.setLatitude(TrackPointsColumns.PAUSE_LATITUDE);
pause.setTime(lastLocation.getTime());
insertLocation(track, pause, null);
insertLocation(track, location, null);
insertTrackPoint(track, trackPoint, null);
isIdle = false;
} else if (sensorDataSet != null || distanceToLastTrackLocation >= recordingDistanceInterval) {
insertLocation(track, lastLocation, lastValidTrackPoint);
insertLocation(track, location, null);
} else if (trackPoint.getSensorDataSet() != null || distanceToLastTrackLocation >= recordingDistanceInterval) {
insertTrackPoint(track, lastTrackPoint, lastValidTrackPoint);
insertTrackPoint(track, trackPoint, null);
isIdle = false;
} else if (!isIdle && location.hasSpeed() && location.getSpeed() < MAX_NO_MOVEMENT_SPEED) {
insertLocation(track, lastLocation, lastValidTrackPoint);
insertLocation(track, location, null);
insertTrackPoint(track, lastTrackPoint, lastValidTrackPoint);
insertTrackPoint(track, trackPoint, null);
isIdle = true;
} else if (isIdle && location.hasSpeed() && location.getSpeed() >= MAX_NO_MOVEMENT_SPEED) {
insertLocation(track, lastLocation, lastValidTrackPoint);
insertLocation(track, location, null);
insertTrackPoint(track, lastTrackPoint, lastValidTrackPoint);
insertTrackPoint(track, trackPoint, null);
isIdle = false;
} else {
Log.d(TAG, "Not recording location, idle");
}
lastLocation = location;
lastTrackPoint = trackPoint;
}
/**
* Inserts a location.
* Inserts a trackPoint.
*
* @param track the track
* @param location the location
* @param trackPoint the trackPoint
* @param lastValidTrackPoint the last valid track point, can be null
*/
private void insertLocation(Track track, Location location, Location lastValidTrackPoint) {
if (location == null) {
Log.w(TAG, "Ignore insertLocation. location is null.");
private void insertTrackPoint(Track track, TrackPoint trackPoint, Location lastValidTrackPoint) {
if (trackPoint == null) {
Log.w(TAG, "Ignore insertLocation. trackPoint is null.");
return;
}
// Do not insert if inserted already
if (lastValidTrackPoint != null && lastValidTrackPoint.getTime() == location.getTime()) {
Log.w(TAG, "Ignore insertLocation. location time same as last valid track point time.");
if (lastValidTrackPoint != null && lastValidTrackPoint.getTime() == trackPoint.getTime()) {
Log.w(TAG, "Ignore insertLocation. trackPoint time same as last valid track point time.");
return;
}
try {
contentProviderUtils.insertTrackPoint(location, track.getId());
trackTripStatisticsUpdater.addLocation(location, recordingDistanceInterval);
contentProviderUtils.insertTrackPoint(trackPoint, track.getId());
trackTripStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
updateRecordingTrack(track);
} catch (SQLiteException e) {
/*
@@ -1,9 +1,8 @@
package de.dennisguse.opentracks.services;
import android.location.Location;
import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
/**
@@ -83,8 +82,8 @@ class TrackRecordingServiceBinder extends android.os.Binder implements TrackReco
@VisibleForTesting
@Override
public void insertTrackPoint(Location location) {
trackRecordingService.onLocationChangedAsync(location);
public void insertTrackPoint(TrackPoint trackPoint) {
trackRecordingService.onLocationChangedAsync(trackPoint);
}
@Override
@@ -15,10 +15,9 @@
*/
package de.dennisguse.opentracks.services;
import android.location.Location;
import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
/**
@@ -101,10 +100,10 @@ public interface TrackRecordingServiceInterface {
* When recording a track, GPS locations are automatically inserted. This is used for
* inserting special track points or for testing.
*
* @param location the track point to be inserted
* @param trackPoint the track point to be inserted
*/
@VisibleForTesting
void insertTrackPoint(Location location);
void insertTrackPoint(TrackPoint trackPoint);
/**
* Gets the current sensor data. Returns null if there is no data.
@@ -21,6 +21,7 @@ import android.util.Log;
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;
@@ -122,24 +123,24 @@ public class TripStatisticsUpdater {
}
/**
* Adds a location.
* TODO: This assume location has a valid time.
* Adds a trackPoint.
* TODO: This assume trackPoint has a valid time.
*
* @param location the location
* @param trackPoint the trackPoint
* @param minRecordingDistance the min recording distance
*/
public void addLocation(Location location, int minRecordingDistance) {
public void addTrackPoint(TrackPoint trackPoint, int minRecordingDistance) {
// Always update time
updateTime(location.getTime());
if (!LocationUtils.isValidLocation(location)) {
updateTime(trackPoint.getTime());
if (!LocationUtils.isValidLocation(trackPoint)) {
// Either pause or resume marker
if (location.getLatitude() == TrackPointsColumns.PAUSE_LATITUDE) {
if (trackPoint.getLatitude() == TrackPointsColumns.PAUSE_LATITUDE) {
if (lastLocation != null && lastMovingLocation != null && lastLocation != lastMovingLocation) {
currentSegment.addTotalDistance(lastMovingLocation.distanceTo(lastLocation));
}
tripStatistics.merge(currentSegment);
}
currentSegment = init(location.getTime());
currentSegment = init(trackPoint.getTime());
lastLocation = null;
lastMovingLocation = null;
elevationBuffer.reset();
@@ -150,23 +151,23 @@ public class TripStatisticsUpdater {
}
//TODO Use Barometer to compute elevation gain.
double elevationDifference = location.hasAltitude() ? updateElevation(location.getAltitude()) : 0.0;
double elevationDifference = trackPoint.hasAltitude() ? updateElevation(trackPoint.getAltitude()) : 0.0;
if (lastLocation == null || lastMovingLocation == null) {
lastLocation = location;
lastMovingLocation = location;
lastLocation = trackPoint;
lastMovingLocation = trackPoint;
return;
}
double movingDistance = lastMovingLocation.distanceTo(location);
if (movingDistance < minRecordingDistance && (!location.hasSpeed() || location.getSpeed() < MAX_NO_MOVEMENT_SPEED)) {
double movingDistance = lastMovingLocation.distanceTo(trackPoint);
if (movingDistance < minRecordingDistance && (!trackPoint.hasSpeed() || trackPoint.getSpeed() < MAX_NO_MOVEMENT_SPEED)) {
speedBuffer.reset();
lastLocation = location;
lastLocation = trackPoint;
return;
}
long movingTime = location.getTime() - lastLocation.getTime();
long movingTime = trackPoint.getTime() - lastLocation.getTime();
if (movingTime < 0) {
lastLocation = location;
lastLocation = trackPoint;
return;
}
@@ -177,22 +178,22 @@ public class TripStatisticsUpdater {
currentSegment.addMovingTime(movingTime);
// Update grade
double run = lastLocation.distanceTo(location);
double run = lastLocation.distanceTo(trackPoint);
updateGrade(run, elevationDifference);
// Update max speed
if (location.hasSpeed() && lastLocation.hasSpeed()) {
updateSpeed(location.getTime(), location.getSpeed(), lastLocation.getTime(), lastLocation.getSpeed());
if (trackPoint.hasSpeed() && lastLocation.hasSpeed()) {
updateSpeed(trackPoint.getTime(), trackPoint.getSpeed(), lastLocation.getTime(), lastLocation.getSpeed());
}
lastLocation = location;
lastMovingLocation = location;
lastLocation = trackPoint;
lastMovingLocation = trackPoint;
}
public void addLocation(TrackPointIterator iterator, int minRecordingDistance) {
public void addTrackPoint(TrackPointIterator iterator, int minRecordingDistance) {
while (iterator.hasNext()) {
Location location = iterator.next();
addLocation(location, minRecordingDistance);
TrackPoint location = iterator.next();
addTrackPoint(location, minRecordingDistance);
}
}
@@ -23,6 +23,7 @@ import java.util.List;
import java.util.Stack;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
/**
* Utility class for decimating tracks at a given level of precision.
@@ -84,16 +85,16 @@ public class LocationUtils {
}
/**
* Decimates the given locations for a given zoom level.
* Decimates the given trackPoints for a given zoom level.
* This uses a Douglas-Peucker decimation algorithm.
*
* @param tolerance in meters
* @param locations input
* @param trackPoints input
* @param decimated output
*/
//TODO What was it used for? Sharing data with other apps?
private static void decimate(double tolerance, List<Location> locations, List<Location> decimated) {
final int n = locations.size();
private static void decimate(double tolerance, List<TrackPoint> trackPoints, List<TrackPoint> decimated) {
final int n = trackPoints.size();
if (n < 1) {
return;
}
@@ -115,7 +116,7 @@ public class LocationUtils {
maxDist = 0;
for (idx = current[0] + 1; idx < current[1]; ++idx) {
dist = LocationUtils.distance(
locations.get(idx), locations.get(current[0]), locations.get(current[1]));
trackPoints.get(idx), trackPoints.get(current[0]), trackPoints.get(current[1]));
if (dist > maxDist) {
maxDist = dist;
maxIdx = idx;
@@ -134,7 +135,7 @@ public class LocationUtils {
int i = 0;
idx = 0;
decimated.clear();
for (Location l : locations) {
for (TrackPoint l : trackPoints) {
if (dists[idx] != 0) {
decimated.add(l);
i++;
@@ -151,9 +152,9 @@ public class LocationUtils {
* @param precision desired precision in meters
*/
public static void decimate(Track track, double precision) {
ArrayList<Location> decimated = new ArrayList<>();
decimate(precision, track.getLocations(), decimated);
track.setLocations(decimated);
ArrayList<TrackPoint> decimated = new ArrayList<>();
decimate(precision, track.getTrackPoints(), decimated);
track.setTrackPoints(decimated);
}
/**