forked from upstream-mirrors/OpenTracks
Import: re-implement GPX parsing.
This commit is contained in:
@@ -204,6 +204,7 @@ public class TrackPoint {
|
|||||||
this.longitude = longitude;
|
this.longitude = longitude;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO Better return null, if no location is present aka latitude == null etc.
|
||||||
@NonNull
|
@NonNull
|
||||||
public Location getLocation() {
|
public Location getLocation() {
|
||||||
Location location = new Location("");
|
Location location = new Location("");
|
||||||
@@ -336,10 +337,12 @@ public class TrackPoint {
|
|||||||
return hasAccuracy() && accuracy < poorAccuracy;
|
return hasAccuracy() && accuracy < poorAccuracy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO Bearing requires a location; what do we do if we don't have any?
|
||||||
public float bearingTo(@NonNull TrackPoint dest) {
|
public float bearingTo(@NonNull TrackPoint dest) {
|
||||||
return getLocation().bearingTo(dest.getLocation());
|
return getLocation().bearingTo(dest.getLocation());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO Bearing requires a location; what do we do if we don't have any?
|
||||||
public float bearingTo(@NonNull Location dest) {
|
public float bearingTo(@NonNull Location dest) {
|
||||||
return getLocation().bearingTo(dest);
|
return getLocation().bearingTo(dest);
|
||||||
}
|
}
|
||||||
|
|||||||
-509
@@ -1,509 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2012 Google Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
||||||
* use this file except in compliance with the License. You may obtain a copy of
|
|
||||||
* the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations under
|
|
||||||
* the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package de.dennisguse.opentracks.io.file.importer;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import org.xml.sax.Locator;
|
|
||||||
import org.xml.sax.SAXException;
|
|
||||||
import org.xml.sax.helpers.DefaultHandler;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
|
||||||
import de.dennisguse.opentracks.content.data.Altitude;
|
|
||||||
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.content.provider.TrackPointIterator;
|
|
||||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
|
||||||
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
|
||||||
import de.dennisguse.opentracks.util.FileUtils;
|
|
||||||
import de.dennisguse.opentracks.util.LocationUtils;
|
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
|
||||||
import de.dennisguse.opentracks.util.StringUtils;
|
|
||||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abstract class for file track importers.
|
|
||||||
*
|
|
||||||
* @author Jimmy Shih
|
|
||||||
*/
|
|
||||||
abstract class AbstractFileTrackImporter extends DefaultHandler implements XMLImporter.TrackParser {
|
|
||||||
|
|
||||||
private static final String TAG = AbstractFileTrackImporter.class.getSimpleName();
|
|
||||||
|
|
||||||
private final Context context;
|
|
||||||
private final ContentProviderUtils contentProviderUtils;
|
|
||||||
private final Distance recordingDistanceInterval;
|
|
||||||
|
|
||||||
private final List<Track.Id> trackIds = new ArrayList<>();
|
|
||||||
private final List<Marker> markers = new ArrayList<>();
|
|
||||||
|
|
||||||
// The current element content
|
|
||||||
//TODO Should be made private and getter be used by child classes.
|
|
||||||
protected String content;
|
|
||||||
|
|
||||||
protected String icon;
|
|
||||||
protected String name;
|
|
||||||
protected String description;
|
|
||||||
protected String category;
|
|
||||||
protected String latitude;
|
|
||||||
protected String longitude;
|
|
||||||
protected String altitude;
|
|
||||||
protected String time;
|
|
||||||
protected String speed;
|
|
||||||
protected String heartrate;
|
|
||||||
protected String cadence;
|
|
||||||
protected String power;
|
|
||||||
protected String markerType;
|
|
||||||
protected String photoUrl;
|
|
||||||
protected String uuid;
|
|
||||||
protected String gain;
|
|
||||||
protected String loss;
|
|
||||||
protected String distance;
|
|
||||||
|
|
||||||
// The current track data
|
|
||||||
private TrackData trackData;
|
|
||||||
|
|
||||||
// The SAX locator to get the current line information
|
|
||||||
private Locator locator;
|
|
||||||
|
|
||||||
private final SharedPreferences sharedPreferences;
|
|
||||||
|
|
||||||
AbstractFileTrackImporter(Context context, ContentProviderUtils contentProviderUtils) {
|
|
||||||
this.context = context;
|
|
||||||
this.contentProviderUtils = contentProviderUtils;
|
|
||||||
sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
|
||||||
this.recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setDocumentLocator(Locator locator) {
|
|
||||||
this.locator = locator;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void characters(char[] ch, int start, int length) {
|
|
||||||
String newContent = new String(ch, start, length);
|
|
||||||
if (content == null) {
|
|
||||||
content = newContent;
|
|
||||||
} else {
|
|
||||||
// In 99% of the cases, a single call to this method will be made for each sequence of characters we're interested in, so we'll rarely be concatenating strings, thus not justifying the use of a StringBuilder.
|
|
||||||
content += newContent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void onFileEnd() {
|
|
||||||
// Add markers to the last imported track
|
|
||||||
int size = trackIds.size();
|
|
||||||
if (size == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Track.Id trackId = trackIds.get(size - 1);
|
|
||||||
Track track = contentProviderUtils.getTrack(trackId);
|
|
||||||
if (track == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int markerPosition = -1;
|
|
||||||
Marker marker = null;
|
|
||||||
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) {
|
|
||||||
if (marker == null) {
|
|
||||||
markerPosition++;
|
|
||||||
marker = markerPosition < markers.size() ? markers.get(markerPosition) : null;
|
|
||||||
if (marker == null) {
|
|
||||||
// No more markers
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// If marker had photo it must be translated to internal photo url (depend on track id)
|
|
||||||
if (marker.hasPhoto()) {
|
|
||||||
marker.setPhotoUrl(getInternalPhotoUrl(marker.getPhotoUrl()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (trackPoint == null) {
|
|
||||||
if (!trackPointIterator.hasNext()) {
|
|
||||||
// No more track points. Ignore the rest of the markers.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
trackPoint = trackPointIterator.next();
|
|
||||||
trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((marker.getTime()).isAfter(trackPoint.getTime())) {
|
|
||||||
trackPoint = null;
|
|
||||||
} else if (marker.getTime().isBefore(trackPoint.getTime())) {
|
|
||||||
Log.w(TAG, "Ignoring marker: current trackPoint was after marker.");
|
|
||||||
marker = null;
|
|
||||||
} else {
|
|
||||||
// The marker trackPoint time matches the track point time
|
|
||||||
if (!trackPoint.hasLocation()) {
|
|
||||||
// Invalid trackPoint, load the next trackPoint
|
|
||||||
trackPoint = null;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Valid trackPoint
|
|
||||||
if (trackPoint.getLatitude() == marker.getLatitude() && trackPoint.getLongitude() == marker.getLongitude()) {
|
|
||||||
String markerDescription = marker.getDescription();
|
|
||||||
String icon = context.getString(R.string.marker_icon_url);
|
|
||||||
TrackStatistics stats = trackStatisticsUpdater.getTrackStatistics();
|
|
||||||
|
|
||||||
// Insert marker
|
|
||||||
Marker newMarker = new Marker(marker.getName(), markerDescription, marker.getCategory(), icon, track.getId(), stats, trackPoint, marker.getPhotoUrl());
|
|
||||||
contentProviderUtils.insertMarker(newMarker);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the next marker
|
|
||||||
marker = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On track start.
|
|
||||||
*/
|
|
||||||
protected void onTrackStart() throws SAXException {
|
|
||||||
trackData = new TrackData();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On track end.
|
|
||||||
*/
|
|
||||||
protected void onTrackEnd() {
|
|
||||||
if (name != null) {
|
|
||||||
trackData.track.setName(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
trackData.track.setUuid(UUID.fromString(uuid));
|
|
||||||
} catch (IllegalArgumentException | NullPointerException e) {
|
|
||||||
Log.w(TAG, "could not parse Track UUID, generating a new one.");
|
|
||||||
trackData.track.setUuid(UUID.randomUUID());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (description != null) {
|
|
||||||
trackData.track.setDescription(description);
|
|
||||||
}
|
|
||||||
if (category != null) {
|
|
||||||
trackData.track.setCategory(category);
|
|
||||||
//TODO remove when GPX and KML support reading this property.
|
|
||||||
if (icon == null)
|
|
||||||
trackData.track.setIcon(TrackIconUtils.getIconValue(context, category));
|
|
||||||
}
|
|
||||||
if (icon != null) {
|
|
||||||
trackData.track.setIcon(icon);
|
|
||||||
}
|
|
||||||
|
|
||||||
TrackStatisticsUpdater statistics = new TrackStatisticsUpdater();
|
|
||||||
//TODO I guess, we should not filter by recordingDistanceInterval on import; the data is already recorded, so we should not change it.
|
|
||||||
for (TrackPoint trackPoint : trackData.bufferedTrackPoints) {
|
|
||||||
statistics.addTrackPoint(trackPoint, recordingDistanceInterval);
|
|
||||||
}
|
|
||||||
if (!statistics.isTrackInitialized()) {
|
|
||||||
throw new ImportParserException("Track did not contain any locations.");
|
|
||||||
}
|
|
||||||
trackData.track.setTrackStatistics(statistics.getTrackStatistics());
|
|
||||||
|
|
||||||
Track track = contentProviderUtils.getTrack(trackData.track.getUuid());
|
|
||||||
if (track != null) {
|
|
||||||
if (PreferencesUtils.getPreventReimportTracks(sharedPreferences, context)) {
|
|
||||||
throw new ImportAlreadyExistsException(context.getString(R.string.import_prevent_reimport));
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO This is a workaround until we have proper UI.
|
|
||||||
trackData.track.setUuid(UUID.randomUUID());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (trackIds.size() > 0) {
|
|
||||||
// TODO Multi track is not supported yet.
|
|
||||||
cleanImport();
|
|
||||||
throw new ImportParserException("Multi track not supported");
|
|
||||||
}
|
|
||||||
Uri uri = contentProviderUtils.insertTrack(trackData.track);
|
|
||||||
Track.Id trackId = new Track.Id(Long.parseLong(uri.getLastPathSegment()));
|
|
||||||
trackIds.add(trackId);
|
|
||||||
trackData.track.setId(trackId);
|
|
||||||
|
|
||||||
flushTrackPoints();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void onTrackSegmentStart() {
|
|
||||||
trackData.numberOfSegments++;
|
|
||||||
|
|
||||||
//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))
|
|
||||||
) {
|
|
||||||
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 ParsingException {
|
|
||||||
// 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.hasLocation()) {
|
|
||||||
Log.w(TAG, "Marker with invalid coordinates ignored: " + trackPoint.getLocation());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Marker marker = new Marker(null, trackPoint);
|
|
||||||
|
|
||||||
if (name != null) {
|
|
||||||
marker.setName(name);
|
|
||||||
}
|
|
||||||
if (description != null) {
|
|
||||||
marker.setDescription(description);
|
|
||||||
}
|
|
||||||
if (category != null) {
|
|
||||||
marker.setCategory(category);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (photoUrl != null) {
|
|
||||||
marker.setPhotoUrl(photoUrl);
|
|
||||||
}
|
|
||||||
markers.add(marker);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected TrackPoint getTrackPoint() throws ParsingException {
|
|
||||||
TrackPoint trackPoint = createTrackPoint();
|
|
||||||
|
|
||||||
if (trackPoint.hasLocation()) {
|
|
||||||
Instant time = trackPoint.getTime();
|
|
||||||
if (trackPoint.getLatitude() == 100) {
|
|
||||||
//TODO Remove by 31st December 2021.
|
|
||||||
trackPoint = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL);
|
|
||||||
trackPoint.setTime(time);
|
|
||||||
} else if (trackPoint.getLatitude() == 200) {
|
|
||||||
//TODO Remove by 31st December 2021.
|
|
||||||
trackPoint = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL);
|
|
||||||
trackPoint.setTime(time);
|
|
||||||
|
|
||||||
} else if (!LocationUtils.isValidLocation(trackPoint.getLocation())) {
|
|
||||||
throw new ParsingException(createErrorMessage("Invalid location detected: " + trackPoint));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate derived attributes from the previous point
|
|
||||||
if (trackData.lastLocationInCurrentSegment != null && trackData.lastLocationInCurrentSegment.getTime() == null) {
|
|
||||||
if (!trackPoint.hasSpeed()) {
|
|
||||||
Duration timeDifference = Duration.between(trackData.lastLocationInCurrentSegment.getTime(), trackPoint.getTime());
|
|
||||||
|
|
||||||
// Check for negative time change
|
|
||||||
if (timeDifference.isNegative()) {
|
|
||||||
Log.w(TAG, "Time difference not positive.");
|
|
||||||
} else {
|
|
||||||
|
|
||||||
/*
|
|
||||||
* We don't have a speed and bearing in GPX, make something up from the last two points.
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
if (trackPoint.hasLocation() && trackData.lastLocationInCurrentSegment.hasLocation()) {
|
|
||||||
trackPoint.setSpeed(Speed.of(trackData.lastLocationInCurrentSegment.distanceToPrevious(trackPoint), timeDifference));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (trackPoint.hasLocation() && trackData.lastLocationInCurrentSegment.hasLocation()) {
|
|
||||||
trackPoint.setBearing(trackData.lastLocationInCurrentSegment.bearingTo(trackPoint));
|
|
||||||
|
|
||||||
Distance maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(sharedPreferences, context); //TODO Should only be read once!
|
|
||||||
Distance distanceToLastTrackLocation = trackPoint.distanceToPrevious(trackData.lastLocationInCurrentSegment);
|
|
||||||
if (distanceToLastTrackLocation.greaterThan(maxRecordingDistance)) {
|
|
||||||
trackPoint.setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trackData.lastLocationInCurrentSegment = trackPoint;
|
|
||||||
return trackPoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an error message.
|
|
||||||
*
|
|
||||||
* @param message the message
|
|
||||||
*/
|
|
||||||
protected String createErrorMessage(String message) {
|
|
||||||
return String.format(Locale.US, "Parsing error at line: %d column: %d. %s", locator.getLineNumber(), locator.getColumnNumber(), message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the photo url for a file.
|
|
||||||
*
|
|
||||||
* @param externalPhotoUrl the file name
|
|
||||||
*/
|
|
||||||
protected String getInternalPhotoUrl(String externalPhotoUrl) {
|
|
||||||
if (trackData.track.getId() == null) {
|
|
||||||
Log.e(TAG, "Track id is invalid.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (externalPhotoUrl == null) {
|
|
||||||
Log.i(TAG, "External photo url is null.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
String importFileName = KmzTrackImporter.importNameForFilename(externalPhotoUrl);
|
|
||||||
File file = FileUtils.buildInternalPhotoFile(context, trackData.track.getId(), Uri.parse(importFileName));
|
|
||||||
if (file != null) {
|
|
||||||
Uri photoUri = FileUtils.getUriForFile(context, file);
|
|
||||||
return "" + photoUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected TrackPoint createTrackPoint() throws ParsingException {
|
|
||||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT);
|
|
||||||
|
|
||||||
try {
|
|
||||||
trackPoint.setTime(StringUtils.parseTime(time));
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse time: %s", time)), e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (latitude == null || longitude == null) {
|
|
||||||
return trackPoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
trackPoint.setLatitude(Double.parseDouble(latitude));
|
|
||||||
trackPoint.setLongitude(Double.parseDouble(longitude));
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse latitude longitude: %s %s", latitude, longitude)), e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (altitude != null) {
|
|
||||||
try {
|
|
||||||
trackPoint.setAltitude(Altitude.WGS84.of(Double.parseDouble(altitude)));
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse altitude: %s", altitude)), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return trackPoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inserts a trackPoint.
|
|
||||||
*
|
|
||||||
* @param trackPoint the trackPoint
|
|
||||||
*/
|
|
||||||
protected void insertTrackPoint(TrackPoint trackPoint) {
|
|
||||||
trackData.bufferedTrackPoints.add(trackPoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean isFirstTrackPointInSegment() {
|
|
||||||
return trackData.lastLocationInCurrentSegment == null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flushes the TrackPoints to the database.
|
|
||||||
*/
|
|
||||||
private void flushTrackPoints() {
|
|
||||||
if (trackData.bufferedTrackPoints.size() > 0) {
|
|
||||||
contentProviderUtils.bulkInsertTrackPoint(trackData.bufferedTrackPoints, trackData.track.getId());
|
|
||||||
trackData.bufferedTrackPoints.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Track.Id> getImportTrackIds() {
|
|
||||||
return trackIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void cleanImport() {
|
|
||||||
contentProviderUtils.deleteTracks(context, trackIds);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Data for the current track.
|
|
||||||
*
|
|
||||||
* @author Jimmy Shih
|
|
||||||
*/
|
|
||||||
// TODO Why private inner class?
|
|
||||||
private static class TrackData {
|
|
||||||
// The current track
|
|
||||||
final Track track = new Track();
|
|
||||||
|
|
||||||
// The number of segments processed for the current track
|
|
||||||
int numberOfSegments = 0;
|
|
||||||
|
|
||||||
// The last location in the current segment; Null if the current segment doesn't have a last location
|
|
||||||
TrackPoint lastLocationInCurrentSegment;
|
|
||||||
|
|
||||||
// The buffered locations
|
|
||||||
final List<TrackPoint> bufferedTrackPoints = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ParsingException extends RuntimeException {
|
|
||||||
|
|
||||||
private ParsingException(@NonNull String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ParsingException(@NonNull String message, Exception cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "" + getMessage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+165
-44
@@ -17,19 +17,30 @@
|
|||||||
package de.dennisguse.opentracks.io.file.importer;
|
package de.dennisguse.opentracks.io.file.importer;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
import org.xml.sax.Attributes;
|
import org.xml.sax.Attributes;
|
||||||
|
import org.xml.sax.Locator;
|
||||||
import org.xml.sax.SAXException;
|
import org.xml.sax.SAXException;
|
||||||
import org.xml.sax.helpers.DefaultHandler;
|
import org.xml.sax.helpers.DefaultHandler;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.content.data.Altitude;
|
||||||
import de.dennisguse.opentracks.content.data.Distance;
|
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.Speed;
|
||||||
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||||
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
|
import de.dennisguse.opentracks.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Imports a GPX file.
|
* Imports a GPX file.
|
||||||
@@ -43,7 +54,9 @@ import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
|||||||
*
|
*
|
||||||
* @author Jimmy Shih
|
* @author Jimmy Shih
|
||||||
*/
|
*/
|
||||||
public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
public class GpxFileTrackImporter extends DefaultHandler implements XMLImporter.TrackParser {
|
||||||
|
|
||||||
|
private static final String TAG = GpxFileTrackImporter.class.getSimpleName();
|
||||||
|
|
||||||
private static final String TAG_DESCRIPTION = "desc";
|
private static final String TAG_DESCRIPTION = "desc";
|
||||||
private static final String TAG_COMMENT = "cmt";
|
private static final String TAG_COMMENT = "cmt";
|
||||||
@@ -70,23 +83,57 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
|||||||
private static final String TAG_EXTENSION_LOSS = "opentracks:loss";
|
private static final String TAG_EXTENSION_LOSS = "opentracks:loss";
|
||||||
private static final String TAG_EXTENSION_DISTANCE = "opentracks:distance";
|
private static final String TAG_EXTENSION_DISTANCE = "opentracks:distance";
|
||||||
|
|
||||||
/**
|
private Locator locator;
|
||||||
* Constructor.
|
|
||||||
*
|
private final Context context;
|
||||||
* @param context the context
|
|
||||||
*/
|
// Belongs to the current track
|
||||||
|
private final ArrayList<Marker> markers = new ArrayList<>();
|
||||||
|
|
||||||
|
// The current element content
|
||||||
|
private String content = "";
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
private String description;
|
||||||
|
private String category;
|
||||||
|
private String latitude;
|
||||||
|
private String longitude;
|
||||||
|
private String altitude;
|
||||||
|
private String time;
|
||||||
|
private String speed;
|
||||||
|
private String heartrate;
|
||||||
|
private String cadence;
|
||||||
|
private String power;
|
||||||
|
protected String markerType;
|
||||||
|
protected String photoUrl;
|
||||||
|
protected String uuid;
|
||||||
|
protected String gain;
|
||||||
|
protected String loss;
|
||||||
|
protected String distance;
|
||||||
|
|
||||||
|
private final LinkedList<TrackPoint> currentSegment = new LinkedList<>();
|
||||||
|
|
||||||
|
private final TrackImporter trackImporter;
|
||||||
|
|
||||||
public GpxFileTrackImporter(Context context) {
|
public GpxFileTrackImporter(Context context) {
|
||||||
this(context, new ContentProviderUtils(context));
|
this(context, new ContentProviderUtils(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
GpxFileTrackImporter(Context context, ContentProviderUtils contentProviderUtils) {
|
GpxFileTrackImporter(Context context, ContentProviderUtils contentProviderUtils) {
|
||||||
super(context, contentProviderUtils);
|
this.context = context;
|
||||||
|
|
||||||
|
//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, contentProviderUtils, recordingDistanceInterval, maxRecordingDistance, preventReimport);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DefaultHandler getHandler() {
|
public void setDocumentLocator(Locator locator) {
|
||||||
return this;
|
this.locator = locator;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -96,7 +143,7 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
|||||||
onMarkerStart(attributes);
|
onMarkerStart(attributes);
|
||||||
break;
|
break;
|
||||||
case TAG_TRACK:
|
case TAG_TRACK:
|
||||||
onTrackStart();
|
trackImporter.newTrack();
|
||||||
break;
|
break;
|
||||||
case TAG_TRACK_SEGMENT:
|
case TAG_TRACK_SEGMENT:
|
||||||
onTrackSegmentStart();
|
onTrackSegmentStart();
|
||||||
@@ -107,6 +154,11 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void characters(char[] ch, int start, int length) {
|
||||||
|
content += new String(ch, start, length);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void endElement(String uri, String localName, String tag) {
|
public void endElement(String uri, String localName, String tag) {
|
||||||
switch (tag) {
|
switch (tag) {
|
||||||
@@ -117,13 +169,13 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
|||||||
onMarkerEnd();
|
onMarkerEnd();
|
||||||
break;
|
break;
|
||||||
case TAG_TRACK:
|
case TAG_TRACK:
|
||||||
onTrackEnd();
|
trackImporter.setTrack(context, name, uuid, description, category, null);
|
||||||
break;
|
break;
|
||||||
case TAG_TRACK_SEGMENT:
|
case TAG_TRACK_SEGMENT:
|
||||||
onTrackSegmentEnd();
|
onTrackSegmentEnd();
|
||||||
break;
|
break;
|
||||||
case TAG_TRACK_POINT:
|
case TAG_TRACK_POINT:
|
||||||
onTrackPointEnd();
|
currentSegment.add(createTrackPoint());
|
||||||
break;
|
break;
|
||||||
case TAG_NAME:
|
case TAG_NAME:
|
||||||
if (content != null) {
|
if (content != null) {
|
||||||
@@ -197,33 +249,65 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset element content
|
content = "";
|
||||||
content = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void onTrackSegmentStart() {
|
||||||
protected void onTrackStart() throws SAXException {
|
|
||||||
super.onTrackStart();
|
|
||||||
name = null;
|
|
||||||
description = null;
|
|
||||||
category = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void onTrackSegmentEnd() {
|
||||||
protected TrackPoint createTrackPoint() throws ParsingException {
|
if (currentSegment.isEmpty()) {
|
||||||
TrackPoint trackPoint = super.createTrackPoint();
|
Log.w(TAG, "No locations in current segment.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TrackPoint first = currentSegment.getFirst();
|
||||||
|
first.setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
|
||||||
|
|
||||||
|
trackImporter.addTrackPoints(currentSegment);
|
||||||
|
currentSegment.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private TrackPoint createTrackPoint() throws ParsingException {
|
||||||
|
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT);
|
||||||
|
|
||||||
|
try {
|
||||||
|
trackPoint.setTime(StringUtils.parseTime(time));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse time: %s", time)), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (latitude == null || longitude == null) {
|
||||||
|
return trackPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
trackPoint.setLatitude(Double.parseDouble(latitude));
|
||||||
|
trackPoint.setLongitude(Double.parseDouble(longitude));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse latitude longitude: %s %s", latitude, longitude)), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (altitude != null) {
|
||||||
|
try {
|
||||||
|
trackPoint.setAltitude(Altitude.WGS84.of(Double.parseDouble(altitude)));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse altitude: %s", altitude)), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (speed != null) {
|
if (speed != null) {
|
||||||
try {
|
try {
|
||||||
trackPoint.setSpeed(Speed.of(speed));
|
trackPoint.setSpeed(Speed.of(speed));
|
||||||
} catch (Exception e) {
|
} catch (NumberFormatException e) {
|
||||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse speed: %s", speed)), e);
|
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse speed: %s", speed)), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (heartrate != null) {
|
if (heartrate != null) {
|
||||||
try {
|
try {
|
||||||
trackPoint.setHeartRate_bpm(Float.parseFloat(heartrate));
|
trackPoint.setHeartRate_bpm(Float.parseFloat(heartrate));
|
||||||
} catch (Exception e) {
|
} catch (NumberFormatException e) {
|
||||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse heart rate: %s", heartrate)), e);
|
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse heart rate: %s", heartrate)), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -239,7 +323,7 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
|||||||
if (power != null) {
|
if (power != null) {
|
||||||
try {
|
try {
|
||||||
trackPoint.setPower(Float.parseFloat(power));
|
trackPoint.setPower(Float.parseFloat(power));
|
||||||
} catch (Exception e) {
|
} catch (NumberFormatException e) {
|
||||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse power: %s", power)), e);
|
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse power: %s", power)), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -247,21 +331,21 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
|||||||
if (gain != null) {
|
if (gain != null) {
|
||||||
try {
|
try {
|
||||||
trackPoint.setAltitudeGain(Float.parseFloat(gain));
|
trackPoint.setAltitudeGain(Float.parseFloat(gain));
|
||||||
} catch (Exception e) {
|
} catch (NumberFormatException e) {
|
||||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse altitude gain: %s", gain)), e);
|
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse altitude gain: %s", gain)), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (loss != null) {
|
if (loss != null) {
|
||||||
try {
|
try {
|
||||||
trackPoint.setAltitudeLoss(Float.parseFloat(loss));
|
trackPoint.setAltitudeLoss(Float.parseFloat(loss));
|
||||||
} catch (Exception e) {
|
} catch (NumberFormatException e) {
|
||||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse altitude loss: %s", loss)), e);
|
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse altitude loss: %s", loss)), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (distance != null) {
|
if (distance != null) {
|
||||||
try {
|
try {
|
||||||
trackPoint.setSensorDistance(Distance.of(distance));
|
trackPoint.setSensorDistance(Distance.of(distance));
|
||||||
} catch (Exception e) {
|
} catch (NumberFormatException e) {
|
||||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse distance: %s", distance)), e);
|
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse distance: %s", distance)), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -269,11 +353,6 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
|||||||
return trackPoint;
|
return trackPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* On track point start.
|
|
||||||
*
|
|
||||||
* @param attributes the attributes
|
|
||||||
*/
|
|
||||||
private void onTrackPointStart(Attributes attributes) {
|
private void onTrackPointStart(Attributes attributes) {
|
||||||
latitude = attributes.getValue(ATTRIBUTE_LAT);
|
latitude = attributes.getValue(ATTRIBUTE_LAT);
|
||||||
longitude = attributes.getValue(ATTRIBUTE_LON);
|
longitude = attributes.getValue(ATTRIBUTE_LON);
|
||||||
@@ -285,15 +364,6 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
|||||||
loss = null;
|
loss = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onTrackPointEnd() {
|
|
||||||
boolean isFirstTrackPointInSegment = isFirstTrackPointInSegment();
|
|
||||||
TrackPoint trackPoint = getTrackPoint();
|
|
||||||
if (isFirstTrackPointInSegment) {
|
|
||||||
trackPoint.setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
|
|
||||||
}
|
|
||||||
insertTrackPoint(trackPoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onMarkerStart(Attributes attributes) {
|
private void onMarkerStart(Attributes attributes) {
|
||||||
name = null;
|
name = null;
|
||||||
description = null;
|
description = null;
|
||||||
@@ -307,6 +377,57 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onMarkerEnd() {
|
private void onMarkerEnd() {
|
||||||
addMarker();
|
// 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.hasLocation()) {
|
||||||
|
Log.w(TAG, "Marker with invalid coordinates ignored: " + trackPoint.getLocation());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Marker marker = new Marker(null, trackPoint);
|
||||||
|
|
||||||
|
if (name != null) {
|
||||||
|
marker.setName(name);
|
||||||
|
}
|
||||||
|
if (description != null) {
|
||||||
|
marker.setDescription(description);
|
||||||
|
}
|
||||||
|
if (category != null) {
|
||||||
|
marker.setCategory(category);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (photoUrl != null) {
|
||||||
|
marker.setPhotoUrl(photoUrl);
|
||||||
|
}
|
||||||
|
markers.add(marker);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 onFileEnd() {
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import java.time.Instant;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.content.data.Distance;
|
import de.dennisguse.opentracks.content.data.Distance;
|
||||||
import de.dennisguse.opentracks.content.data.Marker;
|
import de.dennisguse.opentracks.content.data.Marker;
|
||||||
@@ -41,7 +40,6 @@ import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
|||||||
import de.dennisguse.opentracks.io.file.exporter.KMLTrackExporter;
|
import de.dennisguse.opentracks.io.file.exporter.KMLTrackExporter;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.util.StringUtils;
|
import de.dennisguse.opentracks.util.StringUtils;
|
||||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Imports a KML file.
|
* Imports a KML file.
|
||||||
@@ -120,6 +118,11 @@ public class KmlFileTrackImporter extends DefaultHandler implements XMLImporter.
|
|||||||
this.trackImporter = new TrackImporter(context, new ContentProviderUtils(context), recordingDistanceInterval, maxRecordingDistance, preventReimport);
|
this.trackImporter = new TrackImporter(context, new ContentProviderUtils(context), recordingDistanceInterval, maxRecordingDistance, preventReimport);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDocumentLocator(Locator locator) {
|
||||||
|
this.locator = locator;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void startElement(String uri, String localName, String tag, Attributes attributes) throws SAXException {
|
public void startElement(String uri, String localName, String tag, Attributes attributes) throws SAXException {
|
||||||
switch (tag) {
|
switch (tag) {
|
||||||
@@ -152,7 +155,7 @@ public class KmlFileTrackImporter extends DefaultHandler implements XMLImporter.
|
|||||||
public void endElement(String uri, String localName, String tag) throws SAXException {
|
public void endElement(String uri, String localName, String tag) throws SAXException {
|
||||||
switch (tag) {
|
switch (tag) {
|
||||||
case TAG_KML:
|
case TAG_KML:
|
||||||
onFileEnded();
|
onFileEnd();
|
||||||
break;
|
break;
|
||||||
case TAG_PLACEMARK:
|
case TAG_PLACEMARK:
|
||||||
case TAG_PHOTO_OVERLAY:
|
case TAG_PHOTO_OVERLAY:
|
||||||
@@ -163,7 +166,7 @@ public class KmlFileTrackImporter extends DefaultHandler implements XMLImporter.
|
|||||||
onMarkerLocationEnd();
|
onMarkerLocationEnd();
|
||||||
break;
|
break;
|
||||||
case TAG_GX_MULTI_TRACK:
|
case TAG_GX_MULTI_TRACK:
|
||||||
onTrackEnd();
|
trackImporter.setTrack(context, name, uuid, description, category, icon);
|
||||||
break;
|
break;
|
||||||
case TAG_GX_TRACK:
|
case TAG_GX_TRACK:
|
||||||
onTrackSegmentEnd();
|
onTrackSegmentEnd();
|
||||||
@@ -362,14 +365,14 @@ public class KmlFileTrackImporter extends DefaultHandler implements XMLImporter.
|
|||||||
location.setLatitude(Double.parseDouble(latitude));
|
location.setLatitude(Double.parseDouble(latitude));
|
||||||
location.setLongitude(Double.parseDouble(longitude));
|
location.setLongitude(Double.parseDouble(longitude));
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
throw new AbstractFileTrackImporter.ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse latitude longitude: %s %s", latitude, longitude)), e);
|
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse latitude longitude: %s %s", latitude, longitude)), e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (altitude != null) {
|
if (altitude != null) {
|
||||||
try {
|
try {
|
||||||
location.setAltitude(Double.parseDouble(altitude));
|
location.setAltitude(Double.parseDouble(altitude));
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
throw new AbstractFileTrackImporter.ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse altitude: %s", altitude)), e);
|
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse altitude: %s", altitude)), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -420,38 +423,11 @@ public class KmlFileTrackImporter extends DefaultHandler implements XMLImporter.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
private String createErrorMessage(String message) {
|
||||||
return String.format(Locale.US, "Parsing error at line: %d column: %d. %s", locator.getLineNumber(), locator.getColumnNumber(), message);
|
return String.format(Locale.US, "Parsing error at line: %d column: %d. %s", locator.getLineNumber(), locator.getColumnNumber(), message);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onFileEnded() {
|
private void onFileEnd() {
|
||||||
trackImporter.addMarkers(markers);
|
trackImporter.addMarkers(markers);
|
||||||
trackImporter.finish();
|
trackImporter.finish();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package de.dennisguse.opentracks.io.file.importer;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
public class ParsingException extends RuntimeException {
|
||||||
|
|
||||||
|
private ParsingException(@NonNull String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ParsingException(@NonNull String message, Exception cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "" + getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ import de.dennisguse.opentracks.stats.TrackStatistics;
|
|||||||
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
||||||
import de.dennisguse.opentracks.util.FileUtils;
|
import de.dennisguse.opentracks.util.FileUtils;
|
||||||
import de.dennisguse.opentracks.util.LocationUtils;
|
import de.dennisguse.opentracks.util.LocationUtils;
|
||||||
|
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles logic to import:
|
* Handles logic to import:
|
||||||
@@ -93,8 +94,28 @@ public class TrackImporter {
|
|||||||
this.markers.addAll(markers);
|
this.markers.addAll(markers);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setTrack(Track track) {
|
void setTrack(Context context, String name, String uuid, String description, String category, String icon) {
|
||||||
this.track = 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) {
|
||||||
|
icon = TrackIconUtils.getIconValue(context, category);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
track.setIcon(icon != null ? icon : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
void finish() {
|
void finish() {
|
||||||
@@ -155,12 +176,11 @@ public class TrackImporter {
|
|||||||
Instant time = current.getTime();
|
Instant time = current.getTime();
|
||||||
if (current.getLatitude() == 100) {
|
if (current.getLatitude() == 100) {
|
||||||
//TODO Remove by 31st December 2021.
|
//TODO Remove by 31st December 2021.
|
||||||
current = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL);
|
trackPoints.set(i, new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, time));
|
||||||
current.setTime(time);
|
|
||||||
} else if (current.getLatitude() == 200) {
|
} else if (current.getLatitude() == 200) {
|
||||||
//TODO Remove by 31st December 2021.
|
//TODO Remove by 31st December 2021.
|
||||||
current = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL);
|
trackPoints.set(i, new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, time));
|
||||||
current.setTime(time);
|
//TODO Delete location
|
||||||
} else if (!LocationUtils.isValidLocation(current.getLocation())) {
|
} else if (!LocationUtils.isValidLocation(current.getLocation())) {
|
||||||
throw new ImportParserException("Invalid location detected: " + current);
|
throw new ImportParserException("Invalid location detected: " + current);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ public class XMLImporter {
|
|||||||
try {
|
try {
|
||||||
SAXParserFactory.newInstance().newSAXParser().parse(inputStream, parser.getHandler());
|
SAXParserFactory.newInstance().newSAXParser().parse(inputStream, parser.getHandler());
|
||||||
return parser.getImportTrackIds();
|
return parser.getImportTrackIds();
|
||||||
} catch (SAXException | ParserConfigurationException | AbstractFileTrackImporter.ParsingException e) {
|
} catch (SAXException | ParserConfigurationException | ParsingException e) {
|
||||||
Log.e(TAG, "Unable to import file", e);
|
Log.e(TAG, "Unable to import file", e);
|
||||||
if (parser.getImportTrackIds().size() > 0) {
|
if (parser.getImportTrackIds().size() > 0) {
|
||||||
parser.cleanImport();
|
parser.cleanImport();
|
||||||
|
|||||||
Reference in New Issue
Block a user