Import: re-implement KML parsing.

Fixes #790.
This commit is contained in:
Dennis Guse
2021-05-25 18:40:52 +02:00
parent 68c8839ebb
commit 54e8fd3ee3
9 changed files with 540 additions and 111 deletions
@@ -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.
@@ -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) {