mirror of
https://codeberg.org/OpenTracksApp/OpenTracks.git
synced 2026-10-02 09:33:06 +02:00
+10
-10
@@ -406,18 +406,18 @@ public class ExportImportTest {
|
||||
// TODO Not exported for GPX/KML
|
||||
// assertEquals(trackPoint.getAccuracy(), importedTrackPoint.getAccuracy(), 0.01);
|
||||
|
||||
assertEquals(trackPoint.hasLocation(), importedTrackPoint.hasLocation());
|
||||
assertEquals("" + i, trackPoint.hasLocation(), importedTrackPoint.hasLocation());
|
||||
if (trackPoint.hasLocation()) {
|
||||
assertEquals(trackPoint.getLatitude(), importedTrackPoint.getLatitude(), 0.001);
|
||||
assertEquals(trackPoint.getLongitude(), importedTrackPoint.getLongitude(), 0.001);
|
||||
assertEquals("" + i, trackPoint.getLatitude(), importedTrackPoint.getLatitude(), 0.001);
|
||||
assertEquals("" + i, trackPoint.getLongitude(), importedTrackPoint.getLongitude(), 0.001);
|
||||
}
|
||||
assertEquals(trackPoint.hasSpeed(), importedTrackPoint.hasSpeed());
|
||||
assertEquals("" + i, trackPoint.hasSpeed(), importedTrackPoint.hasSpeed());
|
||||
if (trackPoint.hasSpeed()) {
|
||||
assertEquals(trackPoint.getSpeed().toMPS(), importedTrackPoint.getSpeed().toMPS(), 0.001);
|
||||
assertEquals("" + i, trackPoint.getSpeed().toMPS(), importedTrackPoint.getSpeed().toMPS(), 0.001);
|
||||
}
|
||||
assertEquals(trackPoint.hasAltitude(), importedTrackPoint.hasAltitude());
|
||||
assertEquals("" + i, trackPoint.hasAltitude(), importedTrackPoint.hasAltitude());
|
||||
if (trackPoint.hasAltitude()) {
|
||||
assertEquals(trackPoint.getAltitude().toM(), importedTrackPoint.getAltitude().toM(), 0.001);
|
||||
assertEquals("" + i, trackPoint.getAltitude().toM(), importedTrackPoint.getAltitude().toM(), 0.001);
|
||||
}
|
||||
|
||||
if (type.equals(TrackPoint.Type.SEGMENT_START_MANUAL) || type.equals(TrackPoint.Type.SEGMENT_END_MANUAL)) {
|
||||
@@ -426,13 +426,13 @@ public class ExportImportTest {
|
||||
}
|
||||
|
||||
if (verifyHeartrate) {
|
||||
assertEquals("" + trackPoint, trackPoint.getHeartRate_bpm(), importedTrackPoint.getHeartRate_bpm(), 0.01);
|
||||
assertEquals("" + i, trackPoint.getHeartRate_bpm(), importedTrackPoint.getHeartRate_bpm(), 0.01);
|
||||
}
|
||||
if (verifyCadence) {
|
||||
assertEquals("" + trackPoint, trackPoint.getCyclingCadence_rpm(), importedTrackPoint.getCyclingCadence_rpm(), 0.01);
|
||||
assertEquals("" + i, trackPoint.getCyclingCadence_rpm(), importedTrackPoint.getCyclingCadence_rpm(), 0.01);
|
||||
}
|
||||
if (verifyPower) {
|
||||
assertEquals("" + trackPoint, trackPoint.getPower(), importedTrackPoint.getPower(), 0.01);
|
||||
assertEquals("" + i, trackPoint.getPower(), importedTrackPoint.getPower(), 0.01);
|
||||
}
|
||||
if (verifyAltitudeGain) {
|
||||
assertEquals(trackPoint.getAltitudeGain(), importedTrackPoint.getAltitudeGain(), 0.01);
|
||||
|
||||
@@ -44,7 +44,7 @@ public final class Marker {
|
||||
private String description = "";
|
||||
private String category = "";
|
||||
private String icon = "";
|
||||
private final Track.Id trackId;
|
||||
private Track.Id trackId;
|
||||
|
||||
private final Instant time;
|
||||
private Double latitude;
|
||||
@@ -74,16 +74,13 @@ public final class Marker {
|
||||
if (!trackPoint.hasLocation())
|
||||
throw new RuntimeException("Marker requires a trackpoint with a location.");
|
||||
|
||||
this.latitude = trackPoint.getLatitude();
|
||||
this.longitude = trackPoint.getLongitude();
|
||||
if (trackPoint.hasAccuracy()) this.accuracy = trackPoint.getAccuracy();
|
||||
if (trackPoint.hasAltitude()) this.altitude = trackPoint.getAltitude();
|
||||
if (trackPoint.hasBearing()) this.bearing = trackPoint.getBearing();
|
||||
setTrackPoint(trackPoint);
|
||||
|
||||
this.length = Distance.of(0); //TODO Not cool!
|
||||
this.duration = Duration.ofMillis(0); //TODO Not cool!
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public Marker(String name, String description, String category, String icon, @NonNull Track.Id trackId, @NonNull TrackStatistics statistics, @NonNull TrackPoint trackPoint, String photoUrl) {
|
||||
this(trackId, trackPoint);
|
||||
this.name = name;
|
||||
@@ -95,6 +92,15 @@ public final class Marker {
|
||||
this.photoUrl = photoUrl;
|
||||
}
|
||||
|
||||
//TODO Is somehow part of the initialization process. Can we at least limit visibility?
|
||||
public void setTrackPoint(TrackPoint trackPoint) {
|
||||
this.latitude = trackPoint.getLatitude();
|
||||
this.longitude = trackPoint.getLongitude();
|
||||
if (trackPoint.hasAccuracy()) this.accuracy = trackPoint.getAccuracy();
|
||||
if (trackPoint.hasAltitude()) this.altitude = trackPoint.getAltitude();
|
||||
if (trackPoint.hasBearing()) this.bearing = trackPoint.getBearing();
|
||||
}
|
||||
|
||||
/**
|
||||
* May be null if the it was not loaded from the database.
|
||||
*/
|
||||
@@ -143,16 +149,19 @@ public final class Marker {
|
||||
this.icon = icon;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Track.Id getTrackId() {
|
||||
return trackId;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void setTrackId(@NonNull Track.Id trackId) {
|
||||
this.trackId = trackId;
|
||||
}
|
||||
|
||||
public boolean hasLocation() {
|
||||
return latitude != null || longitude != null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Location getLocation() {
|
||||
Location location = new Location("");
|
||||
location.setTime(time.toEpochMilli());
|
||||
|
||||
@@ -33,6 +33,7 @@ import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
* @author Leif Hendrik Wilden
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
//TODO Do not default initialize attributes; might be confusing for debugging
|
||||
public class Track {
|
||||
|
||||
private Track.Id id;
|
||||
|
||||
@@ -55,7 +55,7 @@ public class TrackPoint {
|
||||
private Altitude altitude;
|
||||
private Speed speed;
|
||||
private Float bearing;
|
||||
private Distance sensorDistance_m;
|
||||
private Distance sensorDistance;
|
||||
|
||||
public enum Type {
|
||||
SEGMENT_START_MANUAL(-2), //Start of a segment due to user interaction (start, resume)
|
||||
@@ -99,7 +99,11 @@ public class TrackPoint {
|
||||
}
|
||||
|
||||
public TrackPoint(@NonNull Location location) {
|
||||
this(Type.TRACKPOINT);
|
||||
this(Type.TRACKPOINT, location, Instant.now());
|
||||
}
|
||||
|
||||
public TrackPoint(@NonNull Type type, @NonNull Location location, @NonNull Instant time) {
|
||||
this(type);
|
||||
|
||||
this.latitude = location.getLatitude();
|
||||
this.longitude = location.getLongitude();
|
||||
@@ -107,7 +111,9 @@ public class TrackPoint {
|
||||
this.speed = Speed.of(location.getSpeed());
|
||||
this.accuracy = location.getAccuracy();
|
||||
|
||||
setTime(Instant.now());
|
||||
//TODO Should we copy the bearing?
|
||||
|
||||
setTime(time);
|
||||
}
|
||||
|
||||
public TrackPoint(@NonNull Type type, Instant time) {
|
||||
@@ -314,10 +320,14 @@ public class TrackPoint {
|
||||
this.accuracy = horizontalAccuracy;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Distance distanceToPrevious(@NonNull TrackPoint previous) {
|
||||
if (hasSensorDistance()) {
|
||||
return getSensorDistance();
|
||||
}
|
||||
if (!(hasLocation() && previous.hasLocation())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Distance.of(getLocation().distanceTo(previous.getLocation()));
|
||||
}
|
||||
@@ -336,15 +346,15 @@ public class TrackPoint {
|
||||
|
||||
// Sensor data
|
||||
public boolean hasSensorDistance() {
|
||||
return sensorDistance_m != null;
|
||||
return sensorDistance != null;
|
||||
}
|
||||
|
||||
public Distance getSensorDistance() {
|
||||
return sensorDistance_m;
|
||||
return sensorDistance;
|
||||
}
|
||||
|
||||
public void setSensorDistance(Distance distance_m) {
|
||||
this.sensorDistance_m = distance_m;
|
||||
this.sensorDistance = distance_m;
|
||||
}
|
||||
|
||||
public boolean hasSensorData() {
|
||||
|
||||
@@ -243,6 +243,7 @@ public class ContentProviderUtils {
|
||||
* @param track the track
|
||||
* @return the content provider URI of the inserted track.
|
||||
*/
|
||||
///TODO Return Track.Id
|
||||
public Uri insertTrack(Track track) {
|
||||
return contentResolver.insert(TracksColumns.CONTENT_URI, createContentValues(track));
|
||||
}
|
||||
@@ -569,6 +570,7 @@ public class ContentProviderUtils {
|
||||
}
|
||||
|
||||
//TODO Only used for file import; might be better to replace it.
|
||||
//TODO Rename to bulkInsert
|
||||
public int bulkInsertTrackPoint(List<TrackPoint> trackPoints, Track.Id trackId) {
|
||||
ContentValues[] values = new ContentValues[trackPoints.size()];
|
||||
for (int i = 0; i < trackPoints.size(); i++) {
|
||||
@@ -577,6 +579,15 @@ public class ContentProviderUtils {
|
||||
return contentResolver.bulkInsert(TrackPointsColumns.CONTENT_URI_BY_ID, values);
|
||||
}
|
||||
|
||||
//TODO Set trackId in this method.
|
||||
public int bulkInsertMarkers(List<Marker> markers, Track.Id trackId) {
|
||||
ContentValues[] values = new ContentValues[markers.size()];
|
||||
for (int i = 0; i < markers.size(); i++) {
|
||||
values[i] = createContentValues(markers.get(i));
|
||||
}
|
||||
return contentResolver.bulkInsert(MarkerColumns.CONTENT_URI, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last location id for a track.
|
||||
* Returns -1L if it doesn't exist.
|
||||
|
||||
+210
-84
@@ -17,28 +17,38 @@
|
||||
package de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.location.Location;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import org.xml.sax.Attributes;
|
||||
import org.xml.sax.Locator;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Marker;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.io.file.exporter.KMLTrackExporter;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
|
||||
/**
|
||||
* Imports a KML file.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
public class KmlFileTrackImporter extends DefaultHandler implements XMLImporter.TrackParser {
|
||||
|
||||
private static final String TAG = KmlFileTrackImporter.class.getSimpleName();
|
||||
|
||||
@@ -64,29 +74,50 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
|
||||
private static final String ATTRIBUTE_NAME = "name";
|
||||
|
||||
private boolean trackStarted = false;
|
||||
private Locator locator;
|
||||
|
||||
private final Context context;
|
||||
|
||||
// Belongs to the current track
|
||||
private final ArrayList<Instant> whenList = new ArrayList<>();
|
||||
private final ArrayList<Location> locationList = new ArrayList<>();
|
||||
|
||||
private String extendedDataType;
|
||||
private final ArrayList<TrackPoint> trackPoints = new ArrayList<>();
|
||||
private final ArrayList<Float> speedList = new ArrayList<>();
|
||||
private final ArrayList<Float> distanceList = new ArrayList<>();
|
||||
private final ArrayList<Float> cadenceList = new ArrayList<>();
|
||||
private final ArrayList<Float> heartRateList = new ArrayList<>();
|
||||
private final ArrayList<Float> powerList = new ArrayList<>();
|
||||
private final ArrayList<Float> sensorSpeedList = new ArrayList<>();
|
||||
private final ArrayList<Float> sensorDistanceList = new ArrayList<>();
|
||||
private final ArrayList<Float> sensorCadenceList = new ArrayList<>();
|
||||
private final ArrayList<Float> sensorHeartRateList = new ArrayList<>();
|
||||
private final ArrayList<Float> sensorPowerList = new ArrayList<>();
|
||||
private final ArrayList<Float> altitudeGainList = new ArrayList<>();
|
||||
private final ArrayList<Float> altitudeLossList = new ArrayList<>();
|
||||
|
||||
private final ArrayList<Marker> markers = new ArrayList<>();
|
||||
|
||||
// The current element content
|
||||
private String content = "";
|
||||
|
||||
private String icon;
|
||||
private String name;
|
||||
private String description;
|
||||
private String category;
|
||||
private String latitude;
|
||||
private String longitude;
|
||||
private String altitude;
|
||||
private String markerType;
|
||||
private String photoUrl;
|
||||
private String uuid;
|
||||
|
||||
private final TrackImporter trackImporter;
|
||||
|
||||
public KmlFileTrackImporter(Context context) {
|
||||
this(context, new ContentProviderUtils(context));
|
||||
}
|
||||
this.context = context;
|
||||
|
||||
@VisibleForTesting
|
||||
KmlFileTrackImporter(Context context, ContentProviderUtils contentProviderUtils) {
|
||||
super(context, contentProviderUtils);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultHandler getHandler() {
|
||||
return this;
|
||||
//TODO move this to instantiation of this class
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
Distance maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(sharedPreferences, context);
|
||||
Distance recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context);
|
||||
boolean preventReimport = PreferencesUtils.getPreventReimportTracks(sharedPreferences, context);
|
||||
this.trackImporter = new TrackImporter(context, new ContentProviderUtils(context), recordingDistanceInterval, maxRecordingDistance, preventReimport);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -98,11 +129,10 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
onMarkerStart();
|
||||
break;
|
||||
case TAG_GX_MULTI_TRACK:
|
||||
trackStarted = true;
|
||||
onTrackStart();
|
||||
trackImporter.newTrack();
|
||||
break;
|
||||
case TAG_GX_TRACK:
|
||||
if (!trackStarted) {
|
||||
if (trackImporter == null) {
|
||||
throw new SAXException("No " + TAG_GX_MULTI_TRACK);
|
||||
}
|
||||
onTrackSegmentStart();
|
||||
@@ -113,11 +143,16 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void characters(char[] ch, int start, int length) {
|
||||
content += new String(ch, start, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endElement(String uri, String localName, String tag) throws SAXException {
|
||||
switch (tag) {
|
||||
case TAG_KML:
|
||||
onFileEnd();
|
||||
onFileEnded();
|
||||
break;
|
||||
case TAG_PLACEMARK:
|
||||
case TAG_PHOTO_OVERLAY:
|
||||
@@ -134,7 +169,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
onTrackSegmentEnd();
|
||||
break;
|
||||
case TAG_GX_COORD:
|
||||
onTrackPointEnd();
|
||||
onCoordEnded();
|
||||
break;
|
||||
case TAG_GX_VALUE:
|
||||
onExtendedDataValueEnd();
|
||||
@@ -166,7 +201,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
break;
|
||||
case TAG_WHEN:
|
||||
if (content != null) {
|
||||
time = content.trim();
|
||||
whenList.add(StringUtils.parseTime(content.trim()));
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -183,7 +218,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
}
|
||||
|
||||
// Reset element content
|
||||
content = null;
|
||||
content = "";
|
||||
}
|
||||
|
||||
private void onMarkerStart() {
|
||||
@@ -196,7 +231,6 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
latitude = null;
|
||||
longitude = null;
|
||||
altitude = null;
|
||||
time = null;
|
||||
markerType = null;
|
||||
}
|
||||
|
||||
@@ -204,7 +238,30 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
if (!MARKER_STYLE.equals(markerType)) {
|
||||
return;
|
||||
}
|
||||
addMarker();
|
||||
|
||||
if (whenList.size() != 1) {
|
||||
Log.w(TAG, "Marker without time ignored.");
|
||||
return;
|
||||
}
|
||||
|
||||
Location location = createLocation(longitude, latitude, altitude);
|
||||
if (location == null) {
|
||||
Log.w(TAG, "Marker with invalid coordinates ignored: " + location);
|
||||
return;
|
||||
}
|
||||
|
||||
Marker marker = new Marker(null, new TrackPoint(TrackPoint.Type.TRACKPOINT, location, whenList.get(0))); //TODO Creating marker without need
|
||||
marker.setName(name != null ? name : "");
|
||||
marker.setDescription(description != null ? description : "");
|
||||
marker.setCategory(category != null ? category : "");
|
||||
marker.setPhotoUrl(photoUrl);
|
||||
markers.add(marker);
|
||||
|
||||
name = null;
|
||||
description = null;
|
||||
category = null;
|
||||
photoUrl = null;
|
||||
whenList.clear();
|
||||
}
|
||||
|
||||
private void onMarkerLocationEnd() {
|
||||
@@ -219,39 +276,56 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onTrackSegmentStart() {
|
||||
super.onTrackSegmentStart();
|
||||
trackPoints.clear();
|
||||
speedList.clear();
|
||||
distanceList.clear();
|
||||
heartRateList.clear();
|
||||
cadenceList.clear();
|
||||
powerList.clear();
|
||||
private void onTrackSegmentStart() {
|
||||
locationList.clear();
|
||||
whenList.clear();
|
||||
|
||||
sensorSpeedList.clear();
|
||||
sensorDistanceList.clear();
|
||||
sensorHeartRateList.clear();
|
||||
sensorCadenceList.clear();
|
||||
sensorPowerList.clear();
|
||||
altitudeGainList.clear();
|
||||
altitudeLossList.clear();
|
||||
}
|
||||
|
||||
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);
|
||||
private void onTrackSegmentEnd() {
|
||||
if (locationList.size() != whenList.size()) {
|
||||
throw new ImportParserException("<coords> and <when> should have the same count.");
|
||||
}
|
||||
|
||||
if (i < speedList.size() && speedList.get(i) != null) {
|
||||
trackPoint.setSpeed(Speed.of(speedList.get(i)));
|
||||
// Close a track segment by inserting the segment locations
|
||||
for (int i = 0; i < locationList.size(); i++) {
|
||||
Instant time = whenList.get(i);
|
||||
Location location = locationList.get(i);
|
||||
|
||||
TrackPoint trackPoint;
|
||||
if (i == 0) {
|
||||
if (location == null) {
|
||||
trackPoint = TrackPoint.createSegmentStartManualWithTime(time);
|
||||
} else {
|
||||
trackPoint = new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, location, time);
|
||||
}
|
||||
} else if (i == locationList.size() - 1 && location == null) {
|
||||
trackPoint = TrackPoint.createSegmentEndWithTime(time);
|
||||
} else {
|
||||
trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, location, time);
|
||||
}
|
||||
if (i < distanceList.size() && distanceList.get(i) != null) {
|
||||
trackPoint.setSensorDistance(Distance.of(distanceList.get(i)));
|
||||
|
||||
if (i < sensorSpeedList.size() && sensorSpeedList.get(i) != null) {
|
||||
trackPoint.setSpeed(Speed.of(sensorSpeedList.get(i)));
|
||||
}
|
||||
if (i < heartRateList.size()) {
|
||||
trackPoint.setHeartRate_bpm(heartRateList.get(i));
|
||||
if (i < sensorDistanceList.size() && sensorDistanceList.get(i) != null) {
|
||||
trackPoint.setSensorDistance(Distance.of(sensorDistanceList.get(i)));
|
||||
}
|
||||
if (i < cadenceList.size()) {
|
||||
trackPoint.setCyclingCadence_rpm(cadenceList.get(i));
|
||||
if (i < sensorHeartRateList.size()) {
|
||||
trackPoint.setHeartRate_bpm(sensorHeartRateList.get(i));
|
||||
}
|
||||
if (i < powerList.size()) {
|
||||
trackPoint.setPower(powerList.get(i));
|
||||
if (i < sensorCadenceList.size()) {
|
||||
trackPoint.setCyclingCadence_rpm(sensorCadenceList.get(i));
|
||||
}
|
||||
if (i < sensorPowerList.size()) {
|
||||
trackPoint.setPower(sensorPowerList.get(i));
|
||||
}
|
||||
if (i < altitudeGainList.size()) {
|
||||
trackPoint.setAltitudeGain(altitudeGainList.get(i));
|
||||
@@ -260,18 +334,11 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
trackPoint.setAltitudeLoss(altitudeLossList.get(i));
|
||||
}
|
||||
|
||||
insertTrackPoint(trackPoint);
|
||||
trackImporter.addTrackPoint(trackPoint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On track point end. gx:coord end tag.
|
||||
*/
|
||||
private void onTrackPointEnd() {
|
||||
// Add trackPoint to trackPoints
|
||||
if (content == null) {
|
||||
return;
|
||||
}
|
||||
private void onCoordEnded() {
|
||||
String[] parts = content.trim().split(" ");
|
||||
if (parts.length == 2 || parts.length == 3) {
|
||||
longitude = parts[0];
|
||||
@@ -279,33 +346,41 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
altitude = parts.length == 3 ? parts[2] : null;
|
||||
}
|
||||
|
||||
// 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;
|
||||
locationList.add(createLocation(longitude, latitude, altitude));
|
||||
|
||||
trackPoint.setType(type);
|
||||
}
|
||||
trackPoints.add(trackPoint);
|
||||
|
||||
// Reset variables for next trackpoint (which might not have such data).
|
||||
time = null;
|
||||
longitude = null;
|
||||
latitude = null;
|
||||
altitude = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* On extended data start. gx:SimpleArrayData start tag.
|
||||
*/
|
||||
private Location createLocation(String longitude, String latitude, String altitude) {
|
||||
Location location = null;
|
||||
if (longitude != null || latitude != null) {
|
||||
location = new Location("import");
|
||||
|
||||
try {
|
||||
location.setLatitude(Double.parseDouble(latitude));
|
||||
location.setLongitude(Double.parseDouble(longitude));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new AbstractFileTrackImporter.ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse latitude longitude: %s %s", latitude, longitude)), e);
|
||||
}
|
||||
|
||||
if (altitude != null) {
|
||||
try {
|
||||
location.setAltitude(Double.parseDouble(altitude));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new AbstractFileTrackImporter.ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse altitude: %s", altitude)), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return location;
|
||||
}
|
||||
|
||||
|
||||
private void onExtendedDataStart(Attributes attributes) {
|
||||
extendedDataType = attributes.getValue(ATTRIBUTE_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* On extended data value end. gx:value end tag.
|
||||
*/
|
||||
private void onExtendedDataValueEnd() throws SAXException {
|
||||
Float value = null;
|
||||
if (content != null) {
|
||||
@@ -320,19 +395,19 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
}
|
||||
switch (extendedDataType) {
|
||||
case KMLTrackExporter.EXTENDED_DATA_TYPE_SPEED:
|
||||
speedList.add(value);
|
||||
sensorSpeedList.add(value);
|
||||
break;
|
||||
case KMLTrackExporter.EXTENDED_DATA_TYPE_DISTANCE:
|
||||
distanceList.add(value);
|
||||
sensorDistanceList.add(value);
|
||||
break;
|
||||
case KMLTrackExporter.EXTENDED_DATA_TYPE_POWER:
|
||||
powerList.add(value);
|
||||
sensorPowerList.add(value);
|
||||
break;
|
||||
case KMLTrackExporter.EXTENDED_DATA_TYPE_HEART_RATE:
|
||||
heartRateList.add(value);
|
||||
sensorHeartRateList.add(value);
|
||||
break;
|
||||
case KMLTrackExporter.EXTENDED_DATA_TYPE_CADENCE:
|
||||
cadenceList.add(value);
|
||||
sensorCadenceList.add(value);
|
||||
break;
|
||||
case KMLTrackExporter.EXTENDED_DATA_TYPE_ALTITUDE_GAIN:
|
||||
altitudeGainList.add(value);
|
||||
@@ -344,4 +419,55 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
Log.w(TAG, "Data from extended data " + extendedDataType + " is not (yet) supported.");
|
||||
}
|
||||
}
|
||||
|
||||
private void onTrackEnd() {
|
||||
Track track = new Track();
|
||||
track.setName(name != null ? name : "");
|
||||
|
||||
try {
|
||||
track.setUuid(UUID.fromString(uuid));
|
||||
} catch (IllegalArgumentException | NullPointerException e) {
|
||||
Log.w(TAG, "could not parse Track UUID, generating a new one.");
|
||||
track.setUuid(UUID.randomUUID());
|
||||
}
|
||||
|
||||
track.setDescription(description != null ? description : "");
|
||||
|
||||
if (category != null) {
|
||||
track.setCategory(category);
|
||||
|
||||
if (icon == null) {
|
||||
//TODO exporting/importing icon is not implemented.
|
||||
icon = TrackIconUtils.getIconValue(context, category);
|
||||
}
|
||||
}
|
||||
|
||||
track.setIcon(icon != null ? icon : "");
|
||||
|
||||
trackImporter.setTrack(track);
|
||||
}
|
||||
|
||||
private String createErrorMessage(String message) {
|
||||
return String.format(Locale.US, "Parsing error at line: %d column: %d. %s", locator.getLineNumber(), locator.getColumnNumber(), message);
|
||||
}
|
||||
|
||||
private void onFileEnded() {
|
||||
trackImporter.addMarkers(markers);
|
||||
trackImporter.finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultHandler getHandler() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Track.Id> getImportTrackIds() {
|
||||
return trackImporter.getTrackIds();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanImport() {
|
||||
trackImporter.cleanImport();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
package de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Marker;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
import de.dennisguse.opentracks.util.LocationUtils;
|
||||
|
||||
/**
|
||||
* Handles logic to import:
|
||||
* 1. addTrackPoints()
|
||||
* 2. addMarkers();
|
||||
* 3. setTrack();
|
||||
* 4. newTrack(); //stores current track to databse
|
||||
* 5. if needed go to 1.
|
||||
* 6. finish()
|
||||
* <p>
|
||||
* NOTE: This class modifies the parameter.
|
||||
* Do not re-use these objects anywhere else.
|
||||
*/
|
||||
public class TrackImporter {
|
||||
|
||||
private static final String TAG = TrackImporter.class.getSimpleName();
|
||||
|
||||
private final Context context;
|
||||
private final ContentProviderUtils contentProviderUtils;
|
||||
|
||||
private final Distance recordingDistanceInterval;
|
||||
private final Distance maxRecordingDistance;
|
||||
private final boolean preventReimport;
|
||||
|
||||
private final List<Track.Id> trackIds = new ArrayList<>();
|
||||
|
||||
// Current track
|
||||
private Track track;
|
||||
private final List<TrackPoint> trackPoints = new LinkedList<>();
|
||||
private final List<Marker> markers = new LinkedList<>();
|
||||
|
||||
public TrackImporter(Context context, ContentProviderUtils contentProviderUtils, Distance recordingDistanceInterval, Distance maxRecordingDistance, boolean preventReimport) {
|
||||
this.context = context;
|
||||
this.contentProviderUtils = contentProviderUtils;
|
||||
this.recordingDistanceInterval = recordingDistanceInterval;
|
||||
this.maxRecordingDistance = maxRecordingDistance;
|
||||
this.preventReimport = preventReimport;
|
||||
}
|
||||
|
||||
void newTrack() {
|
||||
if (track != null) {
|
||||
finishTrack();
|
||||
}
|
||||
|
||||
track = null;
|
||||
trackPoints.clear();
|
||||
markers.clear();
|
||||
}
|
||||
|
||||
void addTrackPoint(TrackPoint trackPoint) {
|
||||
this.trackPoints.add(trackPoint);
|
||||
}
|
||||
|
||||
void addTrackPoints(List<TrackPoint> trackPoints) {
|
||||
this.trackPoints.addAll(trackPoints);
|
||||
}
|
||||
|
||||
void addMarker(Marker marker) {
|
||||
this.markers.add(marker);
|
||||
}
|
||||
|
||||
void addMarkers(List<Marker> markers) {
|
||||
this.markers.addAll(markers);
|
||||
}
|
||||
|
||||
void setTrack(Track track) {
|
||||
this.track = track;
|
||||
}
|
||||
|
||||
void finish() {
|
||||
if (track != null) {
|
||||
finishTrack();
|
||||
}
|
||||
}
|
||||
|
||||
private void finishTrack() {
|
||||
if (trackPoints.isEmpty()) {
|
||||
throw new ImportParserException("Cannot import track without any locations.");
|
||||
}
|
||||
|
||||
// Store Track
|
||||
if (contentProviderUtils.getTrack(track.getUuid()) != null) {
|
||||
if (preventReimport) {
|
||||
throw new ImportAlreadyExistsException(context.getString(R.string.import_prevent_reimport));
|
||||
}
|
||||
|
||||
//TODO This is a workaround until we have proper UI.
|
||||
track.setUuid(UUID.randomUUID());
|
||||
}
|
||||
|
||||
TrackStatisticsUpdater updater = new TrackStatisticsUpdater();
|
||||
updater.addTrackPoints(trackPoints, recordingDistanceInterval);
|
||||
track.setTrackStatistics(updater.getTrackStatistics());
|
||||
|
||||
Uri trackUri = contentProviderUtils.insertTrack(track);
|
||||
Track.Id trackId = new Track.Id(ContentUris.parseId(trackUri));
|
||||
|
||||
// Store TrackPoints
|
||||
adjustTrackPoints();
|
||||
contentProviderUtils.bulkInsertTrackPoint(trackPoints, trackId);
|
||||
|
||||
// Store Markers
|
||||
matchMarkers2TrackPoints(trackId);
|
||||
for (Marker marker : markers)
|
||||
marker.setTrackId(trackId); //TODO Should happen in bulkInsertMarkers
|
||||
|
||||
contentProviderUtils.bulkInsertMarkers(markers, trackId);
|
||||
|
||||
//Clear up.
|
||||
trackPoints.clear();
|
||||
markers.clear();
|
||||
|
||||
trackIds.add(trackId);
|
||||
}
|
||||
|
||||
/**
|
||||
* If not present: calculate data from the previous trackPoint (if present)
|
||||
* NOTE: Modifies content of trackPoints.
|
||||
*/
|
||||
private void adjustTrackPoints() {
|
||||
for (int i = 0; i < trackPoints.size(); i++) {
|
||||
TrackPoint current = trackPoints.get(i);
|
||||
|
||||
if (current.hasLocation()) {
|
||||
Instant time = current.getTime();
|
||||
if (current.getLatitude() == 100) {
|
||||
//TODO Remove by 31st December 2021.
|
||||
current = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL);
|
||||
current.setTime(time);
|
||||
} else if (current.getLatitude() == 200) {
|
||||
//TODO Remove by 31st December 2021.
|
||||
current = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL);
|
||||
current.setTime(time);
|
||||
} else if (!LocationUtils.isValidLocation(current.getLocation())) {
|
||||
throw new ImportParserException("Invalid location detected: " + current);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 1; i < trackPoints.size(); i++) {
|
||||
TrackPoint previous = trackPoints.get(i - 1);
|
||||
TrackPoint current = trackPoints.get(i);
|
||||
|
||||
Distance distanceToPrevious = current.distanceToPrevious(previous);
|
||||
if (distanceToPrevious != null) {
|
||||
if (!current.hasSpeed()) {
|
||||
Duration timeDifference = Duration.between(previous.getTime(), current.getTime());
|
||||
current.setSpeed(Speed.of(distanceToPrevious, timeDifference));
|
||||
}
|
||||
|
||||
if (!current.hasBearing()) {
|
||||
current.setBearing(previous.bearingTo(current));
|
||||
}
|
||||
|
||||
if (current.getType().equals(TrackPoint.Type.TRACKPOINT) && distanceToPrevious.greaterThan(maxRecordingDistance)) {
|
||||
current.setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: Modifies content of markers (incl. removal).
|
||||
*/
|
||||
private void matchMarkers2TrackPoints(Track.Id trackId) {
|
||||
List<TrackPoint> trackPointsWithLocation = trackPoints.stream()
|
||||
.filter(TrackPoint::hasLocation)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<Marker> todoMarkers = new LinkedList<>(markers);
|
||||
List<Marker> doneMarkers = new LinkedList<>();
|
||||
|
||||
for (final TrackPoint trackPoint : trackPointsWithLocation) {
|
||||
if (todoMarkers.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
TrackStatisticsUpdater updater = new TrackStatisticsUpdater();
|
||||
updater.addTrackPoint(trackPoint, recordingDistanceInterval);
|
||||
|
||||
List<Marker> matchedMarkers = todoMarkers.stream()
|
||||
.filter(it -> trackPoint.getLatitude() == it.getLatitude()
|
||||
&& trackPoint.getLongitude() == it.getLongitude()
|
||||
&& trackPoint.getTime().equals(it.getTime())
|
||||
)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
TrackStatistics statistics = updater.getTrackStatistics();
|
||||
for (Marker marker : matchedMarkers) {
|
||||
if (marker.hasPhoto()) {
|
||||
marker.setPhotoUrl(getInternalPhotoUrl(trackId, marker.getPhotoUrl()));
|
||||
}
|
||||
|
||||
marker.setIcon(context.getString(R.string.marker_icon_url)); //TODO Why?
|
||||
|
||||
marker.setLength(statistics.getTotalDistance());
|
||||
marker.setDuration(statistics.getTotalTime());
|
||||
|
||||
marker.setTrackPoint(trackPoint);
|
||||
}
|
||||
|
||||
todoMarkers.removeAll(matchedMarkers);
|
||||
doneMarkers.addAll(matchedMarkers);
|
||||
}
|
||||
|
||||
if (todoMarkers.isEmpty()) {
|
||||
Log.w(TAG, "Some markers could not be attached to TrackPoints; those are not imported.");
|
||||
}
|
||||
|
||||
markers.clear();
|
||||
markers.addAll(doneMarkers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the photo url for a file.
|
||||
*
|
||||
* @param externalPhotoUrl the file name
|
||||
*/
|
||||
private String getInternalPhotoUrl(@NonNull Track.Id trackId, @NonNull String externalPhotoUrl) {
|
||||
String importFileName = KmzTrackImporter.importNameForFilename(externalPhotoUrl);
|
||||
File file = FileUtils.buildInternalPhotoFile(context, trackId, Uri.parse(importFileName));
|
||||
if (file != null) {
|
||||
Uri photoUri = FileUtils.getUriForFile(context, file);
|
||||
return "" + photoUri;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<Track.Id> getTrackIds() {
|
||||
return Collections.unmodifiableList(trackIds);
|
||||
}
|
||||
|
||||
public void cleanImport() {
|
||||
contentProviderUtils.deleteTracks(context, trackIds);
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,7 @@ public class XMLImporter {
|
||||
}
|
||||
|
||||
interface TrackParser {
|
||||
@Deprecated
|
||||
DefaultHandler getHandler();
|
||||
|
||||
List<Track.Id> getImportTrackIds();
|
||||
|
||||
@@ -22,6 +22,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
@@ -98,10 +99,13 @@ public class TrackStatisticsUpdater {
|
||||
return trackInitialized;
|
||||
}
|
||||
|
||||
public void addTrackPoints(List<TrackPoint> trackPoints, Distance minGPSDistance) {
|
||||
for (TrackPoint tp : trackPoints) {
|
||||
addTrackPoint(tp, minGPSDistance);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a trackPoint.
|
||||
*
|
||||
* @param trackPoint the trackPoint
|
||||
* @param minGPSDistance the min recording distance
|
||||
*/
|
||||
public void addTrackPoint(TrackPoint trackPoint, Distance minGPSDistance) {
|
||||
|
||||
Reference in New Issue
Block a user