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

KML exports sensor data if actually present.

#316.
This commit is contained in:
Dennis Guse
2020-12-28 21:28:39 +01:00
parent 47b2d0748a
commit ab47320216
19 changed files with 506 additions and 284 deletions
@@ -44,6 +44,8 @@ public final class Marker {
//TODO It is the distance from the track starting point; rename to something more meaningful
private double length = 0.0;
private long duration = 0;
@Deprecated //TODO Replace by storing latitude/longitude directly.
private final Location location;
@Deprecated //TODO Make an URI instead of String
private String photoUrl = "";
@@ -16,7 +16,6 @@
package de.dennisguse.opentracks.content.data;
import android.location.Location;
import android.location.LocationManager;
import android.os.Parcel;
import androidx.annotation.NonNull;
@@ -40,21 +39,25 @@ import java.util.Objects;
*
* @author Sandor Dornbush
*/
//TODO Merge constructors by use case; we have too many.
public class TrackPoint {
private TrackPoint.Id id;
private final Location location;
private long time_ms;
private Double latitude;
private Double longitude;
private Float accuracy;
private Double altitude_m;
private Float speed_mps;
private Float bearing;
//TODO Private
public enum Type {
SEGMENT_START_MANUAL(-2), //Start of a segment due to user interaction (start, resume); no useful coordinates
SEGMENT_START_MANUAL(-2), //Start of a segment due to user interaction (start, resume)
SEGMENT_START_AUTOMATIC(-1), //Start of a segment due to too much distance from previous TrackPoint
TRACKPOINT(0), //Normal trackpoint got from GPS
TRACKPOINT(0), //Just GPS data.
SEGMENT_END_MANUAL(1); //End of a segment; no useful coordinates
SEGMENT_END_MANUAL(1); //End of a segment
public final int type_db;
@@ -64,7 +67,7 @@ public class TrackPoint {
@Override
public String toString() {
return "" + type_db;
return name() + "(" + type_db + ")";
}
public static Type getById(int id) {
@@ -74,10 +77,6 @@ public class TrackPoint {
throw new RuntimeException("unknown id: " + id);
}
public boolean hasLocation() {
return this == SEGMENT_START_AUTOMATIC || this == TRACKPOINT;
}
}
@NonNull
@@ -89,35 +88,33 @@ public class TrackPoint {
private Float elevationGain = null;
private Float elevationLoss = null;
public TrackPoint() {
this(Type.TRACKPOINT, new Location(""));
}
public TrackPoint(@NonNull Type type) {
this.type = type;
this.location = new Location("");
}
public TrackPoint(@NonNull Location location) {
this.type = Type.TRACKPOINT;
this.location = location;
this(Type.TRACKPOINT);
this.latitude = location.getLatitude();
this.longitude = location.getLongitude();
this.altitude_m = location.getAltitude();
this.speed_mps = location.getSpeed();
this.accuracy = location.getAccuracy();
setTime(System.currentTimeMillis());
}
public TrackPoint(@NonNull Type type, @NonNull Location location) {
this.type = type;
this.location = location;
public TrackPoint(@NonNull Type type, long time_ms) {
this(type);
this.time_ms = time_ms;
}
public TrackPoint(double latitude, double longitude, Double altitude, long time) {
this.type = Type.TRACKPOINT;
location = new Location(LocationManager.GPS_PROVIDER);
location.setLatitude(latitude);
location.setLongitude(longitude);
if (altitude != null) {
location.setAltitude(altitude);
}
location.setTime(time);
this(Type.TRACKPOINT);
this.latitude = latitude;
this.longitude = longitude;
this.altitude_m = altitude;
this.time_ms = time;
}
@Deprecated //See #316
@@ -126,9 +123,7 @@ public class TrackPoint {
}
public static TrackPoint createSegmentStartManualWithTime(long time) {
Location resume = new Location(LocationManager.GPS_PROVIDER);
resume.setTime(time);
return new TrackPoint(Type.SEGMENT_START_MANUAL, resume);
return new TrackPoint(Type.SEGMENT_START_MANUAL, time);
}
@Deprecated //See #316
@@ -137,9 +132,7 @@ public class TrackPoint {
}
public static TrackPoint createSegmentStartAutomaticWithTime(long time) {
Location resume = new Location(LocationManager.GPS_PROVIDER);
resume.setTime(time);
return new TrackPoint(Type.SEGMENT_START_AUTOMATIC, resume);
return new TrackPoint(Type.SEGMENT_START_AUTOMATIC, time);
}
public static TrackPoint createSegmentEnd() {
@@ -151,9 +144,7 @@ public class TrackPoint {
}
public static TrackPoint createSegmentEndWithTime(long time) {
Location pause = new Location(LocationManager.GPS_PROVIDER);
pause.setTime(time);
return new TrackPoint(Type.SEGMENT_END_MANUAL, pause);
return new TrackPoint(Type.SEGMENT_END_MANUAL, time);
}
@NonNull
@@ -185,8 +176,28 @@ public class TrackPoint {
this.id = id;
}
public boolean hasLocation() {
return latitude != null || longitude != null;
}
@Nullable
public Location getLocation() {
Location location = new Location("");
location.setTime(time_ms);
if (hasLocation()) {
location.setLatitude(latitude);
location.setLongitude(longitude);
}
if (hasBearing()) {
location.setBearing(bearing);
}
if (hasAccuracy()) {
location.setAccuracy(accuracy);
}
if (hasAltitude()) {
location.setAltitude(altitude_m);
}
return location;
}
@@ -194,7 +205,7 @@ public class TrackPoint {
return elevationGain != null;
}
public float getElevationGain() {
public Float getElevationGain() {
return elevationGain;
}
@@ -215,87 +226,87 @@ public class TrackPoint {
}
public double getLatitude() {
return location.getLatitude();
return latitude;
}
public void setLatitude(double latitude) {
location.setLatitude(latitude);
this.latitude = latitude;
}
public double getLongitude() {
return location.getLongitude();
return longitude;
}
public void setLongitude(double longitude) {
location.setLongitude(longitude);
this.longitude = longitude;
}
public long getTime() {
return location.getTime();
return time_ms;
}
public void setTime(long time) {
location.setTime(time);
this.time_ms = time;
}
public boolean hasAltitude() {
return location.hasAltitude();
return altitude_m != null;
}
public double getAltitude() {
return location.getAltitude();
return altitude_m;
}
public void setAltitude(double altitude) {
location.setAltitude(altitude);
this.altitude_m = altitude;
}
public boolean hasSpeed() {
return location.hasSpeed();
return speed_mps != null;
}
public float getSpeed() {
return location.getSpeed();
return speed_mps;
}
public void setSpeed(float speed) {
location.setSpeed(speed);
public void setSpeed(Float speed) {
this.speed_mps = speed;
}
public boolean hasBearing() {
return location.hasBearing();
return bearing != null;
}
public float getBearing() {
return location.getBearing();
return bearing;
}
public void setBearing(float bearing) {
location.setBearing(bearing);
public void setBearing(Float bearing) {
this.bearing = bearing;
}
public boolean hasAccuracy() {
return location.hasAccuracy();
return accuracy != null;
}
public float getAccuracy() {
return location.getAccuracy();
return accuracy;
}
public void setAccuracy(float horizontalAccuracy) {
location.setAccuracy(horizontalAccuracy);
this.accuracy = horizontalAccuracy;
}
public float distanceTo(@NonNull TrackPoint dest) {
return location.distanceTo(dest.getLocation());
return getLocation().distanceTo(dest.getLocation());
}
public float bearingTo(@NonNull TrackPoint dest) {
return location.bearingTo(dest.getLocation());
return getLocation().bearingTo(dest.getLocation());
}
public float bearingTo(@NonNull Location dest) {
return location.bearingTo(dest);
return getLocation().bearingTo(dest);
}
// Sensor data
@@ -342,7 +353,16 @@ public class TrackPoint {
@NonNull
@Override
public String toString() {
return "time=" + getTime() + " (type=" + getType() + "): lat=" + getLatitude() + " lng=" + getLongitude() + " acc=" + getAccuracy();
String result = "time=" + getTime() + " (type=" + getType() + ")";
if (!hasLocation()) {
return result;
}
result += ": lat=" + getLatitude() + " lng=" + getLongitude();
if (!hasAccuracy()) {
return result;
}
return result + " acc=" + getAccuracy();
}
public static class Id {
@@ -543,9 +543,7 @@ public class ContentProviderUtils {
*/
static TrackPoint fillTrackPoint(Cursor cursor, CachedTrackPointsIndexes indexes) {
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.getById(cursor.getInt(indexes.typeIndex)));
if (!cursor.isNull(indexes.longitudeIndex)) {
trackPoint.setId(new TrackPoint.Id(cursor.getInt(indexes.idIndex)));
}
trackPoint.setId(new TrackPoint.Id(cursor.getInt(indexes.idIndex)));
if (!cursor.isNull(indexes.longitudeIndex)) {
trackPoint.setLongitude(((double) cursor.getInt(indexes.longitudeIndex)) / 1E6);
@@ -694,7 +692,7 @@ public class ContentProviderUtils {
*/
@Deprecated
public TrackPoint getLastValidTrackPoint(Track.Id trackId) {
String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns.TYPE + " IN (" + TrackPoint.Type.SEGMENT_START_AUTOMATIC + "," + TrackPoint.Type.TRACKPOINT + "))";
String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns.TYPE + " IN (" + TrackPoint.Type.SEGMENT_START_AUTOMATIC.type_db + "," + TrackPoint.Type.TRACKPOINT.type_db + "))";
String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
return findTrackPointBy(selection, selectionArgs);
}
@@ -719,12 +717,12 @@ public class ContentProviderUtils {
private ContentValues createContentValues(TrackPoint trackPoint, Track.Id trackId) {
ContentValues values = new ContentValues();
values.put(TrackPointsColumns.TRACKID, trackId.getId());
if (trackPoint.getType() != null) {
values.put(TrackPointsColumns.TYPE, trackPoint.getType().type_db);
}
values.put(TrackPointsColumns.LONGITUDE, (int) (trackPoint.getLongitude() * 1E6));
values.put(TrackPointsColumns.LATITUDE, (int) (trackPoint.getLatitude() * 1E6));
values.put(TrackPointsColumns.TYPE, trackPoint.getType().type_db);
if (trackPoint.hasLocation()) {
values.put(TrackPointsColumns.LONGITUDE, (int) (trackPoint.getLongitude() * 1E6));
values.put(TrackPointsColumns.LATITUDE, (int) (trackPoint.getLatitude() * 1E6));
}
values.put(TrackPointsColumns.TIME, trackPoint.getTime());
if (trackPoint.hasAltitude()) {
values.put(TrackPointsColumns.ALTITUDE, trackPoint.getAltitude());
@@ -12,6 +12,7 @@ import de.dennisguse.opentracks.content.data.TrackPoint;
/**
* A lightweight wrapper around the original {@link Cursor} with a method to clean up.
*/
//TODO Remove batching; that should be handled by the database/contentprovider (i.e., already in place as we use a cursor)!
public class TrackPointIterator implements Iterator<TrackPoint>, AutoCloseable {
private static final String TAG = TrackPointIterator.class.getSimpleName();
@@ -22,7 +23,6 @@ public class TrackPointIterator implements Iterator<TrackPoint>, AutoCloseable {
private TrackPoint.Id lastTrackPointId = null;
private Cursor cursor;
public TrackPointIterator(ContentProviderUtils contentProviderUtils, Track.Id trackId, TrackPoint.Id startTrackPointId) {
this.contentProviderUtils = contentProviderUtils;
this.trackId = trackId;
@@ -191,7 +191,7 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
}
TrackPoint trackPoint = lastTrackPoint; //NOTE: There seems to be a race condition; just fix the symptom for now.
if (trackPoint != null && trackPoint.getType().hasLocation()) {
if (trackPoint != null && trackPoint.hasLocation()) {
boolean hasFix = !LocationUtils.isTrackPointOld(trackPoint);
if (!hasFix) {
@@ -45,8 +45,8 @@ public class FileTrackExporter implements TrackExporter {
/**
* Constructor.
*
* @param contentProviderUtils the content provider utils
* @param trackWriter the track writer
* @param contentProviderUtils the content provider utils
* @param trackWriter the track writer
*/
public FileTrackExporter(ContentProviderUtils contentProviderUtils, TrackWriter trackWriter) {
this.contentProviderUtils = contentProviderUtils;
@@ -70,6 +70,7 @@ public class FileTrackExporter implements TrackExporter {
if (hasMultipleTracks) {
trackWriter.writeMultiTrackBegin();
}
//TODO Why use startTime of first track for the others?
long startTime = tracks[0].getTrackStatistics().getStartTime_ms();
for (Track track : tracks) {
long offset = track.getTrackStatistics().getStartTime_ms() - startTime;
@@ -122,50 +123,40 @@ public class FileTrackExporter implements TrackExporter {
private void writeLocations(Track track, long offset) throws InterruptedException {
boolean wroteTrack = false;
boolean wroteSegment = false;
boolean isLastLocationValid = false;
TrackPoint lastTrackPoint = null;
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), null)) {
while (trackPointIterator.hasNext()) {
if (Thread.interrupted()) {
throw new InterruptedException();
}
TrackPoint trackPoint = trackPointIterator.next();
if (Thread.interrupted()) throw new InterruptedException();
TrackPoint trackPoint = trackPointIterator.next();
setLocationTime(trackPoint, offset);
boolean isLocationValid = trackPoint.getType().hasLocation();
boolean isSegmentValid = isLocationValid && isLastLocationValid;
if (!wroteTrack && isSegmentValid) {
// Found the first two consecutive locations that are valid
trackWriter.writeBeginTrack(track, lastTrackPoint);
if (!wroteTrack) {
trackWriter.writeBeginTrack(track, trackPoint);
wroteTrack = true;
}
if (isSegmentValid) {
if (!wroteSegment) {
// Start a segment
trackWriter.writeOpenSegment();
wroteSegment = true;
// Write the previous trackPoint, which we had previously skipped
trackWriter.writeTrackPoint(lastTrackPoint);
}
// Write the current trackPoint
boolean newSegment = TrackPoint.Type.SEGMENT_START_AUTOMATIC.equals(trackPoint.getType()) || TrackPoint.Type.SEGMENT_START_MANUAL.equals(trackPoint.getType());
if (newSegment) {
if (wroteSegment) trackWriter.writeCloseSegment();
trackWriter.writeOpenSegment();
trackWriter.writeTrackPoint(trackPoint);
} else {
if (wroteSegment) {
trackWriter.writeCloseSegment();
wroteSegment = false;
}
wroteSegment = true;
continue;
}
lastTrackPoint = trackPoint;
isLastLocationValid = isLocationValid;
if (TrackPoint.Type.SEGMENT_END_MANUAL.equals(trackPoint.getType())) {
if (!wroteSegment) trackWriter.writeOpenSegment();
trackWriter.writeTrackPoint(trackPoint);
trackWriter.writeCloseSegment();
wroteSegment = false;
continue;
}
trackWriter.writeTrackPoint(trackPoint);
}
if (wroteSegment) {
// Should not be necessary as tracks should end with SEGMENT_END_MANUAL.
//Close the last segment
trackWriter.writeCloseSegment();
}
@@ -188,6 +179,7 @@ public class FileTrackExporter implements TrackExporter {
* @param trackPoint the trackPoint
* @param offset the time offset
*/
//TODO Why?
private void setLocationTime(TrackPoint trackPoint, long offset) {
if (trackPoint != null) {
trackPoint.setTime(trackPoint.getTime() - offset);
@@ -190,7 +190,9 @@ public class GpxTrackWriter implements TrackWriter {
@Override
public void writeTrackPoint(TrackPoint trackPoint) {
if (printWriter != null) {
printWriter.println("<trkpt " + formatLocation(trackPoint.getLocation()) + ">");
String coordinates = trackPoint.hasLocation() ? " " + formatLocation(trackPoint.getLocation()) : "";
printWriter.println("<trkpt " + coordinates + ">");
if (trackPoint.hasAltitude()) {
printWriter.println("<ele>" + ELEVATION_FORMAT.format(trackPoint.getAltitude()) + "</ele>");
}
@@ -22,6 +22,7 @@ import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.DescriptionGenerator;
@@ -239,6 +240,8 @@ public class KmlTrackWriter implements TrackWriter {
powerList.clear();
cadenceList.clear();
heartRateList.clear();
elevationGainList.clear();
elevationLossList.clear();
}
}
@@ -247,23 +250,23 @@ public class KmlTrackWriter implements TrackWriter {
if (printWriter != null) {
printWriter.println("<ExtendedData>");
printWriter.println("<SchemaData schemaUrl=\"#" + SCHEMA_ID + "\">");
if (speedList.size() > 0) {
if (speedList.stream().anyMatch(Objects::nonNull)) {
writeSimpleArrayData(speedList, EXTENDED_DATA_TYPE_SPEED);
}
if (exportSensorData) {
if (powerList.size() > 0) {
if (powerList.stream().anyMatch(Objects::nonNull)) {
writeSimpleArrayData(powerList, EXTENDED_DATA_TYPE_POWER);
}
if (cadenceList.size() > 0) {
if (cadenceList.stream().anyMatch(Objects::nonNull)) {
writeSimpleArrayData(cadenceList, EXTENDED_DATA_TYPE_CADENCE);
}
if (heartRateList.size() > 0) {
if (heartRateList.stream().anyMatch(Objects::nonNull)) {
writeSimpleArrayData(heartRateList, EXTENDED_DATA_TYPE_HEART_RATE);
}
if (elevationGainList.size() > 0) {
if (elevationGainList.stream().anyMatch(Objects::nonNull)) {
writeSimpleArrayData(elevationGainList, EXTENDED_DATA_TYPE_ELEVATION_GAIN);
}
if (elevationLossList.size() > 0) {
if (elevationLossList.stream().anyMatch(Objects::nonNull)) {
writeSimpleArrayData(elevationLossList, EXTENDED_DATA_TYPE_ELEVATION_LOSS);
}
}
@@ -280,28 +283,20 @@ public class KmlTrackWriter implements TrackWriter {
printWriter.println("<when>" + getTime(trackPoint.getLocation()) + "</when>");
}
printWriter.println("<gx:coord>" + getCoordinates(trackPoint.getLocation(), " ") + "</gx:coord>");
if (trackPoint.hasSpeed()) {
speedList.add(trackPoint.getSpeed());
if (trackPoint.hasLocation()) {
printWriter.println("<gx:coord>" + (trackPoint.hasLocation() ? getCoordinates(trackPoint.getLocation(), " ") : "") + "</gx:coord>");
} else {
printWriter.println("<gx:coord/>");
}
speedList.add(trackPoint.hasSpeed() ? trackPoint.getSpeed() : null);
if (exportSensorData) {
if (trackPoint.hasHeartRate()) {
heartRateList.add(trackPoint.getHeartRate_bpm());
}
if (trackPoint.hasCyclingCadence()) {
cadenceList.add(trackPoint.getCyclingCadence_rpm());
}
if (trackPoint.hasPower()) {
powerList.add(trackPoint.getPower());
}
if (trackPoint.hasElevationGain()) {
elevationGainList.add(trackPoint.getElevationGain());
}
if (trackPoint.hasElevationLoss()) {
elevationLossList.add(trackPoint.getElevationLoss());
}
heartRateList.add(trackPoint.hasHeartRate() ? trackPoint.getHeartRate_bpm() : null);
cadenceList.add(trackPoint.hasCyclingCadence() ? trackPoint.getCyclingCadence_rpm() : null);
powerList.add(trackPoint.hasPower() ? trackPoint.getPower() : null);
elevationGainList.add(trackPoint.hasElevationGain() ? trackPoint.getElevationGain() : null);
elevationLossList.add(trackPoint.hasElevationLoss() ? trackPoint.getElevationLoss() : null);
}
}
}
@@ -315,7 +310,12 @@ public class KmlTrackWriter implements TrackWriter {
private void writeSimpleArrayData(List<Float> list, String name) {
printWriter.println("<gx:SimpleArrayData name=\"" + name + "\">");
for (int i = 0; i < list.size(); i++) {
printWriter.println("<gx:value>" + list.get(i) + "</gx:value>");
Float value = list.get(i);
if (value == null) {
printWriter.println("<gx:value />");
} else {
printWriter.println("<gx:value>" + list.get(i) + "</gx:value>");
}
}
printWriter.println("</gx:SimpleArrayData>");
}
@@ -158,6 +158,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
TrackPoint trackPoint = null;
TrackStatisticsUpdater trackStatisticsUpdater = new TrackStatisticsUpdater();
// TODO We are doing in memory processing for trackpoints; so we can do this in memory as well.
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), null)) {
while (true) {
@@ -190,7 +191,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
marker = null;
} else {
// The marker trackPoint time matches the track point time
if (!trackPoint.getType().hasLocation()) {
if (!trackPoint.hasLocation()) {
// Invalid trackPoint, load the next trackPoint
trackPoint = null;
continue;
@@ -274,31 +275,43 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
trackIds.add(trackId);
trackData.track.setId(trackId);
flushLocations(trackData);
flushTrackPoints();
}
/**
* On track segment start.
*/
protected void onTrackSegmentStart() {
trackData.numberOfSegments++;
//If not the first segment, add a pause separator if there is at least one location in the last segment.
if (trackData.numberOfSegments > 1 && trackData.lastLocationInCurrentSegment != null) {
insertLocation(TrackPoint.createSegmentEndWithTime(trackData.lastLocationInCurrentSegment.getTime()));
//If not the first segment, add a pause separator if there is at least one TrackPoint in the last segment.
if (trackData.numberOfSegments > 1
&& trackData.lastLocationInCurrentSegment != null
&& (trackData.lastLocationInCurrentSegment.getType().equals(TrackPoint.Type.SEGMENT_START_MANUAL) || trackData.lastLocationInCurrentSegment.getType().equals(TrackPoint.Type.SEGMENT_START_AUTOMATIC))
) {
insertTrackPoint(TrackPoint.createSegmentEndWithTime(trackData.lastLocationInCurrentSegment.getTime()));
}
trackData.lastLocationInCurrentSegment = null;
}
protected void onTrackSegmentEnd() {
TrackPoint trackPoint = trackData.lastLocationInCurrentSegment;
if (trackPoint == null) {
return;
}
if (!trackPoint.hasLocation()) {
trackPoint.setType(TrackPoint.Type.SEGMENT_END_MANUAL);
}
}
protected void addMarker() throws SAXException {
// Markers must have a time, else cannot match to the track points
if (time == null) {
Log.w(TAG, "Marker without time ignored.");
return;
}
TrackPoint trackPoint = createTrackPoint();
if (!trackPoint.getType().hasLocation()) {
if (!trackPoint.hasLocation()) {
Log.w(TAG, "Marker with invalid coordinates ignored: " + trackPoint.getLocation());
return;
}
@@ -320,14 +333,8 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
markers.add(marker);
}
/**
* Gets a track point.
*/
protected TrackPoint getTrackPoint() throws SAXException {
TrackPoint trackPoint = createTrackPoint();
if (trackPoint == null) {
throw new SAXException(createErrorMessage("Invalid location detected: " + trackPoint));
}
// Calculate derived attributes from the previous point
if (trackData.lastLocationInCurrentSegment != null && trackData.lastLocationInCurrentSegment.getTime() != 0) {
@@ -344,34 +351,31 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
* GPS points tend to have some inherent imprecision, speed and bearing will likely be off, so the statistics for things like max speed will also be off.
*/
double duration = timeDifference * UnitConversions.MS_TO_S;
double speed = trackData.lastLocationInCurrentSegment.distanceTo(trackPoint) / duration;
trackPoint.setSpeed((float) speed);
if (trackPoint.hasLocation() && trackData.lastLocationInCurrentSegment.hasLocation()) {
double speed = trackData.lastLocationInCurrentSegment.distanceTo(trackPoint) / duration;
trackPoint.setSpeed((float) speed);
}
}
}
if (trackPoint.hasLocation() && trackData.lastLocationInCurrentSegment.hasLocation()) {
trackPoint.setBearing(trackData.lastLocationInCurrentSegment.bearingTo(trackPoint));
long maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(context);
double distanceToLastTrackLocation = trackPoint.distanceTo(trackData.lastLocationInCurrentSegment);
if (distanceToLastTrackLocation > maxRecordingDistance) {
trackPoint.setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
}
}
trackPoint.setBearing(trackData.lastLocationInCurrentSegment.bearingTo(trackPoint));
}
if (!LocationUtils.isValidLocation(trackPoint.getLocation())) {
if (trackPoint.hasLocation() && !LocationUtils.isValidLocation(trackPoint.getLocation())) {
throw new SAXException(createErrorMessage("Invalid location detected: " + trackPoint));
}
if (trackData.numberOfSegments > 1 && trackData.lastLocationInCurrentSegment == null) {
// If not the first segment, add a resume separator before adding the first location.
insertLocation(TrackPoint.createSegmentStartManualWithTime(trackPoint.getTime()));
}
trackData.lastLocationInCurrentSegment = trackPoint;
return trackPoint;
}
/**
* Inserts a track point.
*
* @param trackPoint the trackPoint
*/
protected void insertTrackPoint(TrackPoint trackPoint) {
insertLocation(trackPoint);
}
/**
* Creates an error message.
*
@@ -411,11 +415,17 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
* Creates a location.
*/
private TrackPoint createTrackPoint() throws SAXException {
if (latitude == null || longitude == null) {
return null;
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT);
try {
trackPoint.setTime(StringUtils.parseTime(time));
} catch (Exception e) {
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse time: %s", time)), e);
}
TrackPoint trackPoint = new TrackPoint();
if (latitude == null || longitude == null) {
return trackPoint;
}
try {
trackPoint.setLatitude(Double.parseDouble(latitude));
@@ -424,11 +434,6 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse latitude longitude: %s %s", latitude, longitude)), e);
}
try {
trackPoint.setTime(StringUtils.parseTime(time));
} catch (Exception e) {
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse time: %s", time)), e);
}
if (altitude != null) {
try {
@@ -484,7 +489,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
*
* @param trackPoint the trackPoint
*/
private void insertLocation(TrackPoint trackPoint) {
protected void insertTrackPoint(TrackPoint trackPoint) {
if (trackData.trackStatisticsUpdater == null) {
trackData.trackStatisticsUpdater = new TrackStatisticsUpdater();
}
@@ -492,21 +497,20 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
trackData.trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
trackData.bufferedTrackPoints.add(trackPoint);
trackData.numBufferedTrackPoints++;
trackData.numberOfLocations++;
}
protected boolean isFirstTrackPointInSegment() {
return trackData.lastLocationInCurrentSegment == null;
}
/**
* Flushes the locations to the database.
*
* @param data the track data
* Flushes the TrackPoints to the database.
*/
private void flushLocations(TrackData data) {
if (data.numBufferedTrackPoints <= 0) {
return;
private void flushTrackPoints() {
if (trackData.bufferedTrackPoints.size() > 0) {
contentProviderUtils.bulkInsertTrackPoint(trackData.bufferedTrackPoints, trackData.track.getId());
trackData.bufferedTrackPoints.clear();
}
contentProviderUtils.bulkInsertTrackPoint(data.bufferedTrackPoints, data.track.getId());
data.numBufferedTrackPoints = 0;
}
/**
@@ -532,16 +536,10 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
// The last location in the current segment; Null if the current segment doesn't have a last location
TrackPoint lastLocationInCurrentSegment;
// The number of locations processed for the current track
int numberOfLocations = 0;
// The TrackStatisticsUpdater for the current track
TrackStatisticsUpdater trackStatisticsUpdater;
// The buffered locations
final List<TrackPoint> bufferedTrackPoints = new ArrayList<>();
// The number of buffered locations
int numBufferedTrackPoints = 0;
}
}
@@ -101,6 +101,9 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
case TAG_TRACK:
onTrackEnd();
break;
case TAG_TRACK_SEGMENT:
onTrackSegmentEnd();
break;
case TAG_TRACK_POINT:
onTrackPointEnd();
break;
@@ -193,13 +196,13 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
loss = null;
}
/**
* On track point end.
*/
private void onTrackPointEnd() throws SAXException {
boolean isFirstTrackPointInSegment = isFirstTrackPointInSegment();
TrackPoint trackPoint = getTrackPoint();
if (trackPoint == null) {
return;
if (isFirstTrackPointInSegment) {
TrackPoint.Type type = !trackPoint.hasLocation() ? TrackPoint.Type.SEGMENT_START_MANUAL : TrackPoint.Type.SEGMENT_START_AUTOMATIC;
trackPoint.setType(type);
}
insertTrackPoint(trackPoint);
}
@@ -159,6 +159,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
if (content != null) {
time = content.trim();
}
break;
case TAG_STYLE_URL:
if (content != null) {
@@ -221,10 +222,8 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
elevationLossList.clear();
}
/**
* On track segment end.
*/
private void onTrackSegmentEnd() {
protected void onTrackSegmentEnd() {
super.onTrackSegmentEnd();
// Close a track segment by inserting the segment locations
for (int i = 0; i < trackPoints.size(); i++) {
TrackPoint trackPoint = trackPoints.get(i);
@@ -256,24 +255,32 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
* On track point end. gx:coord end tag.
*/
private void onTrackPointEnd() throws SAXException {
// Add location to locationList
// Add trackPoint to trackPoints
if (content == null) {
return;
}
String[] parts = content.trim().split(" ");
if (parts.length != 2 && parts.length != 3) {
return;
if (parts.length == 2 || parts.length == 3) {
longitude = parts[0];
latitude = parts[1];
altitude = parts.length == 3 ? parts[2] : null;
}
longitude = parts[0];
latitude = parts[1];
altitude = parts.length == 3 ? parts[2] : null;
TrackPoint location = getTrackPoint();
if (location == null) {
return;
// Similar to GPX
boolean isFirstTrackPointInSegment = isFirstTrackPointInSegment();
TrackPoint trackPoint = getTrackPoint();
if (isFirstTrackPointInSegment) {
TrackPoint.Type type = !trackPoint.hasLocation() ? TrackPoint.Type.SEGMENT_START_MANUAL : TrackPoint.Type.SEGMENT_START_AUTOMATIC;
trackPoint.setType(type);
}
trackPoints.add(location);
trackPoints.add(trackPoint);
// Reset variables for next trackpoint (which might not have such data).
time = null;
longitude = null;
latitude = null;
altitude = null;
}
/**
@@ -287,18 +294,16 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
* On extended data value end. gx:value end tag.
*/
private void onExtendedDataValueEnd() throws SAXException {
if (content == null) {
return;
}
content = content.trim();
if (content.equals("")) {
return;
}
float value;
try {
value = Float.parseFloat(content);
} catch (NumberFormatException e) {
throw new SAXException(createErrorMessage("Unable to parse gx:value:" + content), e);
Float value = null;
if (content != null) {
content = content.trim();
if (!content.equals("")) {
try {
value = Float.parseFloat(content);
} catch (NumberFormatException e) {
throw new SAXException(createErrorMessage("Unable to parse gx:value:" + content), e);
}
}
}
switch (extendedDataType) {
case KmlTrackWriter.EXTENDED_DATA_TYPE_SPEED:
@@ -31,7 +31,7 @@ public class IntervalStatistics {
TrackPoint prevTrackPoint = trackPoints.get(i - 1);
TrackPoint trackPoint = trackPoints.get(i);
if (trackPoint.getType().hasLocation() && prevTrackPoint.getType().hasLocation()) {
if (trackPoint.hasLocation() && prevTrackPoint.hasLocation()) {
interval.distance_m += prevTrackPoint.distanceTo(trackPoint);
interval.time_ms += trackPoint.getTime() - prevTrackPoint.getTime();
interval.gain_m += trackPoint.hasElevationGain() ? trackPoint.getElevationGain() : 0;